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.
- blockSize
是boxFilter
和GaussianBlur
的参数,后面将直接使用boxFilter
去验证
- method
是选择boxFilter
还是GaussianBlur
哪种方法
实验
- 用
boxFilter
去做测试 - 在上述条件下,分别用
THRESH_BINARY
和THRESH_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. 通过boxFilter
或GaussianBlur
计算一个均值mean
矩阵
2. 将原始灰度图片src
与mean
相加
3. 将步骤2
的结果与参数cvCeil(delta)
进行比较, 至于怎么比较, 看THRESH_BINARY
和THRESH_BINARY_INV
总结
- 代码中的
tab[768]
作用是为了将多个功能实现为一体, 自己仿真完全可以不用这个 tab[768]
的768
和前面的boxFilter
或GaussianBlur
有关系, 我认为512
也是可以的, 毕竟sdata[j] - mdata[j] + 255
不会超过512
blockSize
必须是奇数