OpenCV:图像阈值操作

目录

 

大津阈值法(OTSU)

 

固定阈值法

 

自适应阈值

 

双阈值法

 

半阈值法


大津阈值法(OTSU)

最大类间方差法(otsu)的公式推导:

记t为前景与背景的分割阈值,前景点数占图像比例为w0,平均灰度为u0;背景点数占图像比例为w1,平均灰度为u1。

则图像的总平均灰度为:u=w0*u0+w1*u1。

前景和背景图象的方差:g=w0*(u0-u)*(u0-u)+w1*(u1-u)*(u1-u)=w0*w1*(u0-u1)*(u0-u1),此公式为方差公式。

查找所有灰度级中的最大类方差,并返回最大类方差所对应的的灰度级,就是前景与背景的分割阈值。

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

int OTSU(cv::Mat srcImage);

const char* WINDOW = "主窗口";

int main()
{
	cv::Mat srcImage, srcGray;
	cv::namedWindow(WINDOW,cv::WINDOW_AUTOSIZE);
	//加载图像
	srcImage = cv::imread("Qt.png",1);
	if (!srcImage.data)
	{
		std::cout << "加载图像失败"<<std::endl;
		return -1;
	}
	resize(srcImage, srcImage,cv::Size(srcImage.cols/3, srcImage.rows/3));
	imshow(WINDOW, srcImage);
	//灰度转换
	cvtColor(srcImage, srcGray, CV_RGB2GRAY);
	cv::imshow("srcGray", srcGray);
	
	//调用OTSU阈值算法得到阈值
	int  ostuThreshold = OTSU(srcGray);
	std::cout << ostuThreshold << std::endl;
	
	// 定义输出结果图像
	cv::Mat otsuResultImage =cv::Mat::zeros(srcGray.rows, srcGray.cols, CV_8UC1);
	
	// 利用得到的阈值实现二值化操作
	for (int i = 0; i < srcGray.rows; i++)
	{
		for (int j = 0; j < srcGray.cols; j++)
		{
			//满足大于阈值ostuThreshold置255
			if (srcGray.at<uchar>(i, j) > ostuThreshold)
			{
				otsuResultImage.at<uchar>(i, j) = 255;
			}
			else
			{
				otsuResultImage.at<uchar>(i, j) = 0;
			}
		}
	}
	imshow("otsuResultImage", otsuResultImage);
	cv::waitKey(0);
	cv::destroyAllWindows();
	return 0;
}

/*
@ 大津阈值法函数实现
@ srcImage:输入图像,灰度图
@ return int 阈值
*/
int OTSU(cv::Mat srcImage)
{
	int nCols = srcImage.cols;
	int nRows = srcImage.rows;
	int threshold = 0;
	// 初始化统计参数
	int nSumPix[256];
	float nProDis[256];
	for (int i = 0; i < 256; i++)
	{
		nSumPix[i] = 0;
		nProDis[i] = 0;
	}
	//统计灰度级中每个像素在整幅图像中的个数 
	for (int i = 0; i < nRows; i++)
	{
		for (int j = 0; j < nCols; j++)
		{
			nSumPix[(int)srcImage.at<uchar>(i, j)]++;
		}
	}

	//计算每个灰度级占图像中的概率分布,平均数
	for (int i = 0; i < 256; i++)
	{
		nProDis[i] = (float)nSumPix[i] / (nCols * nRows);
	}

	// 遍历灰度级[0,255],计算出最大类间方差下的阈值  
	float w0, w1, u0_temp, u1_temp, u0, u1, delta_temp;
	double delta_max = 0.0;

	for (int i = 0; i < 256; i++)
	{
		// 初始化相关参数
		w0 = w1 = u0_temp = u1_temp = u0 = u1 = delta_temp = 0;
		for (int j = 0; j < 256; j++)
		{
			//背景部分 
			if (j <= i)
			{
				//当前i为分割阈值,第一类总的概率  
				w0 += nProDis[j];
				u0_temp += j * nProDis[j];
			}
			//前景部分   
			else
			{
				// 当前i为分割阈值,第一类总的概率
				w1 += nProDis[j];
				u1_temp += j * nProDis[j];
			}
		}
		// 分别计算各类的平均灰度 
		u0 = u0_temp / w0;
		u1 = u1_temp / w1;
		delta_temp = (float)(w0 *w1* pow((u0 - u1), 2));
		// 依次找到最大类间方差下的阈值    
		if (delta_temp > delta_max)
		{
			delta_max = delta_temp;
			threshold = i;
		}
	}
	return threshold;
}

 

固定阈值法

    图像二值化其实就是阈值操作,先设定一个阈值,然后将灰度图的单通道像素值与固定阈值进行比较,根据比较的结果(大于或者小于),划分为两种的不同的颜色或者灰度,对于二值图像可以更好更好进行边缘检测,然后寻找轮廓。

流程图

固定阈值操作:Threshold()函数

    函数Threshold()对单通道数组应用固定阈值操作,阈值可以设定为初始值或者加上一个轨迹条进行控制。该函数的典型应用是对灰度图像进行阈值操作得到二值图像,中间可以进行一下滤波操作,滤除掉很小或者很大像素值的图像点。(注意标红的地方)

函数原型

double threshold( InputArray src, OutputArray dst,
                               double thresh, double maxval, int type );

src:输入图像或输入数组,单通道灰度图,8或32位浮点类型的Mat。

dst:输出图像或输出数组,经过阈值操作后得到的二值图。

thresh:设定的阈值。

maxval:最大范围值,一般为255,根据最后一个参数type的类型来确定。

type:阈值类型,通常使用的有5种类型,分别是CV_THRESH_BINARYCV_THRESH_BINARY_INVCV_THRESH_TRUNCCV_THRESH_TOZEROCV_THRESH_TOZERO_INV,这5中类型可以分别用数字0、1、2、3、4来代替使用,不要超过4,不然会报错(还有一个大津阈值法是8)。

5种类型分别对应的阈值操作方法:

CV_THRESH_BINARY(可以用标识符0代替,意思是大于阈值为maxval(白),否则为0(黑))

CV_THRESH_BINARY_INV(可以用标识符1代替,意思是大于阈值为0(黑),否则为maxval(白))

CV_THRESH_TRUNC(可以用标识符2代替,意思是大于阈值为threshold(阈值),否则为src(x,y)(本色或原色))

CV_THRESH_TOZERO(可以用标识符3代替,意思是大于阈值为src(x,y)(本色或原色),否则为0(黑))

CV_THRESH_TOZERO_INV(可以用标识符4代替,意思是大于阈值为0(黑),否则为src(x,y)(本色或原色))

代码示例:

/************************************************************************
<*@创建者:OYXL
<*@时间:2018/6/20
<*@实现功能:图片或者视频进行二值化,可选择
<*@备注:图片二值化,显示图像必须和二值响应函数放在一块,不然不会执行
<*@备注:on_Threshold( int, void* );不可更改参数
************************************************************************/
#include "opencv2/opencv.hpp"
#include "iostream"

using namespace std;
using namespace cv;

int Picture();
int Video();
void on_Threshold( int, void* );//回调函数
void ChooseMode();

Mat img,gray,binary;
int thr=100;
int num=0;
int BinaryType=2;

int main()
{
	ChooseMode();
	if (num==1)
	{
		Picture();
	}
	else if (num==2)
	{
		Video();
	}
	return 0;
}
//<图片二值化函数
int Picture()
{
	namedWindow("TrackBar", CV_WINDOW_AUTOSIZE); //create a window called "Control"
	createTrackbar("THR", "TrackBar", &thr, 255,on_Threshold); //Hue (0 - 180)
	img=imread("F:\\磊神图片\\9527.png");
	if (!img.data)
	{
		cout << "picture failed to load !" << endl;
		return -1;
	}
	cvtColor(img, gray, CV_BGR2GRAY);//<必须先进行二值化操作,不然得到的二值图是彩色的
	GaussianBlur(gray, gray, Size(15, 15), 0, 0);
	on_Threshold(0,0);
	//threshold(gray, binary, thr, 255, 4);
	imshow("src", img);
	waitKey(0);
}
//<视频二值化函数
int Video()
{
	namedWindow("TrackBar", CV_WINDOW_AUTOSIZE); //create a window called "Control"
	createTrackbar("THR", "TrackBar", &thr, 255,0); //Hue (0 - 180)
	VideoCapture capture("F:\\磊神视频\\noglasses.avi");
	while (true)
	{
		capture >> img;
		if (img.empty())	//判断摄像头是否打开  
		{
			cout << "Video failed to load !" << endl;
			return -1;
		}
		cvtColor(img, gray, CV_BGR2GRAY);
		GaussianBlur(gray, gray, Size(15, 15), 0, 0);
		threshold(gray, binary, thr, 255, BinaryType);
		imshow("src", img);
		imshow("binary image", binary);
		waitKey(1);
	}
}
/************************************************************************
<*@阈值回调函数
<*@显示二值图片需要放在这里,响应一次,显示一次
************************************************************************/
void on_Threshold( int, void* )
{
	//<调用阈值函数
	threshold(gray,binary,thr,255,BinaryType);
	imshow("binary image", binary);
}
void ChooseMode()
{
	cout<<"Please enter the type you want to select:"<<endl;
	cout<<"0、THRESH_BINARY"<<endl;			//<大于阈值为白,小于为黑
	cout<<"1、THRESH_BINARY_INV"<<endl;		//<大于阈值为黑,小于为白
	cout<<"2、THRESH_TRUNC"<<endl;			//<大于阈值为阈值,小于为原值
	cout<<"3、THRESH_TOZERO"<<endl;			//<大于阈值为原值,小于为黑
	cout<<"4、THRESH_TOZERO_INV"<<endl;		//<小于阈值为原值,于为黑
	cin>>BinaryType;
	cout<<"Please enter the mode you want to select:"<<endl;
	cout<<"1、Picture Mode"<<endl;
	cout<<"2、Video Mode"<<endl;
	cin>>num;
}

代码分析:

    示例代码中可以分别对图片和视频进行阈值操作,不过有些地方需要注意,对于图片来说,需要添加轨迹条响应函数(跟鼠标响应函数一样),而视频则不需要,可以直接使用Threshold()函数,为了适应轨迹条的阈值,需要将显示二值图放在轨迹条响应函数里面,每滑动一次轨迹条,就显示一次图片,轨迹条响应函数参数不可更改。另外,注意图片和视频的路径是否正确。

效果图:

 

自适应阈值

adaptiveThreshold函数:通过计算每个像素位置周围的b*b区域的加权平均值然后减去常数C得到阈值,b为blockSize。相对于一般的阈值化操作,当图像中出现较大的明暗差异时,自适应阈值法非常有效。

void adaptiveThreshold( InputArray src, OutputArray dst,
                                     double maxValue, int adaptiveMethod,
                                     int thresholdType, int blockSize, double C );
src8位单通道图像。
dst与src具有相同大小和相同类型的目标图像
maxValue非0的最大像素值
adaptiveMethod要使用的自适应阈值算法,0:ADAPTIVE_THRESH_MEAN_C,1: ADAPTIVE_THRESH_GAUSSIAN_C
thresholdType阈值类型必须是THRESH_BINARYTHRESH_BINARY_INV
blockSize用于计算像素阈值的像素邻域的大小:3,5,7等。
C从平均值或加权平均值中减去常数。通常情况下,它是正数,但也可能为零或负数。
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>

using namespace std;
using namespace cv;

int main()
{
	Mat srcImage, srcGray;
	// 图像读取及判断
	srcImage = cv::imread("Qt.png");
	if (!srcImage.data)
	{
		cout << "-------- 加载图像失败 ---------"<<endl;
		return -1;
	}
	resize(srcImage, srcImage,Size(srcImage.cols/3, srcImage.rows/3));
	cv::imshow("主窗口", srcImage);
		
	//灰度转换
	cvtColor(srcImage, srcGray, CV_RGB2GRAY);
	imshow("srcGray", srcGray);
	
	Mat dstImage;
	//初始化自适应阈值参数
	int blockSize = 5;
	int constValue = 10;
	const int maxVal = 255;
	//初始化自适应方法和阈值类型
	int adaptiveMethod = 0;
	int thresholdType = 1;
	// 图像自适应阈值操作
	adaptiveThreshold(srcGray, dstImage, maxVal, adaptiveMethod, thresholdType, blockSize,constValue);
	imshow("dstImage", dstImage);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

 

双阈值法

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
	Mat srcImage;
	//图像读取及判断
	srcImage = cv::imread("Qt.png");
	if (!srcImage.data)
	{
		cout << "---------- 加载图像失败 ----------" << endl;
		return -1;
	}
	resize(srcImage, srcImage,Size(srcImage.cols/3, srcImage.rows / 3));
	imshow("主窗口", srcImage);
	//灰度转换
	Mat srcGray;
	cvtColor(srcImage, srcGray, CV_RGB2GRAY);
	imshow("srcGray", srcGray);
	//初始化阈值参数
	const int maxVal = 255;
	int low_threshold = 150;
	int high_threshold = 210;
	Mat dstTempImage1, dstTempImage2, dstImage;
	//小阈值对源灰度图像进行阈值化操作
	threshold(srcGray, dstTempImage1,low_threshold, maxVal, cv::THRESH_BINARY);
	//大阈值对源灰度图像进行阈值化操作
	threshold(srcGray, dstTempImage2,high_threshold, maxVal, cv::THRESH_BINARY_INV);
	//矩阵与运算得到二值化结果
	bitwise_and(dstTempImage1,dstTempImage2, dstImage);
	imshow("dstImage", dstImage);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

 

半阈值法

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream> 

using namespace std;
using namespace cv;

int main()
{
	// 读取源图像及判断
	Mat srcImage = imread("Qt.png");
	if (!srcImage.data)
	{
		cout << "" << endl;
		return -1;
	}
	resize(srcImage, srcImage,Size(srcImage.cols/3, srcImage.rows/3));
	imshow("主窗口", srcImage);
	// 转化为灰度图像
	Mat srcGray;
	cvtColor(srcImage, srcGray, CV_RGB2GRAY);
	imshow("srcGray", srcGray);
	// 初始化阈值参数
	const int maxVal = 255;
	int thresholdVal = 200;
	Mat dstTempImage, dstImage;
	// 阈值对源灰度图像进行阈值化操作
	threshold(srcGray, dstTempImage,thresholdVal, 255, THRESH_BINARY);
	// 矩阵与运算得到二值化结果
	bitwise_and(srcGray, dstTempImage, dstImage);
	imshow("dstImage", dstImage);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值