文章目录
从零开始搭建深度学习验证码识别模型
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