自适应阈值化

自适应阈值化

微信公众号:幼儿园的学霸

目录

文章目录

  • 自适应阈值化
  • 目录
  • 背景介绍及原理
    • 原理
    • 权重选择
    • 说明
    • 自定义实现
    • 结果对比
  • 参考资料

背景介绍及原理

原理

图像阈值化的一般目的是从灰度图像中分离出目标区域和背景区域,然而仅仅通过设定全局固定阈值(对图像中的每个点其二值化的阈值都是相同的)的方法很难达到理想的分割效果。那么就需要一种方法来应对这样的情况。

这种办法就是自适应阈值法(adaptiveThreshold),它的思想不是计算图像的全局阈值,而是根据图像不同区域亮度分布,计算其局部阈值,对于图像不同区域,能够自适应计算不同的阈值,因此被称为自适应阈值法(其实就是局部阈值法)。这种方法能够保证图像中各个像素的阈值会随着其周围邻域像素的变化而变化。

这样做的好处:
1.每个像素位置处的二值化阈值不是固定不变的,而是由其周围邻域像素的分布来决定的
2.亮度较高的图像区域的二值化阈值通常会较高,而亮度低的图像区域的二值化阈值则会相适应的变小
3.不同亮度、对比度、纹理的局部图像区域将会拥有相对应的局部二值化阈值

权重选择

那么自适应阈值化处理中是如何确定局部阈值呢?一种思路是,可以计算该像素其邻域(局部)的均值、中值、高斯加权平均(高斯滤波)来确定阈值。比这个阈值大,该位置像素置为255,比这个阈值小,则置为255,如此就实现了局部阈值分割。

接下来的问题就是,既然每个点都要取周边像素的值,那么应该如何分配权重呢?
如果使用简单平均,显然不是很合理,因为图像都是连续的,越靠近待处理位置的点关系越密切,越远离的点关系越疏远。因此,加权平均更合理,距离越近的点权重越大,距离越远的点权重越小。

cv::adaptiveThreshold()支持两种自适应方法,即cv::ADAPTIVE_THRESH_MEAN_C(平均)和cv::ADAPTIVE_THRESH_GAUSSIAN_C(高斯)。在两种情况下,自适应阈值T(x, y)。通过计算每个像素周围bxb大小像素块的加权均值并减去常量C得到。其中,b由blockSize给出,大小必须为奇数;如果使用平均的方法,则所有像素周围的权值相同;如果使用高斯的方法,则(x,y)周围的像素的权值则根据其到中心点的距离通过高斯方程得到。

OpenCV中已经实现有该函数,

void adaptiveThreshold(InputArray src, OutputArray dst, double maxValue,
    int adaptiveMethod, int thresholdType, int blockSize, double C)

参数说明如下:

src:源图像,必须是8位的灰度图

dst:处理后的目标图像,大小和类型与源图像相同
maxValue:用于指定满足条件的像素设定的灰度值
adaptiveMethod:使用的自适应阈值算法,有2种类型ADAPTIVE_THRESH_MEAN_C算法(局部邻域块均值)或ADAPTIVE_THRESH_GAUSSIAN_C(局部邻域块高斯加权和),
ADAPTIVE_THRESH_MEAN_C的计算方法是计算出邻域的平均值再减去第六个参数C的值,
ADAPTIVE_THRESH_GAUSSIAN_C的计算方法是计算出邻域的高斯均匀值再减去第六个参数C的值。

thresholdType:阈值类型,只能是THRESH_BINARY或THRESH_BINARY_INV二者之一,具体参考上面“图像阈值处理”的表格
blockSize:表示邻域块大小,用来计算区域阈值,一般选择3、5、7……
C:表示常数,它是一个从均匀或加权均值提取的常数,通常为正数,但也可以是负数或零

返回值dst:处理后的图像

说明

一点说明:
由于在灰度图像中,灰度值变化明显的区域往往是物体的轮廓,所以将图像分成一小块一小块的去计算阈值往往会得出图像的轮廓。因此函数adaptiveThreshold除了将灰度图像二值化,也可以进行边缘提取,当block很小时,如block_size=3 or 5 or 7时,“自适应”的程度很高,即容易出现block里面的像素值都差不多,这样便无法二值化,而只能在边缘等梯度大的地方实现二值化,结果显得它是边缘提取函数
当把blockSize设为比较大的值时,如blockSize=21 or 31 or 41时,adaptiveThreshold便是二值化函数

自定义实现

基于OpenCV中函数操作和自适应阈值的思想,自己实现了该函数,并和OpenCV实现进行对比。

#include 
/*!
 * 自定义实现自适应阈值二值化.
 * OpenCV源码函数:cv::adaptiveThreshold(...)
 * @param src 输入图像.CV_8UC1
 * @param dst CV_8UC1
 * @param maxValue 满足自适应阈值后的新像素值
 * @param blockSize 邻域大小.在该邻域内计算自适应阈值
 * @param C 自适应阈值调整量.利用高斯滤波或者均值滤波计算自适应阈值T后,(T+C)为最终的二值化阈值
 * @param adaptiveMethod 自适应阈值计算方法.和OpenCV 中 AdaptiveThresholdTypes 保持一致(均值滤波或这高斯滤波方式计算阈值)
 * @author liheng
 */
void AdaptiveThreshold(const cv::Mat &src, cv::Mat &dst, unsigned char maxValue, int blockSize, unsigned char C, int adaptiveMethod)
{
    CV_Assert( src.type() == CV_8UC1 );
    CV_Assert( blockSize % 2 == 1 && blockSize > 1 );
    CV_Assert(cv::ADAPTIVE_THRESH_MEAN_C== adaptiveMethod || cv::ADAPTIVE_THRESH_GAUSSIAN_C==adaptiveMethod );

    dst.create(src.size(),src.type());
    dst.setTo(cv::Scalar(0));

    cv::Mat mean;//保存计算的自适应阈值

    //计算自适应阈值:均值滤波或高斯滤波
    if (cv::ADAPTIVE_THRESH_MEAN_C == adaptiveMethod)
        cv::boxFilter( src, mean, src.type(), cv::Size(blockSize, blockSize),
                       cv::Point(-1,-1), true, cv::BORDER_REPLICATE|cv::BORDER_ISOLATED );
    else if (cv::ADAPTIVE_THRESH_GAUSSIAN_C == adaptiveMethod)
    {
        cv::Mat srcfloat,meanfloat;
        src.convertTo(srcfloat,CV_32F);
        meanfloat=srcfloat;
        GaussianBlur(srcfloat, meanfloat, cv::Size(blockSize, blockSize), 0, 0, cv::BORDER_REPLICATE|cv::BORDER_ISOLATED);
        meanfloat.convertTo(mean, src.type());
    }


    //======================================//
    //利用常规的遍历思想进行处理
    for(int r=0;r(r,c)+C;
            if( src.at(r,c)-T>0 )
                dst.at(r,c) = maxValue;
            //else
            //    dst.at(r,c) = 0;
        }
    }
    //======================================//


//    //======================================//
//    //利用查找表的思想加速计算
//    unsigned char tab[512]={0};//512来源:后面有公式sdata[j] - mdata[j] + 255的最大值为(255-0+255)=511
//    for(int i=0;i<512;++i)
//        tab[i] = i-255 > -C ? maxValue : 0;
//
//    cv::Size size(src.size());
//    if( src.isContinuous() && mean.isContinuous() && dst.isContinuous() )
//    {
//        size.width *= size.height;
//        size.height = 1;
//    }
//
//    for(int i = 0; i < size.height; i++ )
//    {
//        const uchar* sdata = src.ptr(i);
//        const uchar* mdata = mean.ptr(i);
//        uchar* ddata = dst.ptr(i);
//
//        for( int j = 0; j < size.width; j++ )
//            ddata[j] = tab[sdata[j] - mdata[j] + 255];
//    }
//    //======================================//
}

int main()
{
    cv::Mat src;
    cv::imread("./src.png",cv::IMREAD_GRAYSCALE);
    cv::imshow("src",src);
    
    
    cv::GaussianBlur(src,src,cv::Size(5,5),0);//建议进行一次滤波

    cv::Mat cvdst,mydst;
    int blockSize = 61;//建议尝试邻域大小为3和61分别进行尝试
    
    cv::adaptiveThreshold(src,cvdst,255,cv::ADAPTIVE_THRESH_MEAN_C,cv::THRESH_BINARY,blockSize,0);
    AdaptiveThreshold(src,mydst,255,blockSize,0,cv::ADAPTIVE_THRESH_MEAN_C);

    
    cv::imshow("cv adaptive",cvdst);
    cv::imshow("my adaptive",mydst);
    
    cv::waitKey(0);
    
    return 0;
}

结果对比

输入图像
自适应阈值化_第1张图片
blocksize OpenCV结果 自定义实现结果
3 自适应阈值化_第2张图片 自适应阈值化_第3张图片
61 自适应阈值化_第4张图片 自适应阈值化_第5张图片

参考资料

1.opencv自适应阈值函数adaptiveThreshold() 剖析
2.自适应阈值(adaptiveThreshold)分割原理及实现



下面的是我的公众号二维码图片,按需关注。
图注:幼儿园的学霸

你可能感兴趣的:(OpenCV,C++,计算机视觉,算法,opencv)