C++调用OpenCV实现图像阈值处理

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_BINARYTHRESH_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(十二)--图像阈值处理 - 腾讯云开发者社区-腾讯云

猜你喜欢

转载自blog.csdn.net/chexlong/article/details/128262197