vlambda博客
学习文章列表

厉害啦,智能验证码识别技术


大家好,我是章鱼猫,今天的这篇文章来自于读者的一篇投稿。这个开源项目说实话,真心不错,这篇文章作者也是写的情真意切。我这次就不改动了,原稿如下,请大家认真欣赏吧!

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

2.1


   

2020/06/01 编外:

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

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

  1. 对小白:你可以不需要动手写任何一行代码。

  2. 对小企业:它的可用性和稳定性是经得起考验的,在性能上也是同行领先的,可以放心入坑。

因为小编打算转行了,离开这个行业之前总要留下点什么证明自己来过,总有人和我说的这个部署不会调用,可能你们想要的是一行 pip 就搞定环境的,所以今天给你们安排了麻瓜 OCR(MuggleOCR)。https://pypi.org/project/muggle-ocr/1.0/ 它整合了简单验证码识别通用模型+印刷文字通用识别,并且支持调用本文框架训练的模型。调用只需要三行核心代码:

import time# STEP 1import muggle_ocrimport os# STEP 2sdk = 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


   

1. 前言

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

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

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

厉害啦,智能验证码识别技术

厉害啦,智能验证码识别技术

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

预测服务部署项目源码请移步此处:https://github.com/kerlomz/captcha_platform

2


   

2.环境依赖:

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

Linux

厉害啦,智能验证码识别技术

Windows

厉害啦,智能验证码识别技术

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

2.2


   

2.1 本项目环境依赖

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

厉害啦,智能验证码识别技术

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

厉害啦,智能验证码识别技术

2.3


   

2.1.1 Ubuntu 16.04 下的 Python 3.7

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

sudo apt-get install openssl sudo apt-get install libssl-devsudo 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.tgztar -vxf Python-3.7.6.tar.xzcd Python-3.7.6./configure --prefix=/usr/local --enable-sharedmake -j8sudo 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 环境下的,强烈建议使用虚拟环境进行项目间的环境隔离,如 Virtualenv 或 Anaconda 等等。我一般使用的是 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.4


   

2.1.2 Ubuntu 16.04 下的 CUDA/cuDNN

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

sudo service lightdm stop
  1. 安装 Nvidia Driver

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


  1. 安装 CUDA

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

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


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

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


3.1


   

2.1.3 Windows 系统

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

3


   

3 使用

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

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

  1. 一般简单的几百个样本(需要自行调整 验证集大小和 验证批次大小 )即可。

  2. 稍微复杂的几千个样本一般都能搞定。

  3. 特别复杂的几万样本起。

  4. 中文这种几千个分类的一般十万起。

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

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

3.2


   

3.1 定义一个模型

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

  1. Neural Network - 神经网络区

厉害啦,智能验证码识别技术

  1. Project Configuration - 项目配置区

厉害啦,智能验证码识别技术

  1. Sample Source - 样本源配置区

厉害啦,智能验证码识别技术

  1. Training Configuration - 训练配置区

厉害啦,智能验证码识别技术

  1. Buttons - 功能控制区

厉害啦,智能验证码识别技术

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

3.3


   

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

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

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

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

厉害啦,智能验证码识别技术mod

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

厉害啦,智能验证码识别技术

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

厉害啦,智能验证码识别技术mod

同理如果 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 直接关联于图片宽度,大多情况只要按比例放大宽度即可。

3.4


   

ExtractRegex 参数:

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

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

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

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

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

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

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

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

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

厉害啦,智能验证码识别技术

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

3.5


   

Pretreatment 参数:

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

厉害啦,智能验证码识别技术

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

3.6


   

3.2 开始训练

  1. 经过 采集标注样本形如 xxx_ 随机数.png

厉害啦,智能验证码识别技术

  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.7


   

3.3 部署

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

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

3.8


   

真的值得了解的几点

  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

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

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

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

Linux:1. Tornado

# 端口 19952python3 tornado_server.py
  1. Flask

# 方案1,裸启动, 端口 19951python flask_server.py # 方案2,使用gunicorn,端口 5000pip install gunicorn gunicorn -c deploy.conf.py flask_server:app
  1. Sanic:

# 端口 19953python3 sanic_server.py
  1. gRPC:

# 端口 50054python3 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.9


   

3.4 调用/测试

  1. Tornado 服务:

厉害啦,智能验证码识别技术

具体参数:

厉害啦,智能验证码识别技术

返回结果:

厉害啦,智能验证码识别技术

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

  1. Flask 服务:

厉害啦,智能验证码识别技术

请求参数和返回格式同上

  1. Sanic 服务:

厉害啦,智能验证码识别技术

请求参数和返回格式同上

  1. 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.10


   

3.5 奇技淫巧

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

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


   

后记

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

---特别推荐---