Python验证码识别 | 源码+通用识别模型

  1. 云栖社区>
  2. 博客>
  3. 正文

Python验证码识别 | 源码+通用识别模型

edelweiss 2020-06-02 13:29:33 浏览883

项目地址:https://github.com/kerlomz/captcha_trainer

编译版下载地址: https://github.com/kerlomz/captcha_trainer/releases/tag/v1.0

注意:若使用云服务器 (Windows Server版) 遇到闪退,请按照步骤:我的电脑——属性——管理——添加角色和功能——勾选桌面体验,点击安装,安装之后重启即可。

2020/06/01编外:

想必各位只是偶然间搜到这篇文章,网上文章参差不齐,标题党很多,能跑起来的开源代码很少,对于能跑起来的代码,也经常遇到以下问题如:内存泄漏,网络参数写死导致更换训练集报错,网络跑其他样本识别率低,没有调用示例等等。

再往下看之前,我可以向你们保证,它绝对会是你所见过的所有验证码有关的文章中最实用,最接近生产水平的。

  1. 对小白: 你可以不需要动手写任何一行代码。
  2. 对小企业: 它的可用性和稳定性是经得起考验的,在性能上也是同行领先的,可以放心入坑。

你们要的通用识别模型:

可能你们想要的是一行pip就搞定环境的,所以今天给你们安排了麻瓜OCR(MuggleOCR)。
https://pypi.org/project/muggle-ocr
它整合了简单验证码识别通用模型+印刷文字通用识别,并且支持调用本文框架训练的模型。调用只需要三行核心代码:

import time
# STEP 1
import muggle_ocr
import os
# STEP 2
sdk = muggle_ocr.SDK(model_type=muggle_ocr.ModelType.OCR)
root_dir = r"./imgs"
for i in os.listdir(root_dir):
    n = os.path.join(root_dir, i)
    with open(n, "rb") as f:
        b = f.read()
    st = time.time()
    # STEP 3
    text = sdk.predict(image_bytes=b)
    print(i, text, time.time() - st)

这真的很简单,应付一般的文字识别和验证码都足够了。(文字识别过几天会更新一下新模型,毕竟0601模型就跑了半天。

1. 前言

本项目适用于Python3.7,GPU>=NVIDIA GTX1050Ti,原master分支新增了GUI配置界面以及编译版本了,是时候写一篇新的文章了。

长话短说,开门见山,网络上现有的代码以教学研究为主,本项目是为实用主义者定制的,只要基本的环境安装常识,便可很好的训练出期望的模型,重定义几个简单的参数任何人都能使用深度学习技术训练一个商业化成品。

笔者选用的时下最为流行的CNN+BLSTM+CTC(CRNN)进行端到端的不定长验证码识别,代码中预留了CNNX(搜不到因为是小编自己拼凑的)/MobileNet/DenseNet121/ResNet50等选项,可以在配置界面中直接选用。首先,介绍个大概吧。

10905998_52b0df7590a8d304

网格结构 predict-CPU predict-GPU 模型大小
CNN5+Bi-LSTM+H64+CTC 15ms 8ms 2mb
CNN5+CrossEntropy 8ms 2ms 1.5mb

H16/H64指的是Bi-LSTM的隐藏神经元个数UnitsNum,所以本项目使用GPU训练,使用CPU进行预测。

预测服务部署项目源码请移步此处:https://github.com/kerlomz/captcha_platform
部署项目的编译版下载地址:https://github.com/kerlomz/captcha_platform/releases

2.环境依赖:

花了超长篇幅介绍了训练环境的基本搭建,主要是给尚未入门的读者看的,老鸟们随便跳过,若不希望在环境上面浪费时间的,欢迎使用编译版,可在文章开头找到下载地址。

关于CUDA和cuDNN版本的问题,不少人很纠结,这里就列出官方通过pip安装的TensorFlow的版本对应表:

Linux

Version Python version Compiler Build tools cuDNN CUDA
tensorflow_gpu-1.14.0 3.7 GCC 4.8 Bazel 0.15.0 7.6 9

Windows

Version Python version Compiler Build tools cuDNN CUDA
tensorflow_gpu-1.14.0 3.7 MSVC 2015 update 3 Bazel 0.15.0 7.6 10

如果希望使用上面对应之外的搭配的CUDA和cuDNN,可以自行编译TensorFlow,或者去Github上搜索TensorFlow Wheel找到第三方编译的对应版本的whl安装包。提前预警,若是自己编译将会苦难重重,坑很多,这里就不展开了。

2.1 本项目环境依赖

目前在以下主流操作系统平台均测试通过:

操作系统 最低支持版本
Ubuntu 16.04
Windows 7 SP1
MacOS N/A

本训练项目主要的环境依赖清单如下

依赖 最低支持版本
Python 3.7
TensorFlow-GPU 1.14.0
Opencv-Python 4.1.2.30
Numpy 1.16.0
Pillow 4.3.0
PyYaml 3.13
tqdm N/A

2.1.1 Ubuntu 16.04 下的 Python 3.7

1)先安装Python环境(有Python 3.7环境的可以忽略)

sudo apt-get install openssl  
sudo apt-get install libssl-dev
sudo apt-get install libc6-dev gcc  
sudo apt-get install -y make build-essential zlib1g-dev libbz2-dev libreadline-dev $ libsqlite3-dev wget curl llvm tk-dev 
wget https://www.python.org/ftp/python/3.7.6/Python-3.7.6.tgz
tar -vxf Python-3.7.6.tar.xz
cd Python-3.7.6
./configure --prefix=/usr/local  --enable-shared
make -j8
sudo make install -j8

经过上面指令就安装好Python3.7环境了,如果提示找不到libpython3.7m.so.1.0就到/usr/local/lib路径下将该文件复制一份到/usr/lib和/usr/lib64路径下。
2)安装相关依赖(这一步Windows和Linux通用)
可以直接在项目路径下执行pip3 install -r requirements.txt安装所有依赖,注意这一步是安装在全局Python环境下的,强烈建议使用虚拟环境进行项目间的环境隔离,如VirtualenvAnaconda等等。
我一般使用的是Virtualenv,有修改代码需要的,建议安装PyCharm作为Python IDE

virtualenv -p /usr/bin/python3 venv # venv is the name of the virtual environment.
cd venv/ # venv is the name of the virtual environment.
source bin/activate # to activate the current virtual environment.
cd captcha_trainer # captcha_trainer is the project path.
pip3 install -r requirements.txt

2.1.2 Ubuntu 16.04 下的 CUDA/cuDNN

网上看到过很多教程,我自己也部署过很多次,Ubuntu 16.04遇到的坑还是比较少的。14.04支持就没那么好,如果主板不支持关闭SecureBoot的话千万不要安装Desktop版,因为安装好之后一定会无限循环在登陆界面无法进入桌面。
网上教程说要加驱动黑名单什么的我直接跳过了,亲测没那个必要。就简单的几步:
1. 下载好安装包
注意下载runfile类型的安装包,deb安装会自动安装默认驱动,极有可能导致登陆循环
NVIDIA 驱动下载:https://www.geforce.cn/drivers
CUDA 下载地址:https://developer.nvidia.com/cuda-downloads
cuDNN 下载地址:https://developer.nvidia.com/cudnn (需要注册NVIDIA账号且登陆,下载deb安装包)

2. 关闭图形界面
Ctrl+alt+F1进入字符界面,关闭图形界面

sudo service lightdm stop

3. 安装Nvidia Driver

命令中的版本自己对应下载的版本改,在上面的下载地址根据自己的显卡型号下载最新版,切记是runfile格式的安装包。

sudo chmod a+x NVIDIA-Linux-x86_64-384.90.run //获取执行权限
sudo ./NVIDIA-Linux-x86_64-384.90.run –no-x-check –no-nouveau-check –no-opengl-files //安装驱动

安装成功以后使用以下命令验证,如果显示显卡信息则表示安装成功

nvidia-smi

4. 安装CUDA

1)先安装一些系统依赖库

sudo apt-get install freeglut3-dev build-essential libx11-dev libxmu-dev libxi-dev libgl1-mesa-glx libglu1-mesa libglu1-mesa-dev

2) 执行安装程序,按指示无脑继续就好了,如果提示是否安装驱动选不安装。

sudo sh cuda_9.0.176_384.81_linux.run

安装完如果环境变量没配上去,就写到 ~/.bashrc 文件的尾部

export PATH=/usr/local/cuda-9.0/bin${PATH:+:${PATH}}
export LD_LIBRARY_PATH=/usr/local/cuda-9.0/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}

然后在终端执行 sudo ldconfig更新,安装完毕就可以重启机器重启图形界面了。

sudo service lightdm start

2.1.3 Windows 系统

在Windows其实简单很多,只要到官网下载安装包无脑安装就可以了,下载连接同Ubuntu,先安装Python,显卡驱动,CUDA,然后下载对应的cuDNN替换到对应路径即可。

3 使用

在训练之前,有不少群友经常问我“训练4位数英文数字需要多少样本?”诸如此类的问题,我这里统一做个回复,样本数量主要是看样本的特征复杂度而定。

这里可以提供几个参考依据: 是否变形?是否旋转?是否有复杂背景干扰?是否多种字体?字符集(分类数)多大?位数(标签数)多少?

  1. 一般简单的几百个样本(需要自行调整 验证集大小验证批次大小 )即可。
  2. 稍微复杂的几千个样本一般都能搞定。
  3. 特别复杂的几万样本起。
  4. 中文这种几千个分类的一般十万起。

注:只准备一百个不到样本的亲们,千万不要尝试训练测试,因为根本跑不起来。

入手的第一步环境搭建好了,那就是准备跑代码了,还是有几个必要的条件,巧妇难为无米之炊,首先,既然是训练,要先有训练集,有一个新手尝鲜的训练集,是mnist手写识别的例子,可以在腾讯云下载:https://share.weiyun.com/5pzGF4V
,现在万事俱备,只欠东风。

3.1 定义一个模型

本项目基于参数化配置,不需要改动任何代码,可以通过可视化界面操作训练几乎任何字符型图片验证码。训练框架界面可以大致划分为几个部分:

  1. Neural Network - 神经网络区

    ![image](https://yqfile.alicdn.com/11991b433a38e6662663b7d7646d2a951c65f550.png)
    
  2. Project Configuration - 项目配置区

    ![1.png](https://upload-images.jianshu.io/upload_images/10905998-b2a7c8309c8190c3?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  3. Sample Source - 样本源配置区

    ![image](https://yqfile.alicdn.com/a3fbb29232110ba2a0ac626a25cb79c311497a6c.png)
    
  4. Training Configuration - 训练配置区

    ![image](https://yqfile.alicdn.com/45b11b40ef6956d01f4b8a9038b55fa5f803e262.png)
    
  5. Buttons - 功能控制区

    ![image](https://yqfile.alicdn.com/feb636d1f54f4bfec7534f1d4d18b3bf7cec8e39.png)
    

依此类推的训练配置的步骤如下:

  1. 神经网络区 的配置项看起来很多,对于新手来说,可以直接使用默认的配置:CNNX+GRU+CTC+C1组合(CNN前置网络+GRU+CTC+单通道)。
  2. 项目配置区 的配置项在网络选好之后配置项目名,按回车或者点击空白处确认。
  3. 样本源配置区 的配置项用来配置样本源的路径,训练样本是根据此路径进行打包成TFRecords格式,验证样本可以不指定,使用[Validation Set Num]参数随机从训练集总抽样成验证集。
  4. 训练配置区 的配置项负责定义训练完成的条件如:结束准确率,结束COST,结束Epochs,批次大小
  5. 功能控制区 的配置项,设置完上面步骤,先点击[Make Dataset] 打包样本,再点击[Start Training]开始训练。

以下部分有基础的读者们可以了解一下:

如若使用CrossEntropy作为解码器需要注意标签数LabelNum和图片尺寸需要满足的关系,因为网络为多标签而设计(一般的多标签采用直接连接多个分类器),卷积层的输出 outputs 经过了以下变换:

Reshape([label_num, int(outputs_shape[1] / label_num)])

为了保证运算 int(outputs_shape[1] / label_num) 能够取得正整数,也意味着他们之间存在某种关系,对于CNN5+Cross Entropy的网络结构,Conv2D层的步长皆为1,那么需要保证以下关系成立:

$$ mod(\frac{输入宽度\times输入高度\times输出层参数}{池化步长^{池化层数}\times标签数})= 0 $$

所以有时候需要Resize网络输入的Shape

网络 池化步长^池化层数 输出层参数
CNN5 16 64
CNNX 8 64
ResNet50 16 1024
DenseNet 32 2048

例如使用CNN5+CrossEntropy组合,则输入宽度与输入高度需要满足:

$$ mod(\frac{输入宽度\times输入高度\times64}{16\times标签数})= 0 $$

同理如果CNN5+RNN+CTC,卷积层之后的输出经过以下变换:

Reshape([-1, outputs_shape[2] * outputs_shape[3]])

原输出(batch_size, outputs_shape[1], outputs_shape[2], outputs_shape[3]),RNN层的输入输出要求为(batch, timesteps, num_classes),为了接入RNN经过以上操作,那么又引出一个Time Step的概念,所以timesteps的值也是 outputs_shape[1],而CTC Loss要求的输入为 [batch_size, frames, num_labels],若是 timesteps 小于标签数则无法计算损失,也就无法找损失函数中找到极小值,梯度何以下降。timesteps 最合理的值一般是标签数的2倍,为了达到目的,也可以通过Resize网络输入的Shape解决,一般情况timesteps直接关联于图片宽度,大多情况只要按比例放大宽度即可。

ExtractRegex 参数:

注意:如果训练集的命名格式和我提供的新手训练集不一样,请根据实际情况修改ExtractRegex的正则表达式。目前只支持在yaml配置文件中直接修改,尚未提供GUI界面修改的支持。 DatasetPath 和SourcePath参数允许多个路径,这种操作适用于需要将多种样本训练为一个模型,或者希望训练一套通用泛化模型的人。
字符集Category其实大多数情况下不需要修改,一般的图形验证码离不开数字和英文,而且一般来说是大小写不敏感的,不区分大小写,因为打码平台收集的训练集质量参差不齐,有些大写有些小写,不如全部统一为小写,默认ALPHANUMERIC_LOWER则会自动将大写的转为小写,字符集可定制化很灵活,除了配置备注上提供的几种类型,还可以训练中文,自定义字符集用list表示,示例如下:

Category: ['常', '世', '宁', '慢', '南', '制', '根', '难']

如果是单标签分类,可以配合LabelNum=1,例如:

Category: ["航母", "雨靴", "毛线", "安全帽", "调色板", "海鸥", "日历", "网球拍", ......]

其文件名示例:航母_1231290424123.png

如果是多标签分类,可以配合LabelSplit=&,例如:

Category: ["航母", "雨靴", "毛线", "安全帽", "调色板", "海鸥", "日历", "网球拍", ......]

其文件名示例:航母&雨靴&毛线_1231290424123.png

可以自己根据收集训练集的实际字符集使用率来定义,也可以无脑网上找3500常用字来训练,注意:中文字符集一般比数字英文大很多,刚开始收敛比较慢,需要更久的训练时间,也需要更多的样本量,请量力而行
image

形如上图的图片能轻松训练到95%以上的识别率。
ImageWidth、ImageHeight只要和当前图片尺寸匹配即可,其实这里的配置主要是为了方便后面的部署智能策略。

Pretreatment参数:

该参数是用来做图片预处理的,例如形如以下的GIF动图,
10905998_546e39a222586a56

可以使用ConcatFrames参数选取帧对两帧进行水平拼接,适用于处理滚动型GIF,而闪烁型GIF可以使用BlendFrames参数进行融合。

3.2 开始训练

  1. 经过 采集标注样本形如 xxx_随机数.png
    10905998_4424ab65cd2ea483
  1. 打包样本
    通过GUI界面的 [Make Dataset] 或者 make_dataset.py 直接打包。

注意:使用源码运行本项目的功能模块需要具备一定的语言基础,参数修改的部分和示例已预留好,尽量不修改核心类或函数的代码以免出现错误。

按照上面的介绍,配置只要修改极少数的参数对应的值,就可以开启正式的训练之旅了,具体操作如下:
可以直接使用 PyCharm 的 Run,执行 trains.py,也可以在激活Virtualenv下使用终端亦或在安装依赖的全局环境下执行,但本文建议全程使用GUI界面进行操作,使用GUI仅需启动 app.py 即可。

python3 trains.py

剩下的就是等了,看过程,等结果。
正常开始训练的模样应该是这样的:

训练结束会在项目的out路径下生成一个包含pb文件的graph目录和包含yaml文件的model目录,下面该到部署环节了。

3.3 部署

真的很有必要认真的介绍一下部署项目,比起训练,这个部署项目倾注了笔者更多的心血,为什么呢?
项目地址:https://github.com/kerlomz/captcha_platform

如希望将本系统集成于自己的项目中的可以参考python-sdk的使用:
https://pypi.org/project/muggle-ocr/
该项目的核心基于 captcha_platform/sdk/pb/sdk.py 可以根据需要自行修改,抑或直接使用MuggleOCR 调用训练框架生产的模型。(具体调用方法可点击上面链接有对应的文档介绍)

编译版:https://github.com/kerlomz/captcha_platform/releases,使用编译版无需安装Python和TensorFlow环境。

真的值得了解的几点

  1. 同时管理多个模型,支持模型热拔插
  2. 灵活的版本控制
  3. 支持批量识别
  4. 服务智能路由策略

首先笔者重写了TensorFlow的Graph会话管理,设计会话池,允许同时管理多模型,实现多模型动态部署方案。

1) 训练好的 pb模型只要放在部署项目的graph路径下,yaml模型配置文件放在model, 即可被服务发现并加载。(用SDK调用时,两者置于同一目录下)

2) 如果需要卸载一个正在服务的模型,只需要在model中删除该模型的yaml配置文件,在graph中删除对应的pb模型即可。

3) 如果需要更新一个已经服务中的模型,只需修改新版的模型yaml配置文件的版本号高于原模型的版本号,按先放pb后放yaml的顺序,服务便会自动发现新版的模型并加载使用,旧的模型将因版本低于新版模型不会被调用,可以按照上述的卸载方法卸载已被弃用的模型释放内存。
上面的操作中无需重启服务,完全的无缝切换

其次,一套服务想要服务于各式各样的图像识别需求,可以定义一套策略,训练时将所有尺寸一样的图片训练成一个模型,服务根据图片尺寸自动选择使用哪个模型,这样的设计使定制化和通用性共存,等积累到一定多样的训练集时可以将所有的训练集合到一起训练一个通用模型,亦可以彼此独立,每个模型的叠加仅仅增加了少量的内存或显存,网上的方案大多是不同的模型单独部署一套服务,每个进程加载了一整套TensorFlow框架势必是过于庞大和多余的。

用到批量识别需求的人相对少很多这里就不展开介绍了。但是这里给出一个12306的例子:

FieldParam:
  CorpParams: [
    {
      "start_pos": [118, 0],
      "interval_size": [0, 0],
      "corp_num": [1, 1],
      "corp_size": [60, 30]
    },
    {
      "start_pos": [5, 40],
      "interval_size": [5, 5],
      "corp_num": [4, 2],
      "corp_size": [66, 66]
    }
  ]
  OutputCoord: True

该参数可以用于大图的裁剪组成一批小图作为一个批次的输入,改用法可以避免多次调用。

但是识别项目提供了多套可选的服务有:gRPC,Flask,Tornado,Sanic,其中Flask和Tornado提供了加密接口,类似于微信公众号开发接口的SecretKey和AccessKey接口,感兴趣的可以在demo.py中阅读调用源码了解。

部署的使用可以经过package.py编译为可执行文件,这样可以免去更换机器环境安装的烦恼,部署项目安装流程同训练项目,项目中提供的requirements.txt已经将所需的依赖都列清楚了,强烈建议部署项目安装cpu版TensorFlow。

本项目部署推荐使用Tornado版,功能最齐全,性能最为稳定。

Linux:

  1. Tornado:
# 端口 19952
python3 tornado_server.py
  1. Flask
# 方案1,裸启动, 端口 19951
python flask_server.py 
# 方案2,使用gunicorn,端口 5000
pip install gunicorn 
gunicorn -c deploy.conf.py flask_server:app
  1. Sanic:
# 端口 19953
python3 sanic_server.py
  1. gRPC:
# 端口 50054
python3 grpc_server.py
  1. 编译版(基于Tornado)
# 前台运行
./captcha_platform_tornado
#后台运行
nohup ./captcha_platform_tornado &

Windows:
Windows平台下都是通过python3 xxx_server.py启动对应的服务,注意,Tornado、Flask、Sanic的性能在Windows平台都大打折扣,gRPC是Google开源的RPC服务,有较为优越的性能。
编译版直接运行编译后的exe可执行文件即可。

3.4 调用/测试

1. Tornado服务:

请求地址 Content-Type 参数形式 请求方法
http://[服务IP]:19952/captcha/v1 application/json JSON POST

具体参数:

参数名 必选 类型 说明
image Yes String Base64 编码
model_name No String 模型名,yaml配置中可绑定
need_color No String 颜色过滤,black/red/blue/yellow/green/white
output_split No String 多标签分割字符

请求为JSON格式,形如:{"image": "base64编码后的图像二进制流"}

返回结果:

参数名 类型 说明
message String 识别结果或错误消息
code String 状态码
success String 是否请求成功

该返回为JSON格式,形如:{"message": "xxxx", "code": 0, "success": true}

2. Flask服务:

请求地址 Content-Type 参数形式 请求方法
http://[服务IP]:19951/captcha/v1 application/json JSON POST

请求参数和返回格式同上

3. Sanic服务:

请求地址 Content-Type 参数形式 请求方法
http://[服务IP]:19953/captcha/v1 application/json JSON POST

请求参数和返回格式同上

4. gRPC服务:
需要安装依赖,grpcio、grpcio_tools和对应的grpc.proto文件,可以直接从项目中的示例代码demo.py中提取。

python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. ./grpc.proto

grpcio、grpcio_tools 是根据 grpc.proto 使用上述命令生成的。

class GoogleRPC(object):

    def __init__(self, host: str):
        self._url = '{}:50054'.format(host)
        self.true_count = 0
        self.total_count = 0

    def request(self, image, model_type=None, model_site=None):

        import grpc
        import grpc_pb2
        import grpc_pb2_grpc
        channel = grpc.insecure_channel(self._url)
        stub = grpc_pb2_grpc.PredictStub(channel)
        response = stub.predict(grpc_pb2.PredictRequest(
            image=image, split_char=',', model_type=model_type, model_site=model_site
        ))
        return {"message": response.result, "code": response.code, "success": response.success}

if __name__ == '__main__':
    result = GoogleRPC().request("base64编码后的图片二进制流")
    print(result)

3.5 奇技淫巧

该项目还可以直接用于识别带颜色的验证码,部署项目middleware/impl/color_extractor.py基于k-means实现了颜色分离模块,可用于处理如下形式的验证码:
10905998_6cf1497fcdfe7755

还有一种方案是同时预测验证码和每个字符对应的颜色,不过这需要修改现有的神经网络进行支持,在最后一层修改为双输出,一个输出颜色,一个输出对应字符,这对于样本标注的要求较高,也提高的成本,所以如果能用无限生成样本,那问题就迎刃而解了。
其实还有很多很多技巧,例如,用生成的样本代替训练集,其实网上的图片验证码大多是采用开源的,稍作修改而已,大多数情况都能被近似生成出来,上述展示的验证码图片不代表任何实际的网站,如有雷同,纯属巧合,该项目只能用于学习和交流用途,不得用于非法用途。

后记

如果文章描述不够详尽或需要技术支持的,可以加群 857149419 咨询,或在开源项目中提issue,很荣幸能为开源社区贡献绵薄之力。