经常爬虫的同学,验证码识别应该是经常会碰到的事情。现在网上也有很多验证码识别的工具,但不一定适应全部网站,而且识别率往往不会太高。这周兴起,突然想试试通过机器学习训练后识别验证码的正确率能去到多少,好吧,那就拿自家的生产系统练练手。
从截图可以看到,cBSS系统的验证码并不算复杂,没有倾斜,也不会重叠,就是图片上的字母位置比较靠近边缘或底部,以及背景会有一些颜色线条干扰。那么,就开始本次实验。
从网上看了些教程,对于验证码识别,一般标准流程如下图所示(图片来自于http://www.jianshu.com/p/41127bf90ca9):
Step 1.图像采集
既然要使用到机器学习,那么足够的训练集样本就必不可少了。最方便的办法就是写个小爬虫程序,把系统上的验证码爬一些下来。以下是我写的简单的爬虫程序(作为一个Python新手,代码可能写得很渣):
爬虫程序很简单,主要就是找到验证码的src,程序里的base_url即是验证码的src(后面的“random=”加上随机数就能生成验证码图片)。爬的不多,就只爬下1000张。
Step 2.图像处理
图像处理,是为了方便后续图像识别时的特征提取及训练,如果不嫌麻烦,可以先对这1000个验证码进行切割后手工打标,但我比较懒,后续想借助光学字符识别技术( Optical Character Recognition (OCR))进行预处理,ocr在单文字识别上的效果正确率还是可以的,所以想先进行一系列处理以提高OCR识别。一开始,没仔细研究图片特点,我直接对照片进行了中值滤波、增强对比度、灰度化处理,以下是代码(后面因为效果不好,注释掉了)。
之后,由于每张验证码有4个字母,且间距基本为等距,所以我进行了等距切割,代码如下:
结果处理后的效果图如下,我一脸懵逼;人眼识别都有点困难,机器会更加懵逼吧。。。
于是我又重新审视了验证码的特征,其实原图验证码很端正,且边缘清晰,只是背景有一些干扰线(颜色大体都比较浅),以及验证码上下边框的横线(也列入干扰线)。上网看了一些图像处理方法,发现给图像降噪,也许效果会更好。思路是这样的,每张图片的颜色都是RGB三种颜色构成,对于每个像素点,RGB每种颜色的深浅范围都是[0,256],数字越小,颜色越深。由于图片上的干扰线颜色大体都比字母要浅,这也意味着它的RGB值要比字母小,通过分析RGB值应该可以滤掉大部分的噪声,代码如下:
处理+切割后的验证码图片:
是不是效果比之前的好了很多?其实在进行降噪后切割前,我尝试使用tesseract-OCR工具进行识别,效果也不算差,正确率应该有60%~70%。但咱并不满足于这个正确率,于是继续往下撸。
Step 3.图片预分类
至此,我已将1000个验证码通过降噪处理及切割后,生成4000个字母样本集。此时,OCR可以上场了。对于Python,有个pytessearct库可以使用到OCR功能,对分割后的图片进行预分类。第一次进行分类时,发现各种类别都有,有归类成数字的(比如有的字母Z被识别成2),有归类成小数点“.”的(很伤心,大部分很明显的字母O全被归类为点),有无法识别的,这就尴尬了,难道还是得手工去归类?于是又各种Google。后来看到有个方法或许能有作用,就是修改Tesseract-OCR文件里的digits文件,路径:C:\Program Files (x86)\Tesseract-OCR\tessdata\configs\digits,简单说就是将digits文件里的白名单限制为字母,然后在代码里加入digits参数,那么OCR则会优先将图片归类为字母,而不会出现数字和字符等情况。
再次进行归类,这次预分类效果好了很多,遗留的问题就是将大写的i(“I”)归类为“L”,“g”和“q”混淆,‘t'和'f’混淆,还有各种奇葩混淆('n'和'r'什么鬼的),但总体上识别错误的占比不高,稍微花了点时间手工纠错,预分类基本就这样妥了。
代码如下:
Step 4.特征提取
这一步是为后续的机器学习模型准备训练集,基本上,目前所有机器学习的模型都是基于向量操作,所以我们的特征提取实际上是将每张图片转化为向量,思路是将图像的每个像素点进行灰度化+二值化,在这个实验上,我是将像素值低于或等于128的设置为0,高于128的设置为1,之后将各个图像对应的字母放入向量最后一维,并存储到train_data.txt。
结果:
Step 5 机器学习-SVM
在这个案例里,我采用的SVM(支持向量积)模型。其实对于机器学习,我也是近期才接触,理解的也不深,SVM其实是一种分类方法,在这个算法中,我们将每个图像(即向量)在N维空间中用点标出,N是该向量的所有特征总数,在此案例里,我们的图像有300个像素点,即N=300;举个例子,如果我们只有身高和头发长度两个特征,我们会在二维空间中标出这两个变量,每个点有两个坐标(这些坐标叫做支持向量)。
现在,我们会找到将两组不同数据分开的一条直线。两个分组中距离最近的两个点到这条线的距离同时最优化。下图中,中间的线是两组中距离最近的点(即A、B点)到达黑线的距离满足最优条件。这条直线就是分割线,测试数据落在直线的哪边,就把它归为哪一类。
在这个案例里,我们是通过scikit-learn来实现。SVM支持多个核函数,例如高斯核、线性核、poly以及sgmoid核函数,在这里,我们通过scikit-learn的GridSearchCV来对参数进行最优化选择,对于这个案例里,线性核效果较好,所以我使用线性核训练。
另外,案例里使用了joblib模块进行持久化,主要是方便后续预测时不需要再次进行训练。最后,则使用了5折交叉验证来对结果进行检验。代码如下:
使用5折交叉验证来对结果进行检验,最终准确率达到100%左右!
在最后,为了实际验证机器学习效果,我再次爬了100张验证码放在文件夹E:\lect\pics1\里并进行预测,结果预测正确率确实就是在100%左右。
Yeah,finished!
总结:作为一个新手,大概也只能挑战这种难度的验证码了。今晚在登录微信公众平台的时候,看到这样的验证码,吓shi了,人眼识别都填错了几次,有空再研究看看是否也能做到机器识别。