前面经过各种去除噪点、干扰线,验证码图片现在已经只有两个部分,如果pixel为白就是背景,如果pixel为黑就为字符。正如前面流畅所提到的一样,为了字符的识别,这里需要将图片上的字符一个一个“扣”下来,得到单个的字符,接下来再进行OCR识别。
字符分割可以说是图像验证码识别最关键的一步,因为分割的正确与否直接关系到最后的结果,如果4个字符分割成了3个,即便后面的识别算法识别率达到100%,结果也是错的。当然,前面预处理如果做得够好,干扰因素能够有效的去除,而没有影响到字符的pixel,那么分割来讲要容易得多。反过来,如果前面的干扰因素都没有去除掉,那么分割出来的可能就不是字符了。
字符的粘连是分割的难点,这一点也可以作为验证码安全系数的标准,如果验证码上的几个字符完全是分开的,那么可以保证字符分割成功率百分之百,这样验证码破解的难度就降低了很多,比如下面的字符:
这个就是CSDN的验证码,经过二值化和降噪得到的图片,可以看到这里图片已经非常干净,没有一点多余的信息,字符之间没有重叠的部分,分割起来毫无难度。
当然,大多数IT巨头的网页验证码里地字符都是粘连在一起的,比如谷歌的验证码:
谷歌的验证码不仅粘连成都很大,而且字符扭曲地也特别厉害,所以破解起来那是难度非常大了
至于图片分割,我再这里介绍两种简单地方法。
一、 泛水填充法
泛水填充法在前面降噪的地方就提到过,主要思路还是连通域的思想。对于相互之间没有粘连的字符验证码,直接对图片进行扫描,遇到一个黑的pixel就对其进行泛水填充,所有与其连通的字符都被标记出来,因此一个独立的字符就能够找到了。这个方法优点是效率高,时间复杂度是O(N),N为像素的个数;而且不用考虑图片的大小、相邻字符间隔以及字符在图片中得位置等其他任何因素,任何验证码图片只要字符相互是独立的,不需要对其他任何阀值做预处理,直接就操作;用这种方法分割正确率非常高,几乎不会出现分割错误的情况。但是缺点也很致命:那就是字符之间必须完全隔离,没有粘连的部分,否则会将两个字符误认为一个字符。
代码如下:
- for (i = 0; i < nWidth; ++i)
- for (j = 0; j < nHeight; ++j)
- {
- if ( !getPixel(i,j) )
- {
- //FloodFill each point in connect area using different color
- floodFill(m_Mat,cvPoint(i,j),cvScalar(color));
- color++;
- }
- }
- int ColorCount[256] = { 0 };
- for (i = 0; i < nWidth; ++i)
- {
- for (j = 0; j < nHeight; ++j)
- {
- //caculate the area of each area
- if (getPixel(i,j) != 255)
- {
- ColorCount[getPixel(i,j)]++;
- }
- }
- }
- //get rid of noise point
- for (i = 0; i < nWidth; ++i)
- {
- for (j = 0; j < nHeight; ++j)
- {
- if (ColorCount[getPixel(i,j)] <= nMin_area)
- {
- setPixel(i,j,WHITE);