关于

这部分说明每种验证码中的预处理过程,当时用到的思路和方法

验证码类别对应的序号采用这张图
20180105_1

第0种

20180105_2

一开始会认为这种验证码比较复杂,但是实际写起来发现还蛮好写

去除背景

首先尝试变成黑白,然后观察颜色的分布(挑选五张图片举例):

20180105_3

基本能看出数字在的位置颜色比较深,背景颜色比较浅。也就是说这种形式的验证码变成黑白图像后,数字在的那个位置值比较小。所以只要能找到数字和背景的分界线就比较好办了。多尝试几次能找到值在98左右,按照这个值切割大多数图像都能分割的不错。

由于后面的有一个验证码背景黑色数字白色的成功率高一点,所以就统一在去掉背景的时候将背景变成了黑色

分离背景之后的结果:

20180105_4

切割图片

由于这种验证码数字之间隔得很开,所以切割图片很方便,这边先介绍我最早用的一种方式

首先看所有白色像素点的横坐标,去掉重复值之后的集合记录为x。x中间中断的部分表示没有数字,找到每个中断开始和结束的位置就能提取出这张图片中每个数字横轴的起点和终点:

20180105_5

切割之后将短边扩充成跟长边相同的正方形,然后resize成28 * 28就能喂给神经网络训练模型了

获取标记的数据

原本这种验证码切割好之后就卡住了,直到发现了一个有趣的事情。在单一入口的页面点击刷新验证码其实是有两个请求:一个302重定向以及一个获取图片的请求

多次点击刷新验证码的请求过程:

20180105_6

并且发现后面这个获取图片的请求URL是不同的,每个URL对应一种验证码:

20180105_7

于是我把验证码拖到一个新的窗口打开,发现了另外一个有趣的事情:在新窗口中点击浏览器的刷新只会改变验证码的样式,并不会改变数字的内容。,更关键的是这种验证码数字本身没有什么变化,只有旋转的角度不同。

所以根据这点我就想到可以让我的代码不停的刷第二个请求过去,只要保证在同一个session数字就没有变化。虽然会像上传的训练集那样有很多重复的数字(0336,0472等),但是只要保证切割之后0到9这十个数字的数量比较均匀就行了。

知道这个规律之后就比较容易获取有标记的图片了:

count = 0
while count < 2500:
    if 现在刷新出来的是第一种验证码 and 还需要验证码中的数字作为训练集:
        NUM = 验证码中的数字 # 人工识别
        N = 需要下载这张验证码的次数
        for _ in range(N):
            使用这个连接发送请求
            将请求结果保存为图片文件名为(NUM_GUID.jpg)
        count += N

第1种

20180105_8

这种验证码的特点是背景完全相同,最先想到的思路是:弄到完全相同但是上面没有数字的背景图片,计算验证码和纯背景图片的差值,不等于0的地方就是数字

但是用PS做出来的图片还是有很大的区别,虽然看上去一样,但是真正做数值计算还是有点明显,所以后来还是做一步看一步。

去除背景

先黑白处理:

20180105_9

能看出来数字对应的颜色是最深的,但是不同图片数字对应的颜色相差很大(下图一次代表0059,1953,3225,6989的颜色分布)。

20180105_10

所以就不能像第一种验证码那样设定一个值

通过上面这个图可以观察到数字对应的颜色不光是最深的,也是占比最大的,所以可以通过这两个特点来得到验证码中数字对应的颜色值

拿到颜色值之后就可以通过这个值将背景和数字区分开了:

20180105_11

切割图片

这种类型的验证码有的时候隔着比较近,用前面那种验证码的方式裁剪效果不是很好。刚好OpenCV提供了检测边缘的方法,就直接拿来用了(不确定pillow有没有提供,感觉是有,但是用的不熟练所以每深究)

由于OpenCV画的框框是检测边缘,这种验证码线条比较粗,可能检测到内边缘和外边缘,所以在代码中要特地考虑这种情况

还需要考虑的一个是数字之间相连的情况,这时候要看切开的数字宽和高,一般来说数字的高是大于宽的,所以如果出现宽大于高的情况就判断是两个数字连在一起了,直接从中间对半切开

其实也有可能是三个数字连在一起了,但是测试的结果来看,不用特地考虑三个数字相连的结果已经不错了,就没有继续做下去

获取标记的数据

还是观察之前提到过的刷新验证码的两次请求,第二次URL才是真正获取图片的,并且这个地址不同

20180105_12

这种验证码对应的地址是: https://portal.nctu.edu.tw/captcha/claviska-simple-php-captcha/simple-php-captcha.php?_CAPTCHA&t=0.07573800+1515110377

看到这个URL很自然的就会去github上搜一下claviska或者是simple-php-captcha

很容易就能找到生成这种验证码的代码:https://github.com/yasirmturk/simple-php-captcha

通过代码可以看到这种验证码的逻辑是选取一个背景,然后从characters(原始代码中是”ABCDEFGHJKLMNPRSTUVWXYZabcdefghjkmnprstuvwxyz23456789”,单一入口改成了数字)中选取几个,用这个字体(times_new_yorker.ttf)渲染之后输出到背景上。

单一入口用这个源码改了背景,以及添加了随机变换颜色的功能,字体大小和数字长度也有点调整。所以我就照着调整了一下,然后本地部署一个Apache服务器,打开这个项目的Demo页面就能看到验证码,并且这个页面还有对应的标签。

由于背景还是没有找到完全相同的,自己做的效果也不太好,所以我生成的图片背景都是纯白色,也就是说训练阶段和实际使用阶段这种验证码的预处理逻辑有点不同,在代码中我用一个变量is_fake_img来区分(值为True表示训练阶段,False表示实际使用阶段,图片来源于单一入口网站)

20180105_13

所以就用代码不停的访问这个页面下载图片就能获取到很多的训练集了,访问自己本地电脑也能避免给单一入口的电脑带来额外的负载。

第2种

20180105_14

这个验证码比较麻烦的是干扰线,并且有些干扰线还比较粗,一开始的思路是图片黑白处理后往上下左右四个方向偏移一段距离,然后跟没有偏移的图片取且运算。这种方式确实能去掉干扰线,但是对很多数字的影响也很大,所以后来放弃了这种思路。直接忽略干扰线,拿残缺的数字喂给模型,发现效果还不错。

去除背景

这种类型的验证码背景有三种颜色,并且深浅有点不确定,直接转黑白的话容易分辨不出数字,所以直接统计每个颜色在水平方向的投影。

背景和干扰线一般来说都完全填满了横轴,图片中的每一列都有这两种颜色,但是数字并不会全部都有,所以我设置的限制是85%。

if len(颜色在水平方向投影长度) / len(图片宽) > 0.8:
    该颜色是背景或者干扰线的颜色
else:
    该颜色是数字对应的颜色

为了防止有其他颜色影响,这里只判断出现的最多的三种颜色

通过上面步骤就能分割出数字和其他内容,但是这种情况下的数字是残缺的(之前干扰线遮盖的部分被忽略了):

20180105_15

切割图片

由于数字之间距离较小,没有办法使用第一种切割方式,并且由于分割线被去掉导致单个数字都不是连续的,所以第二种切割方式也不行。

原本在这个地方卡住了,但是有天突然想到了下雨的时候水流过石头的情景。把验证码中的数字看作是四颗石头,只要想办法让雨水流过去,轨迹就可以把每个小石头分开。

所以这种验证码我的分割思路是从上面每隔一段距离往下落一个小球,小球往下滚动的轨迹能记录下来,原本是想用轨迹分割,后来发现用底端的位置分割效果也还不错,所以就没有继续优化了。小球滚落的示意图如下:

20180105_16

为了方便查看,加上原始的图片:

20180105_17

然后滚落到下面的时候,统计底部的小球轨迹,距离太近的忽略,左右两边没有的需要补上边界(比如第三幅图中的3,左侧小球没有滚下去,但是左边应该是有一个边界的,右边情况也是类似)

当然这个算法还是有问题,比如第四幅图中的3,由于3底下之前有个干扰线,去除之后导致小球能从3的下沿流出,导致这张图会分割失败。

获取标记的数据

与第1种类似,这个验证码也是来自于一个开源项目,地址是:https://github.com/dapphp/securimage

大致上也是修改开源项目中的代码,在本地做一个Demo页面,不停的刷新获取验证码和标签。

由于背景和干扰线的颜色不太重要,所以在demo页面中我都随便选了一个颜色(这也是为什么我的压缩包里面这种类型验证码很难看)

后面的分割的效果还好,只是分割的成功率比之前两种验证码要低(自己生成的图片分割成功率是:3320 / 4042 = 0.82,真实数据的样本太少)。

第3种验证码

没有处理

正确率评估

对于一张图片的正确率采用以下公式:

\[\text{正确率} = \text{验证码切割正确率} * \text{单个数字识别正确率} ^ 4\]

第0种和第1种比较简单,单个数字识别正确率基本上到了100%,切割正确率没有统计,第2种两个正确率都偏低,没有真实数据的统计,在现有数据上统计结果是 $0.82 * 0.97^4 = 72.5\%$

这种统计方式是很不严谨的,但是感觉可以作为参考,有点类似于没有测试集的情况下只看验证集的结果

相关文章地址

代码链接

参考

  1. dapphp/securimage: PHP CAPTCHA Script
  2. yasirmturk/simple-php-captcha: A simple PHP CAPTCHA script.
  3. Pillow — Pillow (PIL Fork) 5.0.0 documentation
  4. 仅需15分钟,使用OpenCV+Keras轻松破解验证码 机器之心