OpenCV::adaptiveThreshold 自适应阈值分割的源码分析

自适应阈值分割算法

  • 阈值分割算法或者二值化算法是用输入像素的值 II 与一个值 CC 来比较,根据比较结果确定输出值。
  • 自适应阈值分割的每一个像素的比较值(阈值) CC 都不同,阈值 CC 由这个像素为中心的一个块范围计算在减去差值 delta 得到。

算法优势: 自适应阈值分割是在像素点的局部相邻区域内独立计算阈值, 再进行二值化分割, 尤其适用于明暗程度不一致的目标。

自适应阈值的计算方法

常用的两种方法:

  1. 平均值减去差值delta(使用盒过滤boxfilter,性能会非常不错)
  2. 高斯分布加权和减去差值delta (使用高斯滤波GaussionBlur)

举个例子:如果使用平均值方法,平均值mean为190,差值delta为30。那么灰度小于160的像素为0,大于等于160的像素为255。如下图:
在这里插入图片描述

如果是反向二值化,如下图:
在这里插入图片描述
delta选择负值也是可以的。

源码和注释

/** @brief 自适应二值化
*@param _src     要二值化的灰度图
*@param _dst     二值化后的图
*@param maxValue    二值化后要设置的那个值
*@param method 块计算的方法(ADAPTIVE_THRESH_MEAN_C 平均值,ADAPTIVE_THRESH_GAUSSIAN_C 高斯分布加权和)
*@param type     二值化类型(CV_THRESH_BINARY 大于为最大值,CV_THRESH_BINARY_INV 小于为最大值)
*@param blockSize    块大小(奇数,大于1)
*@param delta 差值(负值也可以)
*/
void cv::adaptiveThreshold(InputArray _src, OutputArray _dst, double maxValue,
    int method, int type, int blockSize, double delta)
{
    Mat src = _src.getMat();
 
    // 原图必须是单通道无符号8位
    CV_Assert(src.type() == CV_8UC1);
 
    // 块大小必须大于1,并且是奇数
    CV_Assert(blockSize % 2 == 1 && blockSize > 1);
    Size size = src.size();
 
    // 构建与原图像相同的图像
    _dst.create(size, src.type());
    Mat dst = _dst.getMat();
 
    if (maxValue < 0)
    {
        // 二值化后值小于0,图像都为0
        dst = Scalar(0);
        return;
    }

	CALL_HAL(adaptiveThreshold, cv_hal_adaptiveThreshold, src.data, src.step, dst.data, dst.step, src.cols, src.rows,
             maxValue, method, type, blockSize, delta);  // 硬件加速算法 cv_hal_adaptiveThreshold 未作实现,直接忽略
 
    // 用于比较的值
    Mat mean;
 
    if (src.data != dst.data)
        mean = dst;
 
    if (method == ADAPTIVE_THRESH_MEAN_C)
        // 计算平均值作为比较值
        boxFilter(src, mean, src.type(), Size(blockSize, blockSize),
        Point(-1, -1), true, BORDER_REPLICATE);
    else if (method == ADAPTIVE_THRESH_GAUSSIAN_C)
        // 计算高斯分布和作为比较值
        GaussianBlur(src, mean, Size(blockSize, blockSize), 0, 0, BORDER_REPLICATE);
    else
        CV_Error(CV_StsBadFlag, "Unknown/unsupported adaptive threshold method");
 
    int i, j;
 
    // 将maxValue夹到[0,255]的uchar范围区间,用作二值化后的值
    uchar imaxval = saturate_cast<uchar>(maxValue);
 
    // 根据二值化类型计算delta值
    int idelta = type == THRESH_BINARY ? cvCeil(delta) : cvFloor(delta);
 
    // 计算生成每个像素差对应的值表格,以后查表就可以。但像素差范围为什么是768,我确实认为512已经够了
    uchar tab[768];
 
    if (type == CV_THRESH_BINARY)
        for (i = 0; i < 768; i++)
            // i = src[j] - mean[j] + 255
            // i - 255 > -idelta ? imaxval : 0
            // = src[j] - mean[j] + 255 -255 > -idelta ? imaxval : 0
            // = src[j] > mean[j] - idelta ? imaxval : 0
            tab[i] = (uchar)(i - 255 > -idelta ? imaxval : 0);
    else if (type == CV_THRESH_BINARY_INV)
        for (i = 0; i < 768; i++)
            // i = src[j] - mean[j] + 255
            // i - 255 <= -idelta ? imaxval : 0
            // = src[j] - mean[j] + 255 - 255 <= -idelta ? imaxval : 0
            // = src[j] <= mean[j] - idelta ? imaxval : 0
            tab[i] = (uchar)(i - 255 <= -idelta ? imaxval : 0);
    else
        CV_Error(CV_StsBadFlag, "Unknown/unsupported threshold type");
 
    // 如果连续,加速运算
    if (src.isContinuous() && mean.isContinuous() && dst.isContinuous())
    {
        size.width *= size.height;
        size.height = 1;
    }
 
    // 逐像素计算src[j] - mean[j] + 255,并查表得到结果
    for (i = 0; i < size.height; i++)
    {
        const uchar* sdata = src.data + src.step*i;
        const uchar* mdata = mean.data + mean.step*i;
        uchar* ddata = dst.data + dst.step*i;
 
        for (j = 0; j < size.width; j++)
            // 将[-255, 255] 映射到[0, 510]然后查表
            ddata[j] = tab[sdata[j] - mdata[j] + 255];
    }
}

算法示例

Mat src = imread("d:\\src.jpg", 0);
cv::adaptiveThreshold(src, bw, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, 17, 0);

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章