从零开始搭建深度学习验证码识别模型

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=0fhj=0fwfilterh,wslice(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

验证码示例:

EasyCaptch项目主页

EasyCaptcha验证码特点在于可以构造Gif动态验证码,而其他验证码则显得相对简单,主要在于该验证码间隔较开,易于区分,因此识别较为简单。根据对上例中的验证码分析可知,验证码由不定位置的1-2个圆圈与曲线构成噪音,对文本加以干扰,文字颜色可变。从布局来看,文字的布局位置相对固定,且间隔也相对固定,这无疑也简化了识别过程。

数据集下载:Dataset-Google Drive for Kaptcha

数据规格:52,794张验证码图片,全由Kaptcha生成,大小为 200 × 50 200 \\times 50 200×50

验证码示例:

Kaptcha项目主页

相对而言,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% 9899左右。

而对于Kaptcha验证码,其存在较多可选的配置项,并且会在验证码中间添加噪音扰动,因此识别较为困难,使用EasyCaptcha的模型,精度仅能达到70%左右,准确度较低,Kaptcha模型适当地加大了CNN网络的深度,并增加了一层全连接隐藏层,在验证集上达到93-94%的准确度。

在训练过程中,采用长度为4的验证码,其中验证码中可选字符为:a-zA-Z0-9,共62个可能字符。

下面为两个模型:EasyNet, KCapNet的详细介绍。

EasyNet模型

EasyNet模型由2层卷积层和4个输出层构成,该模型结构细节如下:

  1. 第一层卷积,卷积核大小为 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个;
  2. 第二层为最大池化层,无参数,池化核大小为 5 × 5 5\\times 5 5×5,步长为5,得到特征图大小为 15 × 23 15 \\times 23 15×23
  3. 第三层为批归一化层,避免过拟合并加速模型收敛。根据效果,同时还可尝试使用shortcut方法。归一化后,采用RReLu激活函数;
  4. 第四层为卷积层,卷积核大小为 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 5 \\times 5 5×5,步长为5,得到特征图大小为 2 × 4 2 \\times 4 2×4,无参数;
  6. 第六层仍为批归一化层,并采用RReLu函数激活;
  7. 第七层为Dropout层,经过第二个卷积后,将得到特征图展开,得到特征向量维度为256维,对256的特征向量进行Dropout处理,避免过拟合,采用的失效概率为 p = 0.3 p=0.3 p=0.3
  8. 第八层为输出层,用于得到验证码序列,由于模型为multi-task,因此输出层有4个(根据验证码中字符长度确定),使用softmax激活,参数量为 4 × 256 × 62 4\\times 256 \\times 62 4×256×62

KCapNet模型

KCapNet共由3个卷积层,1个全连接层,4个输出层组成,以下为模型具体细节:

  1. 第一层为卷积层,卷积核大小为 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
  2. 第二层为最大池化层,无参数,池化核大小为 3 × 3 3 \\times 3 3×3,步长为3,得到特征图大小为 15 × 64 15 \\times 64 15×64
  3. 第三层为批归一化层,在归一化结束后,使用RReLu激活函数激活;
  4. 第四层为第二个卷积层,卷积核大小为 3 × 3 3 \\times 3 3×3,通道数为32,可得到大小为 13 × 62 13 \\times 62 13×使用深度学习模型识别12306图片验证码

    图像学习-验证码识别

    深度学习中损失值(loss值)为nan(以tensorflow为例)

    项目实战解析:基于深度学习搭建卷积神经网络模型算法,实现图像识别分类

    RNN入门识别验证码

    论文笔记旋转图像验证码角度识别-深度学习