本文,将会简述如何利用Matlab的强大功能,调用神经网络处理验证码的识别问题。
预备知识,Matlab基础编程,神经网络基础。
可以先看下:
Matlab基础视频教程
Matlab经典教程——从入门到精通
神经网络入门
验证码识别原理
Matlab对图像读入处理,去掉噪声点和较浅的点,进行二值化,将图像转变为0/1矩阵,这样就完成了预处理。
然后要对图像进行切割,取到每个数字的小图片位置,将其缩放至等大小,方便神经网络进一步处理。
最后将图片转成神经网络能够识别的格式,例如BP网络,则将其转为行向量,深卷积网络,则将其转为矩阵即可。
识别预处理
Matlab对验证码的识别是基于神经网络的,但预处理工作还是占了整体工作的大半,将数据整理好并处理成对应可用的格式,问题就简单了很多。
Matlab的一大缺陷是不注重数据结构,其结构体无比难用,所以我们这里将尽可能使用矩阵进行处理,而参数较多时,我们也只是简单的将其放入到元胞数组中,不优雅之处,敬请见谅。
首先介绍一下matlab的图像基本处理函数:
img = imread('path') # 返回一个图像的矩阵,其每个元素的值,包含rgb三个通道的数据。imshow(img) imgGray = rgb2gray(img) # 转为灰度图像thresh = graythresh(imgGray); % 自动确定二值化阀值BW = 1 - im2bw(imgGray,thresh); % 二值化,且取反,黑的部分是0,白的部分是1,I2 = bwareaopen(BW, 8, 8); % 去除连通分量中小于10的离散点
我们看看目标的图片:
有很多随机的像素点干扰,我们需要将这些像素点去除,然后进行图像切割。
切割图片实际上很简单,就是对图片中每行每列进行统计,然后将形成的波形进行扫描,每个从0上升又下降到0的区域,就是一个字符。
切割后的图片:
下面我们来写一个完整的函数分割器函数,为了检测正确性,我们这里提供了isshow标记,如果设置为true,将会打印中间的调试信息。
function y = cutting(img, isshow) if nargin < 2; isshow = false; end if isshow; imshow(img); end imgGray = rgb2gray(img); thresh = graythresh(imgGray); BW = 1 - im2bw(imgGray,thresh); I2 = bwareaopen(BW, 8, 8); varray = sum(I2); imgsize = size(I2); if isshow figure; imshow(imgGray); harray = sum(I2'); x1 = 1 : imgsize(1, 1); x2 = 1 : imgsize(1, 2); figure; plot(x1, harray, 'r+-', x2, varray, 'y*-'); figure; imshow(I2); end va = mean(varray); harray = sum(I2'); vb = mean(harray); isanum = false; sumy = 0; for i = 1 : imgsize(1, 1) if harray(i) > vb; if isanum == false; isanum = true; cvb = i; end else if isanum; isanum = false; cve = i; sumy = sumy + 1; if isshow; hold on; plot([0 imgsize(1,2)], [cvb cvb],'r--'); plot([0 imgsize(1,2)], [cve cve], 'r--'); end end end end y = {} sumy = 0; for i = 1 : imgsize(1, 2); if varray(i) > va; if isanum == false; isanum = true; ctb = i; end else if isanum; isanum = false; cte = i; sumy = sumy + 1; if isshow; hold on; plot([ctb ctb], [0 imgsize(1,1)],'r--'); plot([cte cte], [0 imgsize(1,1)],'r--'); end t = I2(cvb:cve, ctb:cte); y{sumy} = t; end end endend
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
我们这个函数实现了对图片的预处理工作,成功的将大部分图片分割成了小图片,放到返回的元胞数组中,但这还有一个重要的问题,就是切割后的图片并不等大小。
并且,我们为了让这些图片能够方便的进行训练,希望将他们归好类别,方便标记。将图像等大小十分简单,只需要将图像的最大的长和宽找到,然后对矩阵进行扩展,多余的位置补0即可。
for i = 1 : length(imgs_name) img_name = imgs_name{i}; imgs = cutting(imread(['train/',img_name,'.jpg']), false); if (length(imgs) == length(img_name)) imgs_num_size = length(img_name); for j = 1 : imgs_num_size tmp_num = str2num(img_name(j)) + 1; imgs_sample_num(tmp_num) = imgs_sample_num(tmp_num) + 1; imgs_sample{tmp_num, imgs_sample_num(tmp_num)} = imgs{j}; tmp_size = size(imgs{j}); end endendmax_size = [16 16];for i = 1 : 10 for j = 1 : imgs_sample_num(i) temp = zeros(max_size); imgs_size = size(imgs_sample{i, j}); temp(1:imgs_size(1,1), 1:imgs_size(1,2)) = imgs_sample{i, j}; imgs_sample{i, j} = temp; endend
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
分别用BP网络和深卷积网络来进行图像识别
BP网络结构
由于已经做好了充足的预处理工作,那么接下来的识别就十分简单了,BP网络和深卷积网络都有对应的库支持操作,所以我们只需要编写配置代码就可以了。
BP网络就是简单的三层结构,由于层数太大可能带来误差残差太小等问题,造成训练困难,我们这里使用足够多的隐层节点保障BP网络的精度就可以了。
输入就是整个图像转为1维向量,输出则是可能属于的类别的概率,是一个10维的向量,要确定分类结果,就将其中最大的数字找到即可。
BP网络的训练及识别
那么,我们就可以开始组织训练数据了。
function [inputs outputs] = buildtrainset(imgs, number) i = 1; for k = 1 : 10 for j = 1 : number(k) input = imgs{k, j}; input_size = numel(input); inputs(i, :) = reshape(input', input_size, 1); outputs(i, :) = zeros(10, 1); outputs(i, k) = 1; i = i + 1; end endend
然后训练并比较正确与否:
function y = runbp(imgs_sample, imgs_sample_num, max_size) [a, b] = buildtrainset(imgs_sample, imgs_sample_num); net = bpann(a', b'); image_dir=dir('image/*.jpg'); for i = 1: length(image_dir) str_name = image_dir(i).name; imgs_test{i} = str_name(1:4); end rightnum = 0; sumnum = 0; for i = 1 : length(imgs_test) img_name = imgs_test{i}; imgs = cutting(imread(['image/',img_name,'.jpg']), false); if (length(imgs) == length(img_name)) for j = 1 : length(img_name) tmp_num = str2num(img_name(j)) + 1; temp = zeros(max_size); imgs_size = size(imgs{j}); temp(1:imgs_size(1,1), 1:imgs_size(1,2)) = imgs{j}; imgs{j} = temp; input_size = numel(temp); testInput(j, :) = reshape(temp', input_size, 1); end size(testInput) Y = sim( net , testInput' ); mans = [1:4]; for j = 1 : length(img_name) ymax = 0; yans = NaN; for k = 1 : 10 if (ymax < Y(k, j)) ymax = Y(k, j); yans = k; end end mans(j) = yans-1; sumnum = sumnum + 1; if (mans(j) == str2num(img_name(j))) rightnum = rightnum + 1; end end img_name mans end end rightdata = [rightnum, sumnum-rightnum] pie(rightdata, {'right', 'wrong'});end
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
经过1500次左右的迭代,收敛精度基本达到了要求:
识别结果:
1830mans = 1 8 3 02940mans = 2 9 4 03742mans = 3 7 4 25980mans = 5 9 8 06739mans = 6 7 3 98240mans = 8 2 4 08324mans = 8 3 2 4
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
但遗憾的是,识别正确率并不是100%,而是70%,由于有3组数据在预处理时失败了,并没有被正确的二值化,造成了无法识别,但可以看出,神经网络的识别正确率还是相当高的。
深度卷积网络的图像识别
我们这里使用了一个流行的深度学习工具包DeepLearnToolbox
,这个工具包可以在github上被找到。
将其下载下来,然后添加两个path路径,将其引用:
path(path, 'DeepLearnToolbox-master/CNN/')path(path, 'DeepLearnToolbox-master/util/')
然后我们构建一个卷积网络的结构struct,并利用类似BP的方式,将数据集构造好:
[a, b] = buildtrainset_cnn(imgs_sample, imgs_sample_num); cnn.layers = { struct('type', 'i') struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) struct('type', 's', 'scale', 2) struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) struct('type', 's', 'scale', 2) }; cnn = cnnsetup(cnn, a, b);
而卷积网络的配置如下:
opts.alpha = 2; opts.batchsize = size(a, 3); opts.numepochs = 2000; load cnn_save cnn;
这样我们来观察一下网络的结构,这是一个复杂的网络,input是一个16×16的图片,而每次卷积的核都是5×5的,还会有两个降维层。
然后我们会进行测试,和BP网络几乎一样
image_dir=dir('image/*.jpg'); for i = 1: length(image_dir) str_name = image_dir(i).name; imgs_test{i} = str_name(1:4); end rightnum = 0; sumnum = 0; for i = 1 : length(imgs_test) img_name = imgs_test{i}; imgs = cutting(imread(['image/',img_name,'.jpg']), false); if (length(imgs) == length(img_name)) for j = 1 : length(img_name) tmp_num = str2num(img_name(j)) + 1; temp = zeros(max_size); imgs_size = size(imgs{j}); temp(1:imgs_size(1,1), 1:imgs_size(1,2)) = imgs{j}; imgs{j} = temp; input_size = size(temp); testInput(:, :, j) = reshape(temp', input_size(1,1), input_size(1,2)); end cnn = cnnff(cnn, testInput); cnn.o [~, mans] = max(cnn.o); img_name mans = mans-1 end end plot(cnn.rL);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
附: 源码仓库
https://github.com/sunxfancy/ANN2