项目源码:https://github.com/nladuo/cap… (可以帮我点个star(^__^))
开发语言:python(编写爬虫),c++(编写图像处理部分以及机器学习算法)
开发环境:ubuntu 14.04
依赖库:
Python:PIL、BeautifulSoup4、requests
C++:boost、opencv2、tesseract-ocr
环境搭建
安装opencv 2.4.12
|
安装tesseract-ocr
|
安装boost
|
安装python的PIL库
|
安装python的BeautifulSoup4、requests库
|
下载项目源码
|
本节的代码可以通过此处进行下载。
图像直方图
首先看验证码的样子,
可以看到这几个验证码最大的特点就是字母比较深,而周围的背景很浅,把图像转换成黑白颜色,可以显示的更加的明显。
|
可以看到所有背景的灰度值,都会小于字母的灰度值,为了让tesseract更准确的识别,可以把图像进行二值化,也就是把背景都改成纯白色,把字母都搞成纯黑色。那么如何确定分割的阈值呢?比较好的方法是画出图像的直方图。
|
直方图的横坐标代表灰度值,纵坐标代表指定灰度值的点数。
图像阈值
接下来,开始对图像进行阈值,这里选择的灰度值位150,灰度下于150的设置为白色,灰度大于150的设置为黑色。
|
Tesseract识别
把图片进行阈值后,就可以进行识别了,直接调用tesseract的API就好了。
|
运行程序,可以看到识别出了结果:
前言
在上一节中,我们使用了google的开源OCR库来对字符进行识别,这一节以及下一节我们将要使用机器学习算法来识别验证码。本节的代码都在https://github.com/nladuo/captcha-break/tree/master/csdn可以找到。
下载验证码
在这一节中,将要对CSDN下载的验证码进行破解,就是在http://download.csdn.net/下载东西的时候,短时间内下载次数过多弹出来的验证码。
做机器学习的第一个步骤就是采集数据,构建训练样本。首先,来看一下CSDN下载中出现的验证码。
在每次刷新的时候,会有以上这两种验证码出现。在本节中,为了方便学习K近邻算法(简称为:KNN),选择第二种来进行破解,因为第二种的字母分割十分容易,每个字母的位置都是固定的。
由于两种验证码的图片大小不一样,所以可以使用图片大小来判断哪个是第一种验证码,哪个是第二种验证码,这里使用python进行验证码下载。
|
分割字符
下载过后,就需要对字母进行分割。机器学习虽然牛逼,但是也需要对样本进行预处理,这里的预处理就是把字母分割出来,并且分割成同样的尺寸。分割的方式可以使用代码分割,当然也可以通过人用PS等工具进行手动分割。
我这里使用代码分割,字母分割的代码在spliter文件夹下,我使用了boost库来来读取所有下载的验证码,对图片进行二值化后,进行定点分割,可以看到分割好的字母如下。
之后,需要人工对字母进行分类,分类好的图片见recognizer/dataset,我这里每个字母需要6个样本,10个字母,总共60个样本。
算法原理
K近邻算法的定义十分简单,在百度百科上有这样的解释:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。
也就是说,需要找到要识别的字母在训练样本中K个最近的字母,然后找出这K个字母中最多的是某个类的?要识别的图片也就是该类的。
实现KNN
计算距离
首先,先定义一下距离如何计算,这里可以用各种数学上的距离,欧式距离、马氏距离等等。。
由于我们的图片已经进行了二值化,为了简便起见,这里把两张图片的距离定义为:两张图片灰度不同的像素点个数。也就是逐个比较图片的相对位置上的灰度值,如果不相同,距离就加一。
|
加载数据
数据的加载需要一个图片数组和一个标签数组,来记录图片数组相应位置的类别。
加载样本数据:
|
加载样本数据标签:
|
算法实现
加载完数据后,就可以开始实现KNN分类了。
1、计算输入图片和所有其他图片的距离
|
2、对距离进行排序
|
3、获取K个距离最近的图片的类别
|
4、利用map记录所有类别中出现k_nearest的次数
|
5、得到出现最多的类别
|
识别验证码
最后,我们把验证码的4个字母分割出来,再进行K近邻分类,就可以得到识别结果了。
|
效果
识别图片:
识别结果:
练习
通过以上,我们破解了CSDN下载的第二种验证码,第一种验证码的识别过程也是可以使用KNN的,但是第一种和第二种的分割字母的方式不同,读者可以尝试使用opencv的findCountours函数对字母进行分割,或者使用垂直投影的方式进行分割,需要注意的是第一种验证码有一个黑色的边框,如果不处理会影响findCountours函数的效果。
前言
在这节,我将用卷积神经网络(简称:CNN)破解新浪微博手机端的验证码(http://login.weibo.cn/login/),验证码如下。
本节的代码可以在https://github.com/nladuo/captcha-break/tree/master/weibo.cn找到。
关于神经网络的原理很难在一节讲清楚。在这里,只需要把神经网络当成一个黑匣子,输入是一个图片,输出一个label,也就是类别。
LeNet5
本节使用的神经网络是国外学者Yann LeCun的LeNet5,该神经网络以32×32的图片作为输入,对于字符的变形、旋转、干扰线等扭曲都可以很好的识别,可以实现以下效果。
更多的效果可以在http://yann.lecun.com/exdb/lenet/上查看,具体原理可以查看Yann LeCun的论文。
字符下载
字符下载和上节差不多,这里需要注意的是新浪微博的验证码下载下来是gif格式的,opencv不支持读取gif的读取,需要用PIL把验证码转换成png格式。
另外,新浪微博的验证码明显比CSDN下载的验证码要复杂得多,所以需要大量的样本,至少要下载上千个验证码。
字符分割
新浪微博的验证码需要进行去除椒盐噪声、去除干扰线、二值化后,才能很好的进行垂直投影分割,我算法写的不是很好,就不在这里展开了,代码可以在spliter中找到。LeNet5的输入是32×32像素,所以为了不对神经网络进行大量修改,也需要将每个字母都方法32*32的模板中,分割后如下:
分割好之后,需要开始大量的人工操作了,经过了几个小时的努力,成功完成了5000多样本的分类,结果放在了trainer/training_set中。
这里每个文件夹都是一个分类,共有14个分类(除了ERROR),点进文件夹后可以看到每个文件夹内都有300多张图片。
训练
构建网络
我这里使用的神经网络库是tiny-cnn(现在已改名叫tiny-dnn)。
训练相关的代码都在trainer/main.cpp中,首先看一下神经网络的构造函数。
|
这里可以看到有六层神经网络,C1、S2、C3、S4、C5、F6。其实不用仔细的了解神经网络的构造,只需要把它想象成一个黑匣子,黑匣子的输入就是C1层的输入(C1, 1@32×32-in),黑匣子的输出就是F6层(F6,14-out)。32×32对应着图片的大小,14对应着类的个数。比如说要训练MINST数据集(一个手写字符的数据集)的话,需要把fully_connected_layer<tan_h>(120, 14)改成fully_connected_layer<tan_h>(120, 10),因为MINST中有十类字符(0-9十种数字)。
(注:这里只能修改F6层的参数而不能修改C1层的参数,修改C1参数会影响到其他层的输入。)
加载数据集
接下来,通过boost库加载数据集,其中五分之四的样本作为训练,还有五分之一的作为测试训练的正确性。
|
参数设置
卷积神经网络使用的是随机梯度下降进行训练,涉及一些数学知识,这里就不展开了。
这里只要把它理解为:神经网络会自己不断的对数据集进行学习(不断的迭代,每次迭代都会对识别率有所改进)。学习的过程会有一个学习速率optimizer.alpha,这里选择的是默认的;还有每次学习多少个数据(minibatch_size),这里设置每次对100个数据进行学习;还有一个学习的时间(num_epochs),这里学习了50次之后,学习效果就没有了。也就是识别率达到了峰值。
|
保存结果
神经网络的训练之后,需要保存神经网络的权重,把权重输出到”weibo.cn-nn-weights”中。
|
运行程序
运行trainer后,可以看到开始加载数据,并且进行一次一次的迭代,每一次迭代都会根据测试数据来进行验证,显示正确识别的字符数目。
从上面可以看到,一共有3934个训练样本和972个测试样本,正确识别的字符数目随着迭代次数不断的增加,从72->120->142->223….,识别率不断增加。
训练到最后(第四十几次迭代),可以看到数据已经差不多饱和了,维持在860、870左右,也就是单个字符有89%的识别率,单个验证码有0.89^4=0.64左右的识别率。(如果训练了很多次后,发现识别率还没有饱和,可以增大迭代次数num_epochs或者增大学习速率optimizer.alpha)
识别
最后,可以通过训练好的“weibo.cn-nn-weights”来进行识别,把trainer/weibo.cn-nn-weights放到recognizer文件夹下。
接下来看看神经网络是如何进行识别的,在recognizer/main.cpp中查看recognize函数。
|
在神经网络的最后一层中输出的是一个14维的向量,分别对应着每个类的概率,所以通过sort函数,找出概率最大的类就是识别结果了。
测试图片:
测试识别结果: