深度学习caffe实战验证码识别

来源:互联网 发布:护盾数据恢复软件下载 编辑:程序博客网 时间:2024/06/04 19:21

深度学习caffe实战验证码识别

标签: 验证码识别深度学习caffe
 3844人阅读 评论(24) 收藏 举报
 分类:

狭义上讲验证码识别是将验证码图像转化成字符串值,传统方法常常先对验证码图像进行字符分割,再对验证码进行识别,劣势在于字符分割方法往往针对不同风格的验证码需要做修改,某些验证码加入噪声或线条,字符位置不固定及粘连时,字符分割效果不好,也会影响后续字符识别。除了只包含字母和数字的验证码,国内还有一些识别汉字的验证码,还有你以为考你认字符串实际上考你加减乘除的验证码,还有图像匹配和图像分类的验证码,种类和花样很多。所以验证码识别这种东西不可沉迷,毕竟道高一尺魔高一丈,道再高一尺。你破解了别人的验证码,维护人员立马弄出了新花样,换了一批新的验证码,你之前的方法就不好使了,于是你又破解了他的,他又改了新的…看谁能耗的久…不过博主作为菜鸟,做做验证码识别还是很有意义的,如果你也是搞深度学习的新手,不妨接着看下去,能够把数据集的准备和制作、深度网络模型的设计、训练和测试的流程走一遍。

博主是用深度学习做多任务分类的思路来识别验证码。多任务学习是针对数据给出多个监督信息(标签)进行学习,例如识别一张图像中的脸是否是人脸、脸部表情、性别、年龄等,识别图像中车的颜色、车型、姿态等,都属于多任务分类。项目用深度学习做多标签分类,是用深度神经网络对整张验证码图片进行多标签学习,来完成多任务分类,端到端的识别出验证码中的所有字符。这种思路同样可以用于车牌识别中。

用深度学习来做验证码识别,优势在于,只需要找一个合适的网络模型稍加修改,再给网络送入足够的有标签样本进行训练,就能达到很好的识别效果,无论验证码里面是白的黑的正的歪的、何种字体、粘不粘连都可以,也没必要做去噪、二值化、纠正和调各种阈值等各种处理(不然验证码风格稍微一变,你的处理方法就得改),直接端到端识别验证码的效果完全不比先做字符分割再做识别差。但其劣势也很明显,对不同手段生成的不同风格的验证码,都需要收集、爬取或模仿其验证码风格自己写代码生成大量样本,以维持较高的识别率。

为了满足多任务分类要求,对caffe源码进行针对性的修改满足多标签的输入和训练;然后在2012年ImageNet大赛冠军AlexNet网络模型的基础上进行了修改,作为多任务分类的模型;在windows平台下(ubuntu下当然也可以)利用caffe对两个不同的数据集进行了训练和测试;最后利用caffe的matlab接口,修改classfication_demo.m来做批量验证码图片识别,对caffe自带的classification.cpp做修改,也可以满足多任务分类。如果还没有编译caffe(gpu版本)及matlab接口,请先参照我的博客Windows下编译caffe

第一个验证码数据集条件比较理想,在生成时做了添加随机噪声点、字符变形、旋转缩放、随机赋色等处理。训练集64536张,验证集9096张(验证模型好坏),另外有714张用于做测试(测试模型效果),图片大小为88x28,共有数字0~9和大写字母A~Z共36类,每张验证码图像中包含四个字符,数据集下载。经过训练,该验证集图像上四个字符单个字符的识别准确率都达到了99.5%。而用于测试的714张验证码中,有15个识别出错,验证码整体识别结果准确率接近98%,主要是对0和O比较容易搞混。

这里写图片描述

第二个验证码数据集比较常见,更具有挑战性,生成验证码图像时使用了6种字体并加入随机线条、字体变形、旋转、随机赋色等处理。训练集63926张,验证集11914张,另有4112张用于测试,图片大小为150x40,共有数字0~10和小写字母a~z共36类(实际上该数据集中没有数字0和小写字母o,但并不影响我们用36类做分类),每张验证码图像中包含五个字符,数据集下载,其验证码生成程序来源github链接。经过训练,验证集验证码图像上五个字符单个字符的识别准确率平均达到了88%,最后对4112张图片进行测试,验证码整体识别结果准确率超过63%,虽然效果不是特别理想,但仔细观察下图,会发现随机线条的加入使得很多字符人眼都难以确认。

这里写图片描述

有了数据集之后,下面介绍利用深度学习进行验证码识别的具体步骤,在两种数据集上采用的方法是基本一致的,但第二个数据集似乎更有趣一些,以此为例。

1 数据准备

下载得到的数据集中的前三个文件夹,包括captcha_train、captcha_val和test_images,分别是验证码图像训练集、验证集和用于测试的图片文件夹。 
这里写图片描述

captcha_train.txt和captcha_val.txt分别是captcha_train和captcha_val数据集对应的多标签文本文件,可以由matlab实现,生成captcha_train.txt和captcha_val.txt的create_captcha_train_val_txt.m代码如下。

%create_captcha_train_val_txt.m%创建验证码的多标签标签txt文件%验证码类别数为36,包括0~9和a~z(a~z的ascii码范围为97~122,减去87对应10~35,构成0~35共36类)datadir = 'D:\data\Captcha2\captcha_train'; %train数据集fp=fopen('D:\data\Captcha2\captcha_train.txt','w'); %新建一个txt文件存放结果% datadir = 'D:\data\Captcha2\captcha_val'; %test数据集% fp = fopen('D:\data\Captcha2\captcha_val.txt','w'); %新建一个txt文件存放结果imagefiles = dir(datadir); %列出目录下所有文件for i = 3:length(imagefiles) %windows系统下所有文件夹下都有两个隐藏文件排在最前面,跳过    filename = imagefiles(i).name; %获取某张图像文件名    fprintf(fp,'%s ',filename); %打印文件名    for j=1:5 %打印多标签(5个)        if abs(filename(j)) >= 97 && abs(filename(j)) <= 122 %如果是a~z,则97~122 -> 10~35            label = abs(filename(j)) - 87;            fprintf(fp,'%d ',label);               elseif abs(filename(j)) > 122 || abs(filename(j)) < 97 %如果是0~9,保持不变            fprintf(fp,'%s ',filename(j));        end    end    fprintf(fp,'\n');   endfclose(fp); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

然后在caffe的data\目录下新建captcha文件夹,并将这些文件/文件夹都拷贝到captcha目录下。

2 修改caffe源码使其支持多标签输入和训练

caffe源码对hdf5支持多标签,但lmdb格式只支持单标签输入和训练,但lmdb读取效率更高。对caffe源码做以下修改使得caffe对lmdb格式满足多标签输入和训练。这里提供修改后的相应文件下载 多标签修改

打开caffe目录下src\caffe\proto\caffe.proto,找到Datum如下可以发现label是不可重复(单个)int32型变量,这里不要注释掉它,在后面新增可重复(多个)的float型变量labels,然后在caffe目录下找到并删除src\caffe\proto\caffe.pb.cc和include\caffe\proto\caffe.pb.h这两个文件,再重新编译caffe。注:红框表示需要注意或待修改的部分,绿框为新增或修改后的内容。

这里写图片描述

然后对convert_imageset.cpp(项目convert_imageset中)进行如下修改,修改后可以读取五个标签值。不过修改后也导致后面的ReadImageToDatum函数也要进行修改,转到ReadImageToDatum函数定义处,即在io.hpp(项目libcaffe中include\util\下)中,重载函数ReadImageToDatum并添加新的ReadFileLabelsToDatum函数,然后在io.cpp(项目libcaffe中src\util\下)给出上述两个函数的实现代码,分别参考原来的ReadImageToDatum和ReadFileToDatum进行修改。

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

然后对data_layer.cpp(libcaffe项目中src\layers下)进行如下两处修改,然后重新编译libcaffe,再右键convert_imageset项目生成,这样就完成了caffe源码的修改,能够支持多标签的输入和训练了。

这里写图片描述

这里写图片描述

3 数据格式转换

利用修改后的caffe编译出的convert_imageset将图像数据及标签转换成具有更高读取效率的lmdb格式文件。在caffe目录下新建create_captcha_lmdb.txt文件,在文件中写入如下,其中图像统一缩放到227x227,shuffle表示乱序处理。保存后修改后缀为.bat文件,执行之,开始读取图像和标签并转换成lmdb文件,等待几分钟后,转换生成完毕,可以在data\captcha\下看到生成的captcha_train_lmdb和captcha_val_lmdb文件夹,文件夹比较大,大约接近16G和2G,要预留好足够的内存空间。需要注意的是,我们这里并没有用compute_mean.exe来生成均值文件,是因为我们后面统一采用了ImageNet数据集的均值作为样本均值,ImageNet数据集的均值具有统计特性,而我们的样本又是随机生成的,所以这是合理的做法。

.\Build\x64\Debug\convert_imageset.exe --resize_height=227 --resize_width=227 --backend="lmdb" --shuffle .\data\captcha\captcha_train\ .\data\captcha\captcha_train.txt .\data\captcha\captcha_train_lmdbecho..\Build\x64\Debug\convert_imageset.exe --resize_height=227 --resize_width=227 --backend="lmdb" --shuffle .\data\captcha\captcha_val\ .\data\captcha\captcha_val.txt .\data\captcha\captcha_val_lmdbecho.pause
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

这里写图片描述

4 设计多任务训练深度网络

在models文件夹下新建captcha文件夹,将models\bvlc_alexnet下的deploy.prototxt、solver.prototxt、train_val.prototxt拷贝到captcha文件夹下,然后分别对这三个文件进行修改。需要注意的是,train_val.prototxt和deploy.prototxt都是网络结构的描述文件,但train_val.prototxt还指出了训练集和验证集样本数据及标签,用于训练和验证;训练好的模型和deploy.prototxt可以用于测试单张或多张图片。

用caffe的Python接口提供的draw_net.py可以实现caffe网络结构可视化,也有在线可视化网页 netscope 提供网络结构可视化功能,将train_val.prototxt(或者deploy.prototxt)描述的网络结构画出来。

使用draw_net.py画出的alexnet的网络结构的最后几层如图所示,alexnet是单标签分类网络,后面接了三个全连接层fc6、fc7、fc8,其中全连接层fc6和前面的卷积层相连,fc8同label一起与accuracy层(test模式下)和loss层相连。

这里写图片描述

而我们现在进行验证码识别的思路是多标签分类,输入一张验证码图像输出多路分类结果。为此我们需要对train_val.prototxt和deploy.prototxt(批量测试时会用到deploy.prototxt)做一定修改。 
train_val.prototxt的具体修改方法是对fc6及前面的层保持不变,从fc6后展开多路分类网络,例如第二个数据集验证码中有5个字符,那我们在fc6后连接5个全连接层fc7_1、fc7_2、fc7_3、fc7_4、fc7_5(原先只有1个全连接层fc7),这些全连接层同样经过relu层和dropout层;然后分别与全连接层fc8_1、fc8_2、fc8_3、fc8_4、fc8_5相连,节点个数由1000改成36(0~9、a~z共36类);再由Slice层分割的多标签label_1、label_2、label_3、label_4、label_5、分别和fc8_1、fc8_2、fc8_3、fc8_4、fc8_5一起接上各自对应的accuracy层(test模式下)和loss层。 
修改好train_val.prototxt后,就可以据此自行对deploy.prototxt进行修改了,最后在solver.prototxt中修改网络训练参数,这里提供修改后的deploy.prototxt、solver.prototxt、train_val.prototxt,网络模型文件下载

这里写图片描述

5 训练和测试深度网络

在做好上述准备的基础上,在caffe目录下新建train_captcha.txt,在里面写入如下语句,保存后修改后缀名为.bat文件,双击执行开始训练。博主在NVIDIA GTX1060 6G单gpu上对第二个数据集训练迭代15000次,耗时约两个半小时,之后accuracy和loss趋于稳定,但loss还是有点大。虽然模型在第一个数据集上字符准确率达到99.5%以上,但在第二个数据集上的效果并不是特别理想,如图第1、2、3、4、5个字符在验证集上的准确率约为95.76%、90.61%、88.09%、91.50%、96.21%

.\Build\x64\Debug\caffe.exe train -solver .\models\captcha\solver.prototxt -gpu 0echo.pause
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

这里写图片描述

最后利用caffe的matlab接口调用caffe来对验证码图片进行批量测试,在caffe下的matlab\demo下新建test_captcha.m对验证码测试图片进行批量测试,参考classification.demo进行修改得到test_captcha.m,生成的测试结果输出到test_captcha_result.txt中,其matlab代码如下,测试要用到deploy.prototxt和训练好的.caffemodel网络模型,这里提供训练好的迭代了15000次的captcha_iter_15000.caffemodel 模型文件下载。测试结果显示,4112张验证码有1220个识别出错,识别准确率为70.33%,在GTX1060 6G单gpu上平均每张验证码识别耗时10ms。

%利用训练好的caffe网络模型对验证码图片进行批量测试%参照caffe-master\matlab\demo\classification_demo.m%%%对第二种验证码类型进行批量测试function test_captcha()clear;addpath('..');%添加上级目录搜索路径addpath('.');%添加当前目录搜索路径caffe.set_mode_gpu(); %设置gpu模式caffe.set_device(0); %gpu的id为0%caffe.set_mode_cpu();net_model = 'C:\Users\Administrator\Desktop\caffe-master-captcha\models\captcha\deploy.prototxt'; %网络模型deploy.prototxtnet_weights = 'C:\Users\Administrator\Desktop\caffe-master-captcha\models\captcha\captcha_iter_24000.caffemodel'; %训练好的模型文件phase = 'test'; %不做训练,而是测试net = caffe.Net(net_model, net_weights, phase); %获取网络tic;error = 0;%批量读取图像进行测试datadir = 'C:\Users\Administrator\Desktop\caffe-master-captcha\data\Captcha\test_images';imagefiles = dir(datadir);fp = fopen('captcha_test_result.txt','w');for i = 3:length(imagefiles)    im = imread(fullfile(datadir,imagefiles(i).name));    input_data = {prepare_image(im)}; %图像数据预处理    scores = net.forward(input_data); %做前向传播    %获取前向传播多任务的最后层向量,然后求出最好成绩和对应的类别    scores_1 = net.blobs('prob_1').get_data();    scores_2 = net.blobs('prob_2').get_data();    scores_3 = net.blobs('prob_3').get_data();    scores_4 = net.blobs('prob_4').get_data();    scores_5 = net.blobs('prob_5').get_data();    best_score = zeros(5,1);    best = zeros(5,1);    [best_score(1),best(1)] = max(scores_1);    [best_score(2),best(2)] = max(scores_2);    [best_score(3),best(3)] = max(scores_3);    [best_score(4),best(4)] = max(scores_4);    [best_score(5),best(5)] = max(scores_5);    %输出识别结果    filename = imagefiles(i).name;    fprintf(fp,'%s',filename);%图片名称    flag = 1; %是否识别正确    for j = 1:5        best(j) = best(j) - 1; %matlab从1开始,改为从0开始        if best(j) >= 10 %a~z            best(j) = best(j) + 87; %10~35 -> 97~122            fprintf(fp,' %s',char(best(j)));            if char(best(j)) ~= filename(j)                flag = 0;            end        else            fprintf(fp,' %d',best(j));            if best(j) ~= (abs(filename(j)) - 48) %0~9对应的ascii码为48~57                flag = 0;            end        end     end    if flag == 0        error = error + 1;        %disp(error);        fprintf('error: %d\n',error);        fprintf(fp,'*********'); %识别错误,输出中做明显标记    end    fprintf(fp,'\r\n');endtotal_time = toc;%打印到结果文本中fprintf(fp,'total_time: %.3f s\n',total_time);fprintf(fp,'aver_time: %.3f s\n',total_time/(length(imagefiles)-2));fprintf(fp,'error/total: %d/%d\n',error,length(imagefiles)-2);fprintf(fp,'accurary: %.4f\n',1.0 - (error*1.0)/(length(imagefiles)-2));fclose(fp);%打印到屏幕上fprintf('total_time: %.3f s\n',total_time);fprintf('aver_time: %.3f s\n',total_time/(length(imagefiles)-2));fprintf('error/total: %d/%d\n',error,length(imagefiles)-2);fprintf('accurary: %.4f\n',1.0 - (error*1.0)/(length(imagefiles)-2));%disp(['error/total: ',num2str(error),'/',num2str(length(imagefiles)-2)]);endfunction im_data = prepare_image(im)%d = load('../+caffe/imagenet/ilsvrc_2012_mean.mat');%mean_data = d.mean_data;%resize to 227 x 227im = imresize(im,[227 227],'bilinear');%caffe的blob顺序是[w h c num]%matlab:[h w c] rgb -> caffe:[w h c] bgrim_data = im(:,:,[3,2,1]); %rgb -> bgrim_data = permute(im_data,[2,1,3]); %[h w c] -> [w h c][w,h,~] = size(im_data);%ImageNet数据集的均值具有统计规律,这里可以直接拿来使用mean_data(:,:,1) = ones(w,h) .* 123; %rmean_data(:,:,2) = ones(w,h) .* 117; %gmean_data(:,:,3) = ones(w,h) .* 104; %bim_data = single(im_data);im_data = im_data - single(mean_data);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
  • 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
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 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
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101

这里写图片描述

这里写图片描述

也可以对caffe的example\cpp_classification\下的classification.cpp进行修改来满足多任务分类,用于验证码识别,这里提供修改好的classification.cpp 测试代码下载 ,不过这里还只做了单张图片测试,果然c++相比matlab还是要麻烦点>_<

这里写图片描述

因为第一个验证码数据集条件比较理想,模型表现很完美,但第二个数据集的表现就没那么优秀了,出现过拟合后尝试增加训练的batchsize、适当调整学习率、增加dropout来降低loss,还可以考虑用更深的网络进一步提高识别的准确率。不过博主认为单纯的刷准确率意义不大,最理想的情况是在网络模型只做很小修改的情况下,对采取不同风格和方法生成的验证码都能比较好的识别效果。

至此,完成了验证码识别的整个流程,你有收获吗?