1 前言
在计算机视觉技术中,阈值处理是一种非常重要的操作,它是很多高级算法的底层处理逻辑之一。比如在使用OpenCV检测图形时,通常要先对灰度图像进行阈值(二值化)处理,这样就得到了图像的大致轮廓,以便于识别图形。
在阈值处理中,会将图像的每一个像素值与阈值进行比较,如果小于阈值,则将像素值置为0(黑色),若大于或等于阈值,将像素值置为最大值255(白色)。下边我们一起了解一下OpenCV中的三种阈值处理技术:简单阈值处理、自适应阈值处理和Otsu阈值处理。
2 简单阈值处理
OpenCV中,简单阈值处理的C++接口原型是:
double cv::threshold( InputArray src,
OutputArray dst,
double thresh,
double maxval,
int type
)
参数说明:
参数1:待处理的图像,可以是彩色图像或灰度图像,建议使用灰度图像
参数2:阈值处理后的图像
参数3:阈值,一般在125~150之间取一个阈值,效果比较好
参数4:阈值处理采用的最大值
参数5:阈值处理类型,包括以下表格中的5种类型
返回值:处理时采用的阈值
5种简单阈值处理的C++核心代码如下:
//二值化处理
threshold(src, dst, 127, 255, THRESH_BINARY);
imshow("binary", dst);
imwrite("binary.jpg", dst);
//反二值化处理
threshold(src, dst, 127, 255, THRESH_BINARY_INV);
imshow("binary-inv", dst);
imwrite("binary-inv.jpg", dst);
//截断阈值处理
threshold(src, dst, 127, 255, THRESH_TRUNC);
imshow("trunc", dst);
imwrite("trunc.jpg", dst);
//低于阈值0处理
threshold(src, dst, 127, 255, THRESH_TOZERO);
imshow("tozero", dst);
imwrite("tozero.jpg", dst);
//超出阈值0处理
threshold(src, dst, 127, 255, THRESH_TOZERO_INV);
imshow("to-zero-inv", dst);
imwrite("to-zero-inv.jpg", dst);
效果图如下:
第一张图像是灰度图像,其对应的原始彩色图像如下:
后边5幅图像,依次是二值化阈值处理、反二值化阈值处理、截断阈值处理、低于阈值0处理、超出阈值0处理对应的图像。可以看出来,截断阈值处理和低于阈值0处理,效果相对比较好一些,但图像的有些轮廓依然不清晰。
3 自适应阈值处理
在简单阈值处理中,我们使用了一个全局的阈值,细心的读者可能已经发现了,这个值是127。就是说,对一幅图像的所有像素值,都是用这1个阈值来比较和处理的。而实际情况是,一幅图像的不同区域,往往明暗度不一样。
OpenCV提供了一个改进的阈值处理技术,即自适应阈值处理。在这里,内部算法根据一个像素周围的小区域来确定该像素的阈值,因此对同一图像的不同区域会得到不同的阈值。
OpenCV中的自适应阈值处理接口原型:
void cv::adaptiveThreshold ( InputArray src,
OutputArray dst,
double maxValue,
int adaptiveMethod,
int thresholdType,
int blockSize,
double C
)
参数说明:
参数1:待处理的图像,必须使用灰度图像
参数2:阈值处理后的图像
参数3:阈值处理采用的最大值
参数4:自适应阈值计算方法,包括2种,如下表所示
参数5:阈值处理类型,仅包括以下2种:THRESH_BINARY或THRESH_BINARY_INV
参数6:一个正方形区域的大小,例如11,就是11 x 11的矩阵区域
参数7:常量,阈值等于均值或加权值减去这个常量值
自适应阈值处理的C++核心代码如下:
//自适应阈值
//阈值是邻近区域的平均值减去常数C
adaptiveThreshold(src, dst, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 11, 2);
imshow("mean-binary", dst);
imwrite("mean-binary.jpg", dst);
//自适应阈值
//阈值是邻域值的高斯加权和减去常数C
adaptiveThreshold(src, dst, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 11, 2);
imshow("gauss-binary", dst);
imwrite("gauss-binary.jpg", dst);
效果图如下:
以上4幅图像,依次是原图灰度图像、二值化阈值处理、平均值自适应阈值处理、高斯加权自适应阈值处理的图像。与前边的简单阈值处理相比,自适应阈值处理较好的保留了二值图像的轮廓信息。
4 Otsu阈值处理
在简单阈值处理中,还存在一个问题,示例代码中使用的阈值127,是随机采用的一个数值,并不是通过算法计算得到的。因为实际图像处理时,有的图像阈值选择127可能刚刚好,但有的图像选择这个值并不合适。那怎么办?一个一个去手动尝试吗,可以,但是太耗费时间了。
针对这个问题,OpenCV提供了一个改进的处理技术:Otsu阈值处理。该方法遍历所有可能的阈值,选择一个最合适的阈值。
OpenCV中,Otsu阈值处理的接口,同样是cv::threshold。
double cv::threshold( InputArray src,
OutputArray dst,
double thresh,
double maxval,
int type
)
与简单阈值处理调用不同的地方是:参数type除了选择5种类型中的一种外,再加上THRESH_OTSU,比如THRESH_BINARY + THRESH_OTSU。其它参数使用一样。
在下边的示例代码中,打开了一幅带有高斯噪声的图像,对该图像使用Otsu阈值处理,核心代码如下:
//Otsu阈值
threshold(src, dst, 0, 255, THRESH_BINARY + THRESH_OTSU);
imshow("otsu-binary", dst);
imwrite("otsu-binary.jpg", dst);
//高斯去噪后使用Otsu阈值处理
GaussianBlur(src, blur, Size(5, 5), 0, 0);
threshold(blur, dst, 0, 255, THRESH_BINARY + THRESH_OTSU);
imshow("gaussblur-binary", dst);
imwrite("gaussblur-binary.jpg", dst);
效果图如下:
以上4幅图像,依次是高斯噪声灰度图像、二值化阈值处理图像、Otsu阈值处理图像、高斯去噪后Otsu阈值处理图像。
5 创建测试项目
创建测试项目、配置开发环境,具体可参考之前文章,这里就不多说了。
Win10+OpenCV4.6.0之开发环境(VS2022)配置入门_来灵的博客-CSDN博客_opencv4.6
这次测试项目名称img_threshold,VS2022种创建好的项目截图:
以下是上边介绍的3种阈值处理总代码,将代码编辑到img_threshold.cpp文件里,代码中有详细注释。
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
//简单阈值处理
int SimpleThresholding(const string& filePath)
{
//打开读取原图
Mat src = imread(filePath.c_str(), IMREAD_GRAYSCALE);
//打开失败
if (src.empty())
{
cout << "打开图片失败" << endl;
return -1;
}
Mat dst;
//显示原图
imshow("src", src);
//二值化处理
threshold(src, dst, 127, 255, THRESH_BINARY);
imshow("binary", dst);
imwrite("binary.jpg", dst);
//反二值化处理
threshold(src, dst, 127, 255, THRESH_BINARY_INV);
imshow("binary-inv", dst);
imwrite("binary-inv.jpg", dst);
//截断阈值处理
threshold(src, dst, 127, 255, THRESH_TRUNC);
imshow("trunc", dst);
imwrite("trunc.jpg", dst);
//低于阈值0处理
threshold(src, dst, 127, 255, THRESH_TOZERO);
imshow("tozero", dst);
imwrite("tozero.jpg", dst);
//超出阈值0处理
threshold(src, dst, 127, 255, THRESH_TOZERO_INV);
imshow("to-zero-inv", dst);
imwrite("to-zero-inv.jpg", dst);
//按下任何键结束程序
waitKey(0);
//关闭所有窗口
destroyAllWindows();
}
//自适应阈值处理
int AdaptiveThresholding(const string& filePath)
{
//打开读取原图
Mat src = imread(filePath.c_str(), IMREAD_GRAYSCALE);
//打开失败
if (src.empty())
{
cout << "打开图片失败" << endl;
return -1;
}
Mat dst;
//显示原图
imshow("src", src);
//二值化处理
threshold(src, dst, 127, 255, THRESH_BINARY);
imshow("binary", dst);
imwrite("binary.jpg", dst);
//自适应阈值
//阈值是邻近区域的平均值减去常数C
adaptiveThreshold(src, dst, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 11, 2);
imshow("mean-binary", dst);
imwrite("mean-binary.jpg", dst);
//自适应阈值
//阈值是邻域值的高斯加权和减去常数C
adaptiveThreshold(src, dst, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 11, 2);
imshow("gauss-binary", dst);
imwrite("gauss-binary.jpg", dst);
//按下任何键结束程序
waitKey(0);
//关闭所有窗口
destroyAllWindows();
}
//Otsu阈值处理
int OtsuThresholding(const string& filePath)
{
//打开读取原图
Mat src = imread(filePath.c_str(), IMREAD_GRAYSCALE);
//打开失败
if (src.empty())
{
cout << "打开图片失败" << endl;
return -1;
}
Mat dst, blur;
//显示原图
imshow("src", src);
//二值化处理
threshold(src, dst, 127, 255, THRESH_BINARY);
imshow("binary", dst);
imwrite("binary.jpg", dst);
//Otsu阈值
threshold(src, dst, 0, 255, THRESH_BINARY + THRESH_OTSU);
imshow("otsu-binary", dst);
imwrite("otsu-binary.jpg", dst);
//高斯去噪后使用Otsu阈值处理
GaussianBlur(src, blur, Size(5, 5), 0, 0);
threshold(blur, dst, 0, 255, THRESH_BINARY + THRESH_OTSU);
imshow("gaussblur-binary", dst);
imwrite("gaussblur-binary.jpg", dst);
//按下任何键结束程序
waitKey(0);
//关闭所有窗口
destroyAllWindows();
}
int main(int argc, char** argv)
{
string filePath("");
cout << "图像阈值处理测试:1 简单阈值处理;2 自适应阈值处理;3 Otsu阈值处理" << endl << endl;
while (true)
{
cout << "请选择1或2或3,开始图像阈值处理" << endl << endl;
int option;
cin >> option;
cout << "请输入原始图片文件" << endl;
switch (option)
{
case 1:
//输入文件名 flower.jpg
cin >> filePath;
SimpleThresholding(filePath);
break;
case 2:
//输入文件名 flower.jpg
cin >> filePath;
AdaptiveThresholding(filePath);
break;
case 3:
//输入文件名 gauss-noise.jpg
cin >> filePath;
OtsuThresholding(filePath);
break;
default:
cout << "无效输入,请重新输入" << endl;
}
}
return 0;
}
测试项目工程当前目录:
6 总结
本节我们一起探索了下使用OpenCV,如果对图像进行阈值处理,其中包括简单的阈值处理、自适应阈值处理和Otsu阈值处理。用到的OpenCV关键函数是threshold和adaptiveThreshold,其调用过程都比较简单。但阈值处理的作用很重要,是很多高级算法的底层逻辑之一。比如在做图形检测,轮廓识别时,常常先会对图像进行阈值处理。
7 参考
OpenCV: Basic Thresholding Operations
OpenCV: Image Thresholding
opencv(4.5.3)-python(十二)--图像阈值处理 - 腾讯云开发者社区-腾讯云