opencv中的adaptiveThreshold分析与验证

opencv原始的adaptiveThreshold代码

void cv::adaptiveThreshold( InputArray _src, OutputArray _dst, double maxValue,
                            int method, int type, int blockSize, double delta )
{
    Mat src = _src.getMat();
    CV_Assert( src.type() == CV_8UC1 );
    CV_Assert( blockSize % 2 == 1 && blockSize > 1 );
    Size size = src.size();

    _dst.create( size, src.type() );
    Mat dst = _dst.getMat();

    if( maxValue < 0 )
    {
        dst = Scalar(0);
        return;
    }

    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;
    uchar imaxval = saturate_cast<uchar>(maxValue);
    int idelta = type == THRESH_BINARY ? cvCeil(delta) : cvFloor(delta);
    uchar tab[768];

    if( type == CV_THRESH_BINARY )
        for( i = 0; i < 768; i++ )
            tab[i] = (uchar)(i - 255 > -idelta ? imaxval : 0);
    else if( type == CV_THRESH_BINARY_INV )
        for( i = 0; i < 768; i++ )
            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;
    }

    for( i = 0; i < size.height; i++ )
    {
        const uchar* sdata = src.ptr(i);
        const uchar* mdata = mean.ptr(i);
        uchar* ddata = dst.ptr(i);

        for( j = 0; j < size.width; j++ )
            ddata[j] = tab[sdata[j] - mdata[j] + 255];
    }
}

这是opencv官方自己的算法具体实现,仔细发现,难以阅读。为了更好的理解这个函数,我们需要对这个函数进行适当的“改装”,以便更好的调试。比如可以删除以下代码,结果是不影响的,下面的代码仅仅是为了提速。

if( src.isContinuous() && mean.isContinuous() && dst.isContinuous() )
    {
        size.width *= size.height;
        size.height = 1;
    }

可以增加一些临时变量,更好的去观察这个变量值的变化。如int t = i - 255 <= -idelta ? imaxval : 0;,我们可以观察这个t的变化。这个t并不影响代码的运行效果。

经过修改易调试的adaptiveThreshold代码

/** @brief 自适应阈值-易调试代码
*/
static void adaptiveThreshold2(cv::InputArray _src, cv::OutputArray _dst, double maxValue,
    int method, int type, int blockSize, double delta)
{
    cv::Mat src = _src.getMat();
    CV_Assert(src.type() == CV_8UC1);
    CV_Assert(blockSize % 2 == 1 && blockSize > 1);
    cv::Size size = src.size();

    _dst.create(size, src.type());
    cv::Mat dst = _dst.getMat();

    if (maxValue < 0)
    {
        dst = cv::Scalar(0);
        return;
    }

    cv::Mat mean;

    if (src.data != dst.data)
        mean = dst;

    if (method == cv::ADAPTIVE_THRESH_MEAN_C)
        boxFilter(src, mean, src.type(), cv::Size(blockSize, blockSize),
            cv::Point(-1, -1), true, cv::BORDER_REPLICATE);
    else if (method == cv::ADAPTIVE_THRESH_GAUSSIAN_C)
        GaussianBlur(src, mean, cv::Size(blockSize, blockSize), 0, 0, cv::BORDER_REPLICATE);
    else
        CV_Error(CV_StsBadFlag, "Unknown/unsupported adaptive threshold method");

    int i, j;
    uchar imaxval = cv::saturate_cast<uchar>(maxValue);
    int idelta = type == cv::THRESH_BINARY ? cvCeil(delta) : cvFloor(delta);
    uchar tab[768];

    /*if (type == CV_THRESH_BINARY)
        for (i = 0; i < 768; i++)
            tab[i] = (uchar)(i - 255 > -idelta ? imaxval : 0);
    else if (type == CV_THRESH_BINARY_INV)
        for (i = 0; i < 768; i++)
            tab[i] = (uchar)(i - 255 <= -idelta ? imaxval : 0);
    else
        CV_Error(CV_StsBadFlag, "Unknown/unsupported threshold type");*/

    if (type == CV_THRESH_BINARY)
    {
        for (i = 0; i < 768; i++)
        {
            // 这里的t仅仅用来调试使用, 可以忽略
            int t = i - 255 > -idelta ? imaxval : 0;

            tab[i] = (uchar)(i - 255 > -idelta ? imaxval : 0);
        }
    }
    else if (type == CV_THRESH_BINARY_INV)
    {
        for (i = 0; i < 768; i++)
        {
            // 这里的t仅仅用来调试使用, 可以忽略
            int t = i - 255 <= -idelta ? imaxval : 0;

            tab[i] = (uchar)(i - 255 <= -idelta ? imaxval : 0);
        }
    }
    else
    {
        CV_Error(CV_StsBadFlag, "Unknown/unsupported threshold type");
    }

    // 此处代码仅仅是为了提升速度, k可以忽略
    /*if (src.isContinuous() && mean.isContinuous() && dst.isContinuous())
    {
        size.width *= size.height;
        size.height = 1;
    }*/

    for (i = 0; i < size.height; i++)
    {
        const uchar* sdata = src.ptr(i);
        const uchar* mdata = mean.ptr(i);
        uchar* ddata = dst.ptr(i);

        for (j = 0; j < size.width; j++)
        {
            // 这里的index仅仅用来调试使用, 可以忽略
            int index = sdata[j] - mdata[j] + 255;

            ddata[j] = tab[sdata[j] - mdata[j] + 255];

        }
    }

    // 用来调试使用, 可以忽略
    cv::waitKey(1);
}

通过调试上面改造的代码,我们可以得出以下结论:
- 算法处理结果一定是二值图像, 图像的值可能是cv::saturate_cast<uchar>(maxValue)和0.
- blockSizeboxFilterGaussianBlur的参数,后面将直接使用boxFilter去验证
- method是选择boxFilter还是GaussianBlur哪种方法

实验

  • boxFilter去做测试
  • 在上述条件下,分别用THRESH_BINARYTHRESH_BINARY_INV各写一份代码验证

THRESH_BINARY的自适应代码

/** @brief 自适应阈值代码-仅仅在参数为cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY情况

*/
static void adaptiveThreshold3(cv::Mat& src, cv::Mat& dst, double maxValue, int blockSize, double delta)
{
    // 默认 cv::ADAPTIVE_THRESH_MEAN_C
    // 默认 cv::THRESH_BINARY
    CV_Assert(src.type() == CV_8UC1);
    CV_Assert(blockSize % 2 == 1 && blockSize > 1);

    dst.create(src.size(), CV_8UC1);
    if (maxValue < 0)
    {
        dst = cv::Scalar(0);
        return;
    }

    cv::Mat mean;
    if (src.data != dst.data) mean = dst;
    boxFilter(src, mean, src.type(), cv::Size(blockSize, blockSize), cv::Point(-1, -1), true, cv::BORDER_REPLICATE);
    uchar imaxval = cv::saturate_cast<uchar>(maxValue);
    int idelta = cvCeil(delta);

    for (int y = 0; y < src.rows; ++y)
    {
        for (int x = 0; x < src.cols; ++x)
        {
            uchar s = src.at<uchar>(y, x);
            uchar m = mean.at<uchar>(y, x);
            int value = 0;

            // 条件可以改为s-m > idelta也可以
            if (s + idelta > m) value = imaxval;

            dst.at<uchar>(y, x) = value;
        }
    }

    cv::waitKey(1);
}

THRESH_BINARY_INV的自适应代码

/** @brief 自适应阈值代码-仅仅在参数为cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY_INV情况

*/
static void adaptiveThreshold4(cv::Mat& src, cv::Mat& dst, double maxValue, int blockSize, double delta)
{
    // 默认 cv::ADAPTIVE_THRESH_MEAN_C
    // 默认 cv::THRESH_BINARY_INV
    CV_Assert(src.type() == CV_8UC1);
    CV_Assert(blockSize % 2 == 1 && blockSize > 1);

    dst.create(src.size(), CV_8UC1);
    if (maxValue < 0)
    {
        dst = cv::Scalar(0);
        return;
    }

    cv::Mat mean;
    if (src.data != dst.data) mean = dst;
    boxFilter(src, mean, src.type(), cv::Size(blockSize, blockSize), cv::Point(-1, -1), true, cv::BORDER_REPLICATE);
    uchar imaxval = cv::saturate_cast<uchar>(maxValue);
    int idelta = cvCeil(delta);

    for (int y = 0; y < src.rows; ++y)
    {
        for (int x = 0; x < src.cols; ++x)
        {
            uchar s = src.at<uchar>(y, x);
            uchar m = mean.at<uchar>(y, x);
            int value = 0;

            // 条件可以改为s-m <= -idelta也可以
            if (s + idelta <= m) value = imaxval;

            dst.at<uchar>(y, x) = value;
        }
    }

    cv::waitKey(1);
}

比较两个矩阵是否相同

/** @brief 比较两个Mat数据是否一样, true表示相同,false表示不同

@param img1 第一个图片
@param img2 第二个图片
*/
static bool compareTwoImg(const cv::Mat& img1, cv::Mat& img2)
{
    CV_Assert(img1.type() == CV_8UC1);
    CV_Assert(img2.type() == CV_8UC1);

    if ((img1.rows != img2.rows) || (img1.cols != img2.cols)) return false;

    for (int y = 0; y < img1.rows; ++y)
    {
        for (int x = 0; x < img1.cols; ++x)
        {
            uchar v1 = img1.at<uchar>(y, x);
            uchar v2 = img2.at<uchar>(y, x);
            if (v1 != v2) return false;
        }
    }

    return true;
}

测试前面代码

void test_AdaptiveThreshold()
{
    // 图片路径, 修改成自己的图片路径
    std::string path = "../Resources/wechat_20180409161327.bmp";
    cv::Mat img = cv::imread(path, cv::ImreadModes::IMREAD_GRAYSCALE);

    /********************************************THRESH_BINARY_INV测试*****************************************************/
    // opencv库中的函数调用结果
    cv::Mat dst;
    cv::adaptiveThreshold(img, dst, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY_INV, 15/*blockSize*/, 10/*C*/);


    // 调试代码调用结果
    cv::Mat dst2;
    adaptiveThreshold2(img, dst2, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY_INV, 15/*blockSize*/,10/*C*/);

    // 参数为cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY_INV情况
    cv::Mat dst4;
    adaptiveThreshold4(img, dst4, 255, 15, 10);

    std::cout << "THRESH_BINARY_INV(库, 仿真代码):" << (compareTwoImg(dst, dst4) == true ? "相同" : "不同") << std::endl;
    std::cout << "THRESH_BINARY_INV(库, 调试代码):" << (compareTwoImg(dst, dst2) == true ? "相同" : "不同") << std::endl;

    /********************************************THRESH_BINARY测试*****************************************************/
    // 参数为cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY情况
    cv::Mat dst3;
    adaptiveThreshold3(img, dst3, 255, 15, 10);

    // opencv库中的函数调用结果
    cv::Mat dst5;
    cv::adaptiveThreshold(img, dst5, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, 15/*blockSize*/, 10/*C*/);

    // 调试代码调用结果
    cv::Mat dst6;
    adaptiveThreshold2(img, dst6, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, 15/*blockSize*/, 10/*C*/);

    std::cout << "THRESH_BINARY(库, 仿真代码):" << (compareTwoImg(dst3, dst5) == true ? "相同" : "不同") << std::endl;
    std::cout << "THRESH_BINARY(库, 调试代码):" << (compareTwoImg(dst6, dst5) == true ? "相同" : "不同") << std::endl;

}

所有代码-方便读者复制

#include <string>
#include <iostream>
#include "opencv2/opencv.hpp"



/** @brief 自适应阈值-易调试代码
*/
static void adaptiveThreshold2(cv::InputArray _src, cv::OutputArray _dst, double maxValue,
    int method, int type, int blockSize, double delta)
{
    cv::Mat src = _src.getMat();
    CV_Assert(src.type() == CV_8UC1);
    CV_Assert(blockSize % 2 == 1 && blockSize > 1);
    cv::Size size = src.size();

    _dst.create(size, src.type());
    cv::Mat dst = _dst.getMat();

    if (maxValue < 0)
    {
        dst = cv::Scalar(0);
        return;
    }

    cv::Mat mean;

    if (src.data != dst.data)
        mean = dst;

    if (method == cv::ADAPTIVE_THRESH_MEAN_C)
        boxFilter(src, mean, src.type(), cv::Size(blockSize, blockSize),
            cv::Point(-1, -1), true, cv::BORDER_REPLICATE);
    else if (method == cv::ADAPTIVE_THRESH_GAUSSIAN_C)
        GaussianBlur(src, mean, cv::Size(blockSize, blockSize), 0, 0, cv::BORDER_REPLICATE);
    else
        CV_Error(CV_StsBadFlag, "Unknown/unsupported adaptive threshold method");

    int i, j;
    uchar imaxval = cv::saturate_cast<uchar>(maxValue);
    int idelta = type == cv::THRESH_BINARY ? cvCeil(delta) : cvFloor(delta);
    uchar tab[768];

    /*if (type == CV_THRESH_BINARY)
        for (i = 0; i < 768; i++)
            tab[i] = (uchar)(i - 255 > -idelta ? imaxval : 0);
    else if (type == CV_THRESH_BINARY_INV)
        for (i = 0; i < 768; i++)
            tab[i] = (uchar)(i - 255 <= -idelta ? imaxval : 0);
    else
        CV_Error(CV_StsBadFlag, "Unknown/unsupported threshold type");*/

    if (type == CV_THRESH_BINARY)
    {
        for (i = 0; i < 768; i++)
        {
            // 这里的t仅仅用来调试使用, 可以忽略
            int t = i - 255 > -idelta ? imaxval : 0;

            tab[i] = (uchar)(i - 255 > -idelta ? imaxval : 0);
        }
    }
    else if (type == CV_THRESH_BINARY_INV)
    {
        for (i = 0; i < 768; i++)
        {
            // 这里的t仅仅用来调试使用, 可以忽略
            int t = i - 255 <= -idelta ? imaxval : 0;

            tab[i] = (uchar)(i - 255 <= -idelta ? imaxval : 0);
        }
    }
    else
    {
        CV_Error(CV_StsBadFlag, "Unknown/unsupported threshold type");
    }

    // 此处代码仅仅是为了提升速度, k可以忽略
    /*if (src.isContinuous() && mean.isContinuous() && dst.isContinuous())
    {
        size.width *= size.height;
        size.height = 1;
    }*/

    for (i = 0; i < size.height; i++)
    {
        const uchar* sdata = src.ptr(i);
        const uchar* mdata = mean.ptr(i);
        uchar* ddata = dst.ptr(i);

        for (j = 0; j < size.width; j++)
        {
            // 这里的index仅仅用来调试使用, 可以忽略
            int index = sdata[j] - mdata[j] + 255;

            ddata[j] = tab[sdata[j] - mdata[j] + 255];

        }
    }

    // 这里的index仅仅用来调试使用, 可以忽略
    cv::waitKey(1);
}

/** @brief 自适应阈值代码-仅仅在参数为cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY情况

*/
static void adaptiveThreshold3(cv::Mat& src, cv::Mat& dst, double maxValue, int blockSize, double delta)
{
    // 默认 cv::ADAPTIVE_THRESH_MEAN_C
    // 默认 cv::THRESH_BINARY
    CV_Assert(src.type() == CV_8UC1);
    CV_Assert(blockSize % 2 == 1 && blockSize > 1);

    dst.create(src.size(), CV_8UC1);
    if (maxValue < 0)
    {
        dst = cv::Scalar(0);
        return;
    }

    cv::Mat mean;
    if (src.data != dst.data) mean = dst;
    boxFilter(src, mean, src.type(), cv::Size(blockSize, blockSize), cv::Point(-1, -1), true, cv::BORDER_REPLICATE);
    uchar imaxval = cv::saturate_cast<uchar>(maxValue);
    int idelta = cvCeil(delta);

    for (int y = 0; y < src.rows; ++y)
    {
        for (int x = 0; x < src.cols; ++x)
        {
            uchar s = src.at<uchar>(y, x);
            uchar m = mean.at<uchar>(y, x);
            int value = 0;

            // 条件可以改为s-m > idelta也可以
            if (s + idelta > m) value = imaxval;

            dst.at<uchar>(y, x) = value;
        }
    }

    cv::waitKey(1);
}

/** @brief 自适应阈值代码-仅仅在参数为cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY_INV情况

*/
static void adaptiveThreshold4(cv::Mat& src, cv::Mat& dst, double maxValue, int blockSize, double delta)
{
    // 默认 cv::ADAPTIVE_THRESH_MEAN_C
    // 默认 cv::THRESH_BINARY_INV
    CV_Assert(src.type() == CV_8UC1);
    CV_Assert(blockSize % 2 == 1 && blockSize > 1);

    dst.create(src.size(), CV_8UC1);
    if (maxValue < 0)
    {
        dst = cv::Scalar(0);
        return;
    }

    cv::Mat mean;
    if (src.data != dst.data) mean = dst;
    boxFilter(src, mean, src.type(), cv::Size(blockSize, blockSize), cv::Point(-1, -1), true, cv::BORDER_REPLICATE);
    uchar imaxval = cv::saturate_cast<uchar>(maxValue);
    int idelta = cvCeil(delta);

    for (int y = 0; y < src.rows; ++y)
    {
        for (int x = 0; x < src.cols; ++x)
        {
            uchar s = src.at<uchar>(y, x);
            uchar m = mean.at<uchar>(y, x);
            int value = 0;

            // 条件可以改为s-m <= -idelta也可以
            if (s + idelta <= m) value = imaxval;

            dst.at<uchar>(y, x) = value;
        }
    }

    cv::waitKey(1);
}


/** @brief 比较两个Mat数据是否一样, true表示相同,false表示不同

@param img1 第一个图片
@param img2 第二个图片
*/
static bool compareTwoImg(const cv::Mat& img1, cv::Mat& img2)
{
    CV_Assert(img1.type() == CV_8UC1);
    CV_Assert(img2.type() == CV_8UC1);

    if ((img1.rows != img2.rows) || (img1.cols != img2.cols)) return false;

    for (int y = 0; y < img1.rows; ++y)
    {
        for (int x = 0; x < img1.cols; ++x)
        {
            uchar v1 = img1.at<uchar>(y, x);
            uchar v2 = img2.at<uchar>(y, x);
            if (v1 != v2) return false;
        }
    }

    return true;
}


void test_AdaptiveThreshold()
{
    // 图片路径, 修改成自己的图片路径
    std::string path = "../Resources/wechat_20180409161327.bmp";
    cv::Mat img = cv::imread(path, cv::ImreadModes::IMREAD_GRAYSCALE);

    /********************************************THRESH_BINARY_INV测试*****************************************************/
    // opencv库中的函数调用结果
    cv::Mat dst;
    cv::adaptiveThreshold(img, dst, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY_INV, 15/*blockSize*/, 10/*C*/);


    // 调试代码调用结果
    cv::Mat dst2;
    adaptiveThreshold2(img, dst2, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY_INV, 15/*blockSize*/,10/*C*/);

    // 参数为cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY_INV情况
    cv::Mat dst4;
    adaptiveThreshold4(img, dst4, 255, 15, 10);

    std::cout << "THRESH_BINARY_INV(库, 仿真代码):" << (compareTwoImg(dst, dst4) == true ? "相同" : "不同") << std::endl;
    std::cout << "THRESH_BINARY_INV(库, 调试代码):" << (compareTwoImg(dst, dst2) == true ? "相同" : "不同") << std::endl;

    /********************************************THRESH_BINARY测试*****************************************************/
    // 参数为cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY情况
    cv::Mat dst3;
    adaptiveThreshold3(img, dst3, 255, 15, 10);

    // opencv库中的函数调用结果
    cv::Mat dst5;
    cv::adaptiveThreshold(img, dst5, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, 15/*blockSize*/, 10/*C*/);

    // 调试代码调用结果
    cv::Mat dst6;
    adaptiveThreshold2(img, dst6, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, 15/*blockSize*/, 10/*C*/);

    std::cout << "THRESH_BINARY(库, 仿真代码):" << (compareTwoImg(dst3, dst5) == true ? "相同" : "不同") << std::endl;
    std::cout << "THRESH_BINARY(库, 调试代码):" << (compareTwoImg(dst6, dst5) == true ? "相同" : "不同") << std::endl;

}

为了让读者去验证,出现单个单个复制代码麻烦的问题,所以可以直接复制上面的代码并修改自己的图片路径即可!

实验分析

adaptiveThreshold的原理是:
1. 通过boxFilterGaussianBlur计算一个均值mean矩阵
2. 将原始灰度图片srcmean相加
3. 将步骤2的结果与参数cvCeil(delta)进行比较, 至于怎么比较, 看THRESH_BINARYTHRESH_BINARY_INV

总结

  1. 代码中的tab[768]作用是为了将多个功能实现为一体, 自己仿真完全可以不用这个
  2. tab[768]768和前面的boxFilterGaussianBlur有关系, 我认为 512也是可以的, 毕竟sdata[j] - mdata[j] + 255不会超过512
  3. blockSize必须是奇数
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值