图像验证码识别(五)——去除噪点

降噪在计算机和信号学中用到的非常多,在多媒体技术中,降噪主要是去除图片上的干扰噪点,玩过单反的都知道,当ISO调太高的时候,图片会产生颗粒感,这些颗粒就是噪点。当然,验证码图片上的噪点有大有小,只要是用来干扰机器识别的,会对后面的图片处理以及识别造成干扰。

常见的降噪算法一般都是一些滤波算法——均值滤波、中值滤波、自适应维纳滤波器和小波滤波等,不过在这里由于大多数验证码字符本身不是标准的打印体,而且字符相对较小,直线感很强,所以滤波算法降噪的效果不是很好。比如有些字符本身就比较模糊,在笔画弯折的地方很浅,使用滤波算法之后很容易将连贯的字符分成两个部分,另外如果验证码图片很小,有可能滤波之后整个验证码字符都被视为噪点了。

所以这里我自己实现了两种算法,都比较简单,但是经过试验,去除验证码噪点的效果很好,基本上能够应付所有的验证码噪点。

一、8邻域降噪

先介绍第一种方法,这种方法类似均值滤波,不过对于每个pixel,不是取其周围像素的灰度平均值,而是统计其周围像素点的灰度值为0或255的个数。从前面经过二值化处理可知,如果一个pixel是验证码或者干扰因素的一部分,那么这个pixel在二值化结果中其灰度值一定是0,即黑色;如果一个pixel是背景,则其灰度值应该是255是白色。因此对于孤立的噪点,其周围应该都是白色,或者大多数点都是白色pixel,比如下面的图片:


所以对一个噪点来讲,其周围的pixel应该全是白色的背景才对,准确来讲就是一个噪点pixel是黑色的并且外包的8个相邻pixel全是白色。当然,如果图片分辨率够高,一个噪点实际上可能是有很多个pixel组成,所以此时的判断条件应该放宽,即一个pixel是黑色的并且相邻的8个pixel白色的大于一个固定值,那么这个pixel就是噪点。对于不同的验证码,这个阀值是不固定的,所以在这可以设置大小,多试几次,找到最佳的阀值。

经过测试,8领域降噪法对于小的噪点的去除是很有效的,而且计算量不大,下面是一些结果图


左边的时原图,右边的是降噪以后的图,当然降噪没有降干净,这是因为这个方法对小噪点比较好,如果阀值设的比较大,很多验证码字符也会受到很大影响,因为验证码可能就是一些断断续续的点连出来的,阀值设太大,尽管噪点没了,验证码也会没了。

代码如下,所有关于像素的处理,都是基于opencv库:

[cpp]  view plain  copy
  1. void Image::NaiveRemoveNoise(int pNum)  
  2. {  
  3.     //naive remove noise  
  4.     int i,j,m,n,nValue,nCount;  
  5.     int nWidth = getWidth();  
  6.     int nHeight = getHeight();  
  7.     //set boundry to be white  
  8.     for (i = 0; i < nWidth ; ++i)  
  9.     {  
  10.         setPixel(i,0,WHITE);  
  11.         setPixel(i,nHeight-1,WHITE);  
  12.     }  
  13.     for (i = 0; i < nHeight ; ++i)  
  14.     {  
  15.         setPixel(0,i,WHITE);  
  16.         setPixel(nWidth-1,i,WHITE);  
  17.     }  
  18.     //if the neighbor of a point is white but it is black, delete it  
  19.     for (j = 1; j < nHeight; ++j)  
  20.         for (i = 1; i < nWidth; ++i)  
  21.         {  
  22.             nValue = getPixel(i,j);  
  23.             if ( !nValue )  
  24.             {  
  25.                 nCount = 0;  
  26.                 for (m = i-1; m <= i+1; ++m)  
  27.                     for (n = j-1; n <= j+1; ++n)  
  28.                     {  
  29.                         if( !getPixel(m,n) )  
  30.                             nCount++;  
  31.                     }  
  32.                 if (nCount <= pNum)  
  33.                     setPixel(i,j,WHITE);  
  34.             }  
  35.             else  
  36.             {  
  37.                 nCount = 0;  
  38.                 for (m = i-1; m <= i+1; ++m)  
  39.                     for (n = j-1; n <= j+1; ++n)  
  40.                     {  
  41.                         if( !getPixel(m,n) )  
  42.                             nCount++;  
  43.                     }  
  44.                 if (nCount >= 7)  
  45.                     setPixel(i,j,BLACK);  
  46.             }  
  47.         }  
  48. }  

二、连通域降噪

对于较大的噪点,还有一个思路就是求其面积,因为字符pixel大部分都是相互连通的,因此求出每一个相互连通的黑色点的个数,如果个数很多那么就说明这一片pixel很有可能是字符的部分,如果一个连通域的像素个数很少,那么基本可以确定这一片pixel就是噪点。

对于求连通域的面积,opencv是有API可以直接利用的,那就是cvStartFindContours,这里不再过多介绍,其主要思路就是先求出连通域的轮廓,然后用指定的形状拟合,然后求每个连通域的面积。

为了精确性,我这里没有用上面那个API,而是用了另外一个方法——泛水填充法,其API如下:

[cpp]  view plain  copy
  1. floodFill(Mat,cvPoint(i,j),cvScalar(color));  
其中Mat就是图片对于的矩阵对象,cvPoint(i,j)就是图片种位置为(i,j)的一个点,cvScalar就是颜色对象,这个函数的意思就是将与坐标为 cvPoint(i,j)连通的所有的点的颜色都改为cvScalar(color),整个过程就像一张纸第一滴水,水泛染的样子,因故得名。

在计算的过程中,每扫描到一个黑色(灰度值为0)的点,就将与该点连通的所有点的灰度值都改为1,因此这一个连通域的点都不会再次重复计算了。下一个灰度值为0的点所有连通点的颜色都改为2,这样依次递加,知道所有的点都扫描完。接下来再次扫描所有的点,统计每一个灰度值对应的点的个数,每一个灰度值的点的个数对应该连通域的大小,并且不同连通域由于灰度值不同,因此每个点只计算一次,不会重复。这样一来就统计到了每个连通域的大小,再根据预设的阀值,如果该连通域大小小于阀值,则其就为噪点。这个算法比较适合检查大的噪点,与上个算法正好相反。

上面采用8邻域降噪得到的验证码还是保留不少较大的噪点,这里对上面处理过的验证码图片再次使用连通域降噪算法,对其进行2次降噪,得到的结果如下图:


可以看到,此时所有的噪点已经全部去除掉,效果很好。下面给出代码:

[cpp]  view plain  copy
  1. void Image::ContoursRemoveNoise(double pArea)  
  2. {  
  3.     int i,j;  
  4.     int color = 1;  
  5.     int nHeight = getHeight();  
  6.     int nWidth = getWidth();  
  7.   
  8.     for (i = 0; i < nWidth; ++i)  
  9.         for (j = 0; j < nHeight; ++j)  
  10.         {  
  11.             if ( !getPixel(i,j) )  
  12.             {  
  13.                 //FloodFill each point in connect area using different color  
  14.                 floodFill(m_Mat,cvPoint(i,j),cvScalar(color));  
  15.                 color++;  
  16.             }  
  17.         }  
  18.   
  19.     int ColorCount[255] = { 0 };  
  20.     for (i = 0; i < nWidth; ++i)  
  21.     {  
  22.         for (j = 0; j < nHeight; ++j)  
  23.         {  
  24.             //caculate the area of each area  
  25.             if (getPixel(i,j) != 255)  
  26.             {  
  27.                 ColorCount[getPixel(i,j)]++;  
  28.             }  
  29.         }  
  30.     }  
  31.     //get rid of noise point  
  32.     for (i = 0; i < nWidth; ++i)  
  33.     {  
  34.         for (j = 0; j < nHeight; ++j)  
  35.         {  
  36.             if (ColorCount[getPixel(i,j)] <= pArea)  
  37.             {  
  38.                 setPixel(i,j,WHITE);  
  39.             }  
  40.         }  
  41.     }  
  42.     for (i = 0; i < nWidth; ++i)  
  43.     {  
  44.         for (j = 0; j < nHeight; ++j)  
  45.         {  
  46.             if (getPixel(i,j) < WHITE)  
  47.             {  
  48.                 setPixel(i,j,BLACK);  
  49.             }  
  50.         }  
  51.     }  
  52. }  

三、总结

上面提到的两个降噪算法——8邻域降噪和连通域降噪,分别适合去除小的噪点和大的噪点,因此我在实际应用中,两个算法组合来使用的,对于一个二值化的验证码图片,先采用8邻域算法降噪,然后再使用连通域算法降噪,这样无论大的还是小的噪点都能够去除掉,经过测试,效果很好

你可能感兴趣的:(验证码识别)