图像处理——常用阈值分割方法及源码

目录

 

1、Otsu阈值分割

2、自适应阈值分割

3、 最大熵阈值分割法

4、 迭代阈值分割

 5、测验

 

1、Otsu阈值分割

        Otsu(大津法或最大类间方差法)使用的是聚类的思想,把图像的灰度数按灰度级分成2个部分,使得两个部分之间的灰度值差异最大,每个部分之间的灰度差异最小,通过方差的计算来寻找一个合适的灰度级别来划分。 所以可以在二值化的时候采用otsu算法来自动选取阈值进行二值化。otsu算法被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响。因此,使类间方差最大的分割意味着错分概率最小。

参考链接:https://www.cnblogs.com/moon1992/p/5092726.html

opencv调用格式为:threshold(sourceImage, dstImage, 0, 255, CV_THRESH_OTSU);

sourceImage:输入图像;

dstImage:输出图像;

otsu算法的源码如下:

Mat OtsuAlgThreshold(Mat &image)
{
	if (image.channels() != 1)
	{
		cout << "Please input Gray-image!" << endl;
	}
	int T = 0; //Otsu算法阈值  
	double varValue = 0; //类间方差中间值保存
	double w0 = 0; //前景像素点数所占比例  
	double w1 = 0; //背景像素点数所占比例  
	double u0 = 0; //前景平均灰度  
	double u1 = 0; //背景平均灰度  
	double Histogram[256] = { 0 }; //灰度直方图,下标是灰度值,保存内容是灰度值对应的像素点总数  
	uchar *data = image.data;

	double totalNum = image.rows*image.cols; //像素总数

	for (int i = 0; i < image.rows; i++)
	{
		for (int j = 0; j < image.cols; j++)
		{
			if (image.at(i, j) != 0) Histogram[data[i*image.step + j]]++;
		}
	}
	int minpos, maxpos;
	for (int i = 0; i < 255; i++)
	{
		if (Histogram[i] != 0)
		{
			minpos = i;
			break;
		}
	}
	for (int i = 255; i > 0; i--)
	{
		if (Histogram[i] != 0)
		{
			maxpos = i;
			break;
		}
	}

	for (int i = minpos; i <= maxpos; i++)
	{
		//每次遍历之前初始化各变量  
		w1 = 0;       u1 = 0;       w0 = 0;       u0 = 0;
		//***********背景各分量值计算**************************  
		for (int j = 0; j <= i; j++) //背景部分各值计算  
		{
			w1 += Histogram[j];   //背景部分像素点总数  
			u1 += j*Histogram[j]; //背景部分像素总灰度和  
		}
		if (w1 == 0) //背景部分像素点数为0时退出  
		{
			break;
		}
		u1 = u1 / w1; //背景像素平均灰度  
		w1 = w1 / totalNum; // 背景部分像素点数所占比例
		//***********背景各分量值计算**************************  

	    //***********前景各分量值计算**************************  
		for (int k = i + 1; k < 255; k++)
		{
			w0 += Histogram[k];  //前景部分像素点总数  
			u0 += k*Histogram[k]; //前景部分像素总灰度和  
		}
		if (w0 == 0) //前景部分像素点数为0时退出  
		{
			break;
		}
		u0 = u0 / w0; //前景像素平均灰度  
		w0 = w0 / totalNum; // 前景部分像素点数所占比例  
		//***********前景各分量值计算**************************  

		//***********类间方差计算******************************  
		double varValueI = w0*w1*(u1 - u0)*(u1 - u0); //当前类间方差计算  
		if (varValue < varValueI)
		{
			varValue = varValueI;
			T = i;
		}
	}
	Mat dst;
	threshold(image, dst, T, 255, CV_THRESH_OTSU);
	return dst;
}

2、自适应阈值分割

这一部分讲解的是opencv自带的adaptiveThreshold()函数,其算法流程:

         二值化算法是用输入像素的值I与一个值C来比较,根据比较结果确定输出值。

        自适应二值化的每一个像素的比较值C都不同,比较值C由这个像素为中心的一个块范围计算在减去差值delta得到。

        其中,C的常用计算方法有两种:

         a、平均值减去差值delta(使用盒过滤boxfilter,性能会非常不错)

         b、高斯分布加权和减去差值delta (使用高斯滤波GaussionBlur)

adaptiveThreshold()源码如下:

void myadaptive(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_GAUSSIAN_C)
	{
		GaussianBlur(src, mean, Size(blockSize, blockSize), 0, 0, BORDER_REPLICATE);
	}
	else if (method == ADAPTIVE_THRESH_MEAN_C)
	{
		boxFilter(src, mean, src.type(), Size(blockSize, blockSize),
			Point(-1, -1), true, BORDER_REPLICATE);
	}
	else
	{
		CV_Error(CV_StsBadFlag, "Unknown/unsupported adaptive threshold method");
	}

	int i, j;
	uchar imaxval = saturate_cast(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.data + src.step*i;
		const uchar* mdata = mean.data + mean.step*i;
		uchar* ddata = dst.data + dst.step*i;

		for (j = 0; j < size.width; j++)
			// 将[-255, 255] 映射到[0, 510]然后查表
			ddata[j] = tab[sdata[j] - mdata[j] + 255];
	}
}

3、 最大熵阈值分割法

参考链接:https://blog.csdn.net/qq_27668313/article/details/77949596

https://blog.csdn.net/robin__chou/article/details/53931442

https://blog.csdn.net/xw20084898/article/details/22760169

最大熵阈值分割函数源码如下:

Mat EntropySeg(Mat src)
{
	int tbHist[256] = { 0 };
	int index = 0;
	double Property = 0.0;
	double maxEntropy = -1.0;
	double frontEntropy = 0.0;
	double backEntropy = 0.0;
	int TotalPixel = 0;
	int nCol = src.cols*src.channels();
	for (int i = 0; i < src.rows; i++)
	{
		uchar* pData = src.ptr(i);
		for (int j = 0; j < nCol; j++)
		{
			++TotalPixel;
			tbHist[pData[j]] += 1;
		}
	}

	for (int i = 0; i < 256; i++)
	{
		double backTotal = 0;
		for (int j = 0; j < i; j++)
		{
			backTotal += tbHist[j];
		}

		for (int j = 0; j < i; j++)
		{
			if (tbHist[j] != 0)
			{
				Property = tbHist[j] / backTotal;
				backEntropy += -Property*logf((float)Property);
			}
		}

		for (int k = i; k < 256; k++)
		{
			if (tbHist[k] != 0)
			{
				Property = tbHist[k] / (TotalPixel - backTotal);
				frontEntropy += -Property * logf((float)Property);
			}
		}

		if (frontEntropy + backEntropy > maxEntropy) 
		{
			maxEntropy = frontEntropy + backEntropy;
			index = i;
		}

		frontEntropy = 0.0;
		backEntropy = 0.0;
	}

	Mat dst;
	threshold(src, dst, index, 255, 0);
	return dst;
}

4、 迭代阈值分割

通过迭代方法选择阈值, 计算方法如下:

(1)选择灰度图的平均值作为初始阈值T0 ;

(2)计算小于等于T0的平均值T1, 和大于T0的平均值T2;

(3)新的阈值为T = (T1 + T2)/ 2;

(4)比较T和T0,若相等,则返回T,即为迭代阈值; 否则 T0 = T,重复(1)-(3)

迭代阈值分割的源码如下:

Mat IterationThreshold(Mat src)
{
	int width = src.cols;
	int height = src.rows;
	int hisData[256] = { 0 };
	for (int j = 0; j < height; j++)
	{
		uchar* data = src.ptr(j);
		for (int i = 0; i < width; i++)
			hisData[data[i]]++;
	}

	int T0 = 0;
	for (int i = 0; i < 256; i++)
	{
		T0 += i*hisData[i];
	}
	T0 /= width*height;

	int T1 = 0, T2 = 0;
	int num1 = 0, num2 = 0;
	int T = 0;
	while (1)
	{
		for (int i = 0; i < T0 + 1; i++)
		{
			T1 += i*hisData[i];
			num1 += hisData[i];
		}
		if (num1 == 0)
			continue;
		for (int i = T0 + 1; i < 256; i++)
		{
			T2 += i*hisData[i];
			num2 += hisData[i];
		}
		if (num2 == 0)
			continue;

		T = (T1 / num1 + T2 / num2) / 2;

		if (T == T0)
			break;
		else
			T0 = T;
	}

	Mat dst;
	threshold(src, dst, T, 255, 0);
	return dst;
}

 5、测验

void main()
{
	Mat src = imread("1.jpg");
	cvtColor(src, src, COLOR_RGB2GRAY);

	Mat bw1, bw2, bw3, bw4;
	myadaptive(src, bw1, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, 15, 10);
	bw2 = EntropySeg(src);
	bw3 = OtsuAlgThreshold(src);
	bw4 = IterationThreshold(src);

	imshow("source", src);
	imshow("自适应阈值分割", bw1);
	imshow("最大熵阈值分割", bw2);
	imshow("Otsu阈值分割", bw3);
	imshow("迭代阈值分割", bw4);
	waitKey(0);
}

测验结果:

图像处理——常用阈值分割方法及源码_第1张图片

图像处理——常用阈值分割方法及源码_第2张图片

 

图像处理——常用阈值分割方法及源码_第3张图片

 

图像处理——常用阈值分割方法及源码_第4张图片

图像处理——常用阈值分割方法及源码_第5张图片

 

你可能感兴趣的:(opencv)