因朋友需求,对某网站的验证码图片进行自动识别,原以为是个复杂的问题,后来查看了网上的一些资料,总体思路上参考了:
http://www.cr173.com/html/16871_1.html
尝试用Delphi做了Demo,过程如下
1、获取到验证码图片生成的URL,如http://www.aaa.bbb.cn/ValidateCode.aspx;
2、使用TIdHTTP控件通过URL获取图片,由于URL获取验证码图片是以数据流形式传递过来的,所以处理非常方便,主要代码如下:
识别主要分为三个步骤:第一步进行图片处理,最好形成二值化,第二步需要进行学习并保存特征码,第三步完成正常的识别功能。
3、图片处理
图片处理过程包括:
(1)将png图片转换为bmp图片,在Delphi下非常简单:
bmp1.Assign(Image1.Picture.Graphic);
(2)进行图片二值化
对于本次识别的图片,非常简单,如下图:
进行色彩分析,将相同颜色的像素进行计数后发现只有三种颜色:背景色、干扰色和字体蓝色。做二值化处理时就比较简单了,逐个处理像素点,将干扰色处理成背景色:
for w := 0 to ABmpDesc.Width -1 do for h := 0 to ABmpDesc.Height -1 do begin c := ABmpDesc.Canvas.Pixels[w, h]; { 不是字体颜色的都处理成背景色 } if c <> AFontColor then ABmpDesc.Canvas.Pixels[w, h] := ABkColor; end;
效果如下:
(3)进行图片分割
其实花时间最多的就是这步了,开始时考虑字符之间存在间隙,所以算法上以2个字符间的间隙为标准划分,结果发现存在多个字符连接在一起的情况,如下图:
2个TT实际是连在一起的,中间没有分割间隙,所以无法区分。后来又考虑采用等宽字符方式进行切割(网上常用算法),但是碰到了验证码图片中的字符是不等宽的,还是不行。后采用了字符轨迹法,由于都是英文字符和数字,单个字符或数字的笔画都是连在一起的,就把所有连在一起的笔画内容识别为同一个字符。采用递归算法,取左上第一个字符的第一个像素,然后对该像素周围8个像素进行扫描,记录相邻的像素,并对符合字体颜色的像素再次递归运算,直到没有符合字体颜色的像素为止。效果还是可以,但是仍然没有解决2个字符连在一起的情况,最后跳出这个思维圈子,干脆将连在一起的2个或多个字符共同识别为一个整体,即TT就当是一个字符处理,问题基本得到解决。
(4)进行分割后图片特征码提取
特征码非常简单,采用从上往下,从左向右依次获取每个像素,如果是字体颜色像素则为1,否则为0。当然也可以采用其他算法的特征码,只要能确定唯一性即可。然后将特征码与字符关联,并保存起来。
function GetbmpFlag(Abmp: TBitmap; AFontColor : TColor): String; var w, h: Integer; begin Result := ''; { 获取图片特征码 } for h := 0 to Abmp.Height -1 do for w := 0 to Abmp.Width -1 do if Abmp.Canvas.Pixels[w, h] = AFontColor then Result := Result + '0' else Result := Result + '1'; end;
4、学习
反复获取验证码图片,进行上述处理,并填写人工识别后的验证码,以便软件将字符对应的图片特征码与字符关联起来,我的学习界面如下:
将学习好的特征码保存到文件,以便在下次识别时载入。
5、正常识别
特征码学习差不多后保存在磁盘文件上,当正式识别时,载入该特征码,按照上述步骤进行处理:
(1)获取验证码图片
(2)二值化处理
(3)图片分割
(4)获取图片特征码
(5)根据图片特征码,在已学习的特征码中进行查找,找到后返回其对应的字符,将所有分割后图片的特征码对应的字符组合起来就是验证码。
上述工作真正的难点在于2个:
1、二值化图片
本次试验的图片比较简单,如果遇到非常复杂的图片,如:
且字体颜色还是随机的,就比较麻烦了,必须对所有像素进行颜色统计,取其最多的4个颜色,我的统计图如下:
颜色最多的4个即为字体颜色。但是也有特殊情况,如下图:
其统计图就比较杂乱了,如下图:
字体颜色统计被背景颜色掩盖了,按照上文提示,应采用HSL对色彩进行变化,然后进行统计,这项工作待有时间再进行。
2、图片切割
本例中的图片切割还是比较简单的,对于复杂的、不规则的图片,其切割可能更复杂。尤其是遇到多个字符上下部分位置有交叉,字符有随机大小,字符与字符之间有粘连时,这种图片切割更加复杂,这里不再讨论,今后有时间再试试。