这第三篇,要识别的Qunar的验证码,先看样图。
图1 样图
看到上面这张图,最头疼的就是那两条一横一竖两条干扰线了,前前后后想了很久,最后有了想法。
1. 干扰线识别
对于这两条干扰线,关键就是怎么看待这两条线了,用什么样的办法进行去进行建模,用来"逼近"这两条线。在观察了很多样本之后,得出下面的结论:干扰线本身可以看作是一个连续函数的图像,可以这么做的理由是一条干扰线是一个整体,并且大多是手工造成或者由随机变量生成,这样做的结果也使得我们可以使用连续函数的图像对其进行拟合,再根据连续函数的性质,
由此对于干扰线而言,可以将一条干扰线看作若干条宽度为1的竖线(或者宽度为1的横线)组合起来的,并且相邻的竖线的高度(或宽度)应该是差别不大的,在下面的叙述里,把这种宽度为1的线称为干扰线的子线。
接着,可以用下面的步骤去进行搜索,找到这两条干扰线:
1) 按照干扰线的趋势方向进行搜索,根据干扰线的特点,其在左端或者右端,肯定会具有一个“突出”的现象,也就是明显和字符不在一个矩形范围内,这是人眼感觉的启发,如果干扰线和字符块重叠在一起,那就无所谓干扰线了,在这种“突出”的位置上,从上至下进行搜索,连续的黑点组成的就是干扰线的一部分;
2) 设置两个变量h和dir,初始化为0和0,分别标记干扰线从上一个位置到当前位置高度变化的大小和伸展方向的变化,这里的伸展方向指的是向上或者向下偏移多少;
3) 沿着干扰线伸展的趋势方向进行搜索,例如当前子线的高度是h,而干扰线的起始子线的高度是pos,伸展方向是dir,那么下一条子线应该从pos+dir位置开始搜索,搜索在该垂直(或水平)位置上连续的黑点数目接近[H-h,H+h]的作为该位置干扰线的子线,并重置h和dir;
4) 此处重置dir的方法,假设上一次子线的起始位置为s0,终止位置为e0,本次找到的子线起始位置为s1,终止位置为e1,则分别找出两条子线的中点,在求其差值,即
5) 因为干扰线的部分子线和原字符块重合,所以如果全部去掉,将会损失过多的字符图像内容,所以如果子线起始位置和终止位置的两边都是背景色,则去掉有很大概率不会损失字符图像内容,可以安全去掉,否则需要保留。
图2是根据一定的阈值二值化之后的样图,
图2 二值化后的图片
图3是根据上面的叙述执行干扰线寻找算法后的图片,画为绿色的是可以安全去掉的,黄色的是需要谨慎处理的,也就是有可能会是字符的组成部分,在后的执行中,绿色部分去掉,黄色保留。
图3 标出干扰线的图片
2. 模板制作
下面就是做点样本,手工标注后,和图片进行匹配,找到每块图像最相似的字符。
这里使用特征点的思想制作模板,因为每个图样,必须具有一定的字符区域和空白区域,才能被识别为图片,所以每个样本同样也具有两种类型的特征点,一类点标识字符区域,另一类标识空白区域,为了量化,给两类点不同的数值作为分数进行统计,针对不同图片的特点,给予两类点不同的数值,例如,如果当前图片空白区域很少,那么就给空白区域的特征点较大的数值。
图4是用程序作出的模板图片,用程序找到一些特征点,绿色表示字符区域的特征点,红色表示空白区域的特征点。
注:这个模板图片当然需要手工切出来,这种样图,用程序去实现切图不太现实。
图4 模板图片
为了加快程序匹配的速度,将模板数值化,用不同的数字分别表示两类特征点,以矩阵形式保存在文本里。下面的矩阵就是图4。
0000000101000000
0000000000000000
0000000001010100
0000001000100000
0010000000000010
0001000000200000
0100020020001000
0000200200000000
0002000000000002
0000000002010100
0200002000000000
0000020000010000
0202002001000002
0000020000000000
0000000001000000
0002000100002000
0200001001000000
0002000010000000
0000000100000100
0001000000000000
1010010100010000
0000000001001010
0010001000100000
0100100000000000
0010001010000000
3. 字符匹配
经过前面的处理,可以得到去掉干扰线的待匹配图片和模板的特征点矩阵,读入待匹配图片,去掉四周过多的空白区域,然后从左往右、从上往下的顺序进行搜索,因为是使用特征点进行匹配,为了保证准确度,设置一个阈值,表示当前搜索的块和模板有多大比例是匹配的,例如90%,在这阈值之上的就认为是找到了,否则就继续搜索,最后返回的结果是在该阈值之上的所有备选结果中得分最高的,如果没有找到任何一个大于该阈值的模板,则继续向右向下搜索。
如果匹配到的字符不等于5个,也就是这里图片中的字符长度,则认为匹配失败,直接返回。
代码可以参考:GitHub