从零开始搭建深度学习验证码识别模型
Posted Zip Zou
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零开始搭建深度学习验证码识别模型相关的知识,希望对你有一定的参考价值。
文章目录
从零开始搭建深度学习验证码识别模型
CNN模型与图像识别
CNN模型是图像问题的基本深度学习算法,使用CNN算法不用人工从图片中提取特征,更加end2end,符合表示学习的特征,避免繁琐、低效的特征工程。CNN算法目前在CV领域已经成为基本方法之一。
CNN模型的核心在于,利用卷积核在特征图上进行运算,从中提取到充足的特征。在CNN的研究发现,在浅层网络,CNN模型可以提取到部分简单的特征,如轮廓等,而深层CNN则将基础特征进行整合,提取到更加复杂的特征,从而能够对图片中的内容进行特征提取。
CNN的核心在于卷积运算规则,其大致为:
C N N ( m a p ) = ∑ i = 0 f h ∑ j = 0 f w f i l t e r h , w ⋅ s l i c e ( m a p ) h , w CNN(map)=\\sum\\limits_i=0^f_h \\sum\\limits_j=0^f_w filter_h,w \\cdot slice(map)_h,w CNN(map)=i=0∑fhj=0∑fwfilterh,w⋅slice(map)h,w
其中, s l i c e ( ⋅ ) slice(\\cdot) slice(⋅)为特征图切片运算,如当 f i l t e r filter filter为 3 3 3时,则 m a p map map将会根据步长,从中进行切片,并与卷积核进行element-wise乘积运算后并求和。
在CV诸多任务中,卷积层往往被用来做特征提取,而到具体的任务时,需要拼接更多的网络,如拼接全连接网络进行分类任务。
验证码识别,本质上也为一个图像识别任务。在对象识别模型中,通常需要从图像中尽可能识别多的对象,并以框的形式对其位置进行标记。验证码识别也可采用该方式实现,该方法为multi-stage方法,即:通常使用对象识别模型,识别图片中的文字,并用框标记出文字所在位置,再利用CNN和FCN的结构对所识别的文字进行分类。并且,若为文档OCR识别,输出层还可能借助LSTM等RNN结构网络。
考虑到验证码通常位数有限,即4位、5位较为常见,因此该模型采用end2end multi-task方法也可满足需求,且模型复杂度并不高。multi-task任务可简单的理解为,有多个输出层负责不同任务的输出,其弊端在于扩展性低,如只能识别固定数量的对象。
验证码数据集介绍
所有训练数据均以验证码图片内容为名称命名,如2ANF.jpg
,因此可以保证训练数据没有重复项,根据文件名即可获取样本label。
数据集下载:Dataset-Google Drive for Easy Captcha
数据规格:48,320张验证码图片,全由
Easy Captcha
框架生成,大小为 120 × 80 120 \\times 80 120×80。
验证码示例:
EasyCaptcha验证码特点在于可以构造Gif动态验证码,而其他验证码则显得相对简单,主要在于该验证码间隔较开,易于区分,因此识别较为简单。根据对上例中的验证码分析可知,验证码由不定位置的1-2个圆圈与曲线构成噪音,对文本加以干扰,文字颜色可变。从布局来看,文字的布局位置相对固定,且间隔也相对固定,这无疑也简化了识别过程。
数据集下载:Dataset-Google Drive for Kaptcha
数据规格:52,794张验证码图片,全由
Kaptcha
生成,大小为 200 × 50 200 \\times 50 200×50。
验证码示例:
相对而言,Kaptcha验证码相对而言文本排布默认更加紧凑,但是文字间距再kaptcha中是一个可以调节的超参数。Kaptcha较难识别的主要原因在于其文本存在可能的扭曲形变,并且形变状态不定,因此模型需要能够克服该形变,方可较为准确的识别,因此Kaptcha识别较captcha困难,并且准确度指标会有所下降。
注:在直接使用模型时需要严格注意验证码规格,这主要在于图片过小会导致CNN过程异常。若对图片进行分辨率调整,长宽比不一,将导致严重形变,导致识别精度下降。
生成数据集
基于上述两个验证码框架,可以使用其提供的开源库进行验证码生成。
生成EasyCaptcha
如下代码所示,主要是从配置中获取验证码的配置,并使用给定的框架进行验证码生成,并最终输出到文件中。
public boolean generate()
String outputFolder = config.get(ConfigConstants.OUT_DIR);
int width = Integer.parseInt(config.get(ConfigConstants.WIDTH, "120"));
int height = Integer.parseInt(config.get(ConfigConstants.HEIGHT, "80"));
int len = Integer.parseInt(config.get(ConfigConstants.LENGTH));
SpecCaptcha captcha = new SpecCaptcha(width, height, len);
captcha.setCharType(Captcha.TYPE_DEFAULT);
try
captcha.setFont(Captcha.FONT_3);
catch (IOException | FontFormatException e1)
e1.printStackTrace();
return false;
String codes = captcha.text();
if (LOG.isInfoEnabled())
LOG.info("Generating " + codes + "...");
return ImageOutputUtil.writeToFile(captcha, outputFolder, codes);
为提升图片生成的效率,我们使用多线程的方式,同时生成:
public class CaptchaTaskRunner implements Runnable
private static final Logger LOG = Logger.getLogger(CaptchaTaskRunner.class);
private CaptchaGenerator generator;
@Override
public void run()
boolean success = generator.generate();
if (success)
if (LOG.isInfoEnabled())
LOG.info("Complete!");
else
if (LOG.isInfoEnabled())
LOG.info("Failed!");
/**
* @return CaptchaGenerator return the generator
*/
public CaptchaGenerator getGenerator()
return generator;
/**
* @param generator the generator to set
*/
public void setGenerator(CaptchaGenerator generator)
this.generator = generator;
代码中CaptchaGenerator
即为generate()
方法的接口类,在线程池中提交若干任务,最终都由CaptchaTaskRunner
实例进行生成。
生成Kcaptcha
相较于EasyCaptcha,Kcaptcha的配置项更多,因此其识别更加困难,为增强最终模型的可信度与拟合能力,可随机地产生若干配置,来生成验证码:
public KaptchaGeneratorWorker(me.zouzhipeng.config.Config config)
Properties prop = new Properties();
prop.put(Constants.KAPTCHA_BORDER, true);
prop.put(Constants.KAPTCHA_BORDER_COLOR,
String.join(",", rand.nextInt(256) + "", rand.nextInt(256) + "", rand.nextInt(256) + ""));
prop.put(Constants.KAPTCHA_IMAGE_WIDTH, config.get(ConfigConstants.WIDTH, "200"));
prop.put(Constants.KAPTCHA_IMAGE_HEIGHT, config.get(ConfigConstants.HEIGHT, "50"));
String textColor = config.get(ConfigConstants.TEXT_COLOR);
if (null == textColor)
textColor = String.join(",", rand.nextInt(256) + "", rand.nextInt(256) + "", rand.nextInt(256) + "");
prop.put(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR,
textColor);
prop.put(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, config.get(ConfigConstants.LENGTH, "4"));
prop.put(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "彩云,宋体,楷体,微软雅黑,Arial,SimHei,SimKai,SimSum");
if (Boolean.parseBoolean(config.get(ConfigConstants.NOISE_SAME_TEXT_COLOR, "true")))
prop.put(Constants.KAPTCHA_NOISE_COLOR, textColor);
else
prop.put(Constants.KAPTCHA_NOISE_COLOR,
String.join(",", rand.nextInt(256) + "", rand.nextInt(256) + "", rand.nextInt(256) + ""));
prop.put(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345679");
this.output = config.get(ConfigConstants.OUT_DIR);
Config kaptchaConfig = new Config(prop);
producer = kaptchaConfig.getProducerImpl();
如上述构造函数,针对Kaptcha进行了所需配置项的配置。
而生成部分,同EasyCaptcha相似,如下:
public boolean generate(String folder)
String text = producer.createText();
BufferedImage imageBuffered = producer.createImage(text);
if (LOG.isInfoEnabled())
LOG.info("Generating " + text + "...");
return ImageOutputUtil.writeToFile(imageBuffered, folder, text, "jpg");
同样地,采用多线程对图片进行生成,以得到大量验证码训练图片。
搭建模型
针对两个不同的数据集,本项目设计了两个不同的模型,但是总体上都是基于CNN和FCN结构的分类任务。在诸多OCR任务中,通常会使用multi-stage方法设计模型,即:通常使用对象识别模型,识别图片中的文字,并用框标记出文字所在位置,再利用CNN和FCN的结构对所识别的文字进行分类。并且,若为文档OCR识别,输出层还可能借助LSTM等RNN结构网络。
考虑到验证码通常位数有限,即4位、5位较为常见,因此该模型采用end2end multi-task方法也可满足需求,且模型复杂度并不高。
针对EasyCaptcha验证码,其产生的验证码较容易区分,字符分隔较开,且变形选项较少,因此使用很简单的模型即可达到较高的精度,在本项目的模型中,验证集准确度可达到 98 − 99 98-99% 98−99左右。
而对于Kaptcha验证码,其存在较多可选的配置项,并且会在验证码中间添加噪音扰动,因此识别较为困难,使用EasyCaptcha的模型,精度仅能达到70%左右,准确度较低,Kaptcha模型适当地加大了CNN网络的深度,并增加了一层全连接隐藏层,在验证集上达到93-94%的准确度。
在训练过程中,采用长度为4的验证码,其中验证码中可选字符为:a-zA-Z0-9,共62个可能字符。
下面为两个模型:EasyNet, KCapNet的详细介绍。
EasyNet模型
EasyNet模型由2层卷积层和4个输出层构成,该模型结构细节如下:
- 第一层卷积,卷积核大小为 5 × 5 5 \\times 5 5×5,步长为1,通道为16,参数量为 3 × 5 × 5 × 16 3 \\times 5 \\times 5 \\times 16 3×5×5×16,得到大小 76 × 116 76\\times 116 76×116的特征图共16个;
- 第二层为最大池化层,无参数,池化核大小为 5 × 5 5\\times 5 5×5,步长为5,得到特征图大小为 15 × 23 15 \\times 23 15×23;
- 第三层为批归一化层,避免过拟合并加速模型收敛。根据效果,同时还可尝试使用shortcut方法。归一化后,采用RReLu激活函数;
- 第四层为卷积层,卷积核大小为 5 × 5 5 \\times 5 5×5,步长为1,通道数为32,得到特征图大小为 11 × 19 11 \\times 19 11×19,参数量为 16 × 5 × 5 × 32 16 \\times 5 \\times 5 \\times 32 16×5×5×32;
- 第五层为最大池化层,无参数,池化核大小为 5 × 5 5 \\times 5 5×5,步长为5,得到特征图大小为 2 × 4 2 \\times 4 2×4,无参数;
- 第六层仍为批归一化层,并采用RReLu函数激活;
- 第七层为Dropout层,经过第二个卷积后,将得到特征图展开,得到特征向量维度为256维,对256的特征向量进行Dropout处理,避免过拟合,采用的失效概率为 p = 0.3 p=0.3 p=0.3;
- 第八层为输出层,用于得到验证码序列,由于模型为multi-task,因此输出层有4个(根据验证码中字符长度确定),使用softmax激活,参数量为 4 × 256 × 62 4\\times 256 \\times 62 4×256×62。
KCapNet模型
KCapNet共由3个卷积层,1个全连接层,4个输出层组成,以下为模型具体细节:
- 第一层为卷积层,卷积核大小为 5 × 5 5 \\times 5 5×5,步长为1,通道数为16,输入图片大小为 50 × 200 50 \\times 200 50×200,因此可得到16个大小为 46 × 196 46 \\times 196 46×196的特征图,参数量为 3 × 5 × 5 × 16 3\\times 5 \\times 5 \\times 16 3×5×5×16;
- 第二层为最大池化层,无参数,池化核大小为 3 × 3 3 \\times 3 3×3,步长为3,得到特征图大小为 15 × 64 15 \\times 64 15×64;
- 第三层为批归一化层,在归一化结束后,使用RReLu激活函数激活;
- 第四层为第二个卷积层,卷积核大小为
3
×
3
3 \\times 3
3×3,通道数为32,可得到大小为
13
×
62
13 \\times 62
13×使用深度学习模型识别12306图片验证码
深度学习中损失值(loss值)为nan(以tensorflow为例)