最近发现武大的app们都越来越geek了,都有越来越多的黑科技加入。比如谢神的各种插件全都支持验证码的自动识别了。而验证码识别背后的机器学习和模式识别一直都是我想接触一下的方向,这次正好借助这个契机来做一下了解。期间我也问了下谢神,但是貌似也没有通用的解决策略。于是我觉得我有必要自己来做一下验证码识别(国庆在家pythoning 我是不是够宅 - -)
要想识别一个验证码其实你需要做这样几件事:
降噪 : 图片可能有干扰线点,我们需要把这些都去掉,以免妨碍我们的干扰
二值化: 图像是由像素组成的,每个pix对应了自己的RGBA, 但是想要让我们的机器识别他们,我们就需要把像素信息转化成0101011这些。
切分,旋转:如果我们的验证码中字母存在倾斜,那么我们就需要把字母们分开,每一个旋转回自己的角度,方便后续识别。
识别:最后当然是把前期处理过的验证码用我们的算法识别。
我们首先来分析一下我们的验证码:
可以看到,这个验证码的混淆是:雪花状噪点+字母倾斜
我的方案,是基于rgb值对雪花和图片进行分离
def removenoise(img): pixdata = pixpic(img) for y in xrange(img.size[1]): for x in xrange(img.size[0]): if pixdata[x, y][1] > 100: #print pixdata[x, y] pixdata[x, y] = WHITE if pixdata[x, y][0] < 110: #print pixdata[x, y] pixdata[x, y] = WHITE return pixdata
执行了 pixdata = pixpic(img)
pixdata以元组的形式存储了每个像素的rgb信息
pixdata[x, y][1] < 100 是字母的红色的rgb范围,我们把这个范围以外的全部变白。
接下来是二值化这个很简单,二维数组,value=1表示红色区域,value=0表示白色
def binarypic(img): pixunion = [] pixdata = pixpic(img) for i in range(img.size[0]): pixunion.append([]) for j in range(img.size[1]): pixunion[i].append(0) for y in xrange(img.size[1]): for x in xrange(img.size[0]): if pixdata[x, y][1] < 100 and pixdata[x, y][3]>0: pixunion[x][y] = 1 return pixunion
最后我们切块,为了后续旋转字母到正确的位置。
def boundpic(img): scanlight = 0 pixnum = 0 wordpix = 0 silderesult = [] pixunion = binarypic(img) for x in xrange(img.size[0]): for y in xrange(img.size[1]): pixnum += pixunion[x][y] if pixnum>1 and scanlight == 0: scanlight = 1 start = x if pixnum
现在成了这样
然后就是旋转了,到底该旋转多少角度呢?
这里我们采用旋转卡壳算法
此算法非常简单,对一张图片左右各旋转30度的范围,每次1度,旋转后用扫描线法判断字符的宽度,对于标准的长方形字体,在完全垂直的时候肯定是宽度最窄的。嗯?纳尼?上面的图是中间的最窄?好像的确是这样,不过只要每次旋转后的结果都一样,对于识别率不会有影响。
这里我还是用射线投影法识别字母边缘
def boundrotatepic(img): scanlight = 0 pixnum = 0 prepixnum = 0 boundresult = [] pixunion = binarypic(img) for x in xrange(img.size[0]): for y in xrange(img.size[1]): pixnum += pixunion[x][y] if ((pixnum>0 and prepixnum >0) or (x == 0 and pixnum > 1)) and scanlight == 0 : scanlight = 1 start = x-1 if pixnum==0 and prepixnum == 0 and scanlight == 1: scanlight = 0 end = x-2 boundresult = (start,end) if x == img.size[0]-1 and scanlight == 1: end = x boundresult = (start,end) prepixnum = pixnum pixnum = 0 return boundresult
最后的图片是这样的
好了最后我们直接调用现成的Pytesser 强大的python库进行识别
可以看到整个识别过程的核心我依然调用了现有的库,而不是设计算法或者是算法实现,我希望在将来可以再次维护这个功能并且改进。
最近我发现我也变得二次元了,热衷于一个美国动漫叫《南方公园》
难道我要变身二次元死宅了?
有点期待呢。。。