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

整体框架来自ysc_ysc大神的图像验证码识别系列
http://blog.csdn.net/ysc6688/article/details/50772382

改写了一部分自己不明白的代码,加了少量注释。对于阀值的选择更加清晰,着重体现算法的优势。

一、8邻域降噪

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

原图:
原图

二值化效果图:
二值化效果图

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

阀值为4阀值为5阀值为6阀值为7

如图是阀值从4到7。

结果一目了然,阀值为5时最优选择。
当然降噪没有降干净,这是因为这个方法对小噪点比较好,如果阀值设的比较大,很多验证码字符也会受到很大影响,因为验证码可能就是一些断断续续的点连出来的,阀值设太大,尽管噪点没了,验证码也会没了。
代码如下:

#include "stdafx.h"
#include<opencv2\core\core.hpp>
#include<opencv2\highgui\highgui.hpp>
#include<opencv2\imgproc\imgproc.hpp>

using namespace cv;

Mat srcImage;

void NaiveRemoveNoise(double pNum);

int main()
{
    srcImage = imread("E:\\picture\\mask1.jpg", 0);
    imshow("srcImage", srcImage);

    adaptiveThreshold(srcImage, srcImage, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 31, 7);
    imshow("tempImage", srcImage);

    NaiveRemoveNoise(5);   
    imshow("dstImage", srcImage);

    waitKey();

    return 0;
}

void NaiveRemoveNoise(double pNum) {

    int m, n, nValue, nCount;
    int nCols = srcImage.cols;
    int nRows = srcImage.rows;

    //set boundry to be white  
    for (int i = 0; i < nRows; ++i) {
        srcImage.at<uchar>(i, 0) = 255;
        srcImage.at<uchar>(i, nCols - 1) = 255;
    }
    for (int j = 0; j < nCols; ++j) {
        srcImage.at<uchar>(0, nCols) = 255;
        srcImage.at<uchar>(nRows - 1, nCols) = 255;
    }

    //if the neighbor of a point is white but it is black, delete it  
    for (int i = 1; i < nRows; ++i)
        for (int j = 1; j < nCols; ++j)
        {
            nValue = srcImage.at<uchar>(i, j);
            if (nValue == 0)      //fine a black point
            {
                nCount = 0;
                for (m = i - 1; m <= i + 1; ++m)
                    for (n = j - 1; n <= j + 1; ++n)
                    {
                        if (srcImage.at<uchar>(m, n) == 255)
                            nCount++;
                    }
                if (nCount >= pNum)
                    srcImage.at<uchar>(i, j) = 255;
            }
        }
}

二、连通域降噪

对于较大的噪点,还有一个思路就是求其面积,因为字符pixel大部分都是相互连通的,因此求出每一个相互连通的黑色点的个数,如果个数很多那么就说明这一片pixel很有可能是字符的部分,如果一个连通域的像素个数很少,那么基本可以确定这一片pixel就是噪点。
对于求连通域的面积,opencv是有API可以直接利用的,那就是cvStartFindContours,这里不再过多介绍,其主要思路就是先求出连通域的轮廓,然后用指定的形状拟合,然后求每个连通域的面积。
为了精确性,我这里没有用上面那个API,而是用了另外一个方法——泛水填充法,其API如下:

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次降噪,得到的结果如下图:
这里写图片描述
可以看到,此时所有的噪点已经全部去除掉,效果很好。下面给出代码:

#include "stdafx.h"
#include<opencv2\core\core.hpp>
#include<opencv2\highgui\highgui.hpp>
#include<opencv2\imgproc\imgproc.hpp>

using namespace cv;

void ContoursRemoveNoise(double pArea);

Mat srcImage;

int main()
{
    srcImage = imread("E:\\picture\\IDCode\\5.jpg", 0);
    imshow("srcImage", srcImage);
    imwrite("E:\\picture\\IDCode\\8.jpg", srcImage);

    adaptiveThreshold(srcImage, srcImage, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 31, 7);
    imshow("tempImage", srcImage);
    imwrite("E:\\picture\\IDCode\\9.jpg", srcImage);

    ContoursRemoveNoise(20);
    imshow("dstImage", srcImage);
    imwrite("E:\\picture\\IDCode\\10.jpg", srcImage);

    waitKey();

    return 0;
}

void ContoursRemoveNoise(double pArea)
{
    int i, j;
    int color = 1;
    int nRows = srcImage.rows;
    int nCols = srcImage.cols;

    for (i = 0; i < nRows; ++i)
        for (j = 0; j < nCols; ++j){
            if (!srcImage.at<uchar>(i,j)){
                //FloodFill each point in connect area using different color  
                floodFill(srcImage, Point(j, i), Scalar(color));   //注意  point是(_x,_y)形式 所以注意反写行列
                color++;
            }
        }

    int ColorCount[255] = { 0 };

    for (i = 0; i < nRows; ++i){
        for (j = 0; j < nCols; ++j){
            //caculate the area of each area  
            if (srcImage.at<uchar>(i, j) != 255)
                ColorCount[srcImage.at<uchar>(i, j)]++;
        }
    }

    //get rid of noise point  
    for (i = 0; i < nRows; ++i){
        for (j = 0; j < nCols; ++j){
            if (ColorCount[srcImage.at<uchar>(i, j)] <= pArea)
                srcImage.at<uchar>(i, j) = 255;
            else
                srcImage.at<uchar>(i, j) = 0;
        }
    }
}

2017.11.5

  • 11
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值