自适应阈值化

自适应阈值化

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

目录

文章目录

背景介绍及原理

原理

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

这种办法就是自适应阈值法(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 <opencv2 opencv.hpp>
/*!

 * &#x81EA;&#x5B9A;&#x4E49;&#x5B9E;&#x73B0;&#x81EA;&#x9002;&#x5E94;&#x9608;&#x503C;&#x4E8C;&#x503C;&#x5316;.

 * OpenCV&#x6E90;&#x7801;&#x51FD;&#x6570;:cv::adaptiveThreshold(...)
 * @param src &#x8F93;&#x5165;&#x56FE;&#x50CF;.CV_8UC1
 * @param dst CV_8UC1
 * @param maxValue &#x6EE1;&#x8DB3;&#x81EA;&#x9002;&#x5E94;&#x9608;&#x503C;&#x540E;&#x7684;&#x65B0;&#x50CF;&#x7D20;&#x503C;
 * @param blockSize &#x90BB;&#x57DF;&#x5927;&#x5C0F;.&#x5728;&#x8BE5;&#x90BB;&#x57DF;&#x5185;&#x8BA1;&#x7B97;&#x81EA;&#x9002;&#x5E94;&#x9608;&#x503C;
 * @param C &#x81EA;&#x9002;&#x5E94;&#x9608;&#x503C;&#x8C03;&#x6574;&#x91CF;.&#x5229;&#x7528;&#x9AD8;&#x65AF;&#x6EE4;&#x6CE2;&#x6216;&#x8005;&#x5747;&#x503C;&#x6EE4;&#x6CE2;&#x8BA1;&#x7B97;&#x81EA;&#x9002;&#x5E94;&#x9608;&#x503C;T&#x540E;,(T+C)&#x4E3A;&#x6700;&#x7EC8;&#x7684;&#x4E8C;&#x503C;&#x5316;&#x9608;&#x503C;
 * @param adaptiveMethod &#x81EA;&#x9002;&#x5E94;&#x9608;&#x503C;&#x8BA1;&#x7B97;&#x65B9;&#x6CD5;.&#x548C;OpenCV &#x4E2D; AdaptiveThresholdTypes &#x4FDD;&#x6301;&#x4E00;&#x81F4;(&#x5747;&#x503C;&#x6EE4;&#x6CE2;&#x6216;&#x8FD9;&#x9AD8;&#x65AF;&#x6EE4;&#x6CE2;&#x65B9;&#x5F0F;&#x8BA1;&#x7B97;&#x9608;&#x503C;)
 * @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;//&#x4FDD;&#x5B58;&#x8BA1;&#x7B97;&#x7684;&#x81EA;&#x9002;&#x5E94;&#x9608;&#x503C;

    //&#x8BA1;&#x7B97;&#x81EA;&#x9002;&#x5E94;&#x9608;&#x503C;:&#x5747;&#x503C;&#x6EE4;&#x6CE2;&#x6216;&#x9AD8;&#x65AF;&#x6EE4;&#x6CE2;
    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());
    }

    //======================================//
    //&#x5229;&#x7528;&#x5E38;&#x89C4;&#x7684;&#x904D;&#x5386;&#x601D;&#x60F3;&#x8FDB;&#x884C;&#x5904;&#x7406;
    for(int r=0;r<src.rows; ++r) { for(int c="0;c<src.cols;++c)" const auto t="mean.at<uchar">(r,c)+C;
            if( src.at<uchar>(r,c)-T>0 )
                dst.at<uchar>(r,c) = maxValue;
            //else
            //    dst.at<uchar>(r,c) = 0;
        }
    }
    //======================================//

//    //======================================//
//    //&#x5229;&#x7528;&#x67E5;&#x627E;&#x8868;&#x7684;&#x601D;&#x60F3;&#x52A0;&#x901F;&#x8BA1;&#x7B97;
//    unsigned char tab[512]={0};//512&#x6765;&#x6E90;:&#x540E;&#x9762;&#x6709;&#x516C;&#x5F0F;sdata[j] - mdata[j] + 255&#x7684;&#x6700;&#x5927;&#x503C;&#x4E3A;(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);//&#x5EFA;&#x8BAE;&#x8FDB;&#x884C;&#x4E00;&#x6B21;&#x6EE4;&#x6CE2;

    cv::Mat cvdst,mydst;
    int blockSize = 61;//&#x5EFA;&#x8BAE;&#x5C1D;&#x8BD5;&#x90BB;&#x57DF;&#x5927;&#x5C0F;&#x4E3A;3&#x548C;61&#x5206;&#x522B;&#x8FDB;&#x884C;&#x5C1D;&#x8BD5;

    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;
}

</512;++i)></uchar></uchar></uchar></src.rows;></opencv2>

结果对比

输入图像

自适应阈值化

blocksizeOpenCV结果自定义实现结果3

自适应阈值化

61

自适应阈值化

; 参考资料

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

下面的是我的公众号二维码图片,按需关注。

自适应阈值化

Original: https://blog.csdn.net/leonardohaig/article/details/119535608
Author: leonardohaig
Title: 自适应阈值化

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/704931/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球