otsu结合OpenCV实现灰度图像自动阈值处理

Posted 晴堂

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了otsu结合OpenCV实现灰度图像自动阈值处理相关的知识,希望对你有一定的参考价值。

        简单的说,这种算法假设一副图像由前景色和背景色组成,通过统计学的方法来选取一个阈值,使得这个阈值可以将前景色和背景色尽可能的分开。
或者更准确的说是在某种判据下最优。与数理统计领域的 fisher 线性判别算法其实是等价的。
otsu算法中这个判据就是最大类间方差 (intra-class variance or the variance within the class)。下面就来详细说说什么是 intra-class variance。
我们知道一副灰度图像,可以计算它的颜色平均值,或者更进一步。可以计算出灰度直方图。
这里给出一个示例图片:

这个图片拍摄的是一个条形码。在这个图中,前景色就是黑色的条形码,背景色是其余部分的灰色。那么我们可以计算出这个图像的灰度直方图。

图中那个大的峰是背景色的部分,小的峰是前景色。灰度值的均值是 122. 我们称这个均值为 M。
现在任意选取一个灰度值 t,则可以将这个直方图分成前后两部分。我们称这两部分分别为 A 和 B。对应的就是前景色和背景色。这两部分各自的平均值成为 MA 和 MB。 
A 部分里的像素数占总像素数的比例记作 PA,B部分里的像素数占总像素数的比例记作 PB。 
Nobuyuki Otsu 给出的类间方差定义为:

那么这个最佳的阈值t 就是使得 ICV 最大的那个值。 
对于上面的测试图像,我们可以遍历 t 的各种取值,计算 ICV。之后可以画出这样的ICV 曲线(绿色线条):

可以看出,ICV 取最值的点确实将前景色和背景色分开了。

以上理论部分参考了这篇博客http://blog.csdn.net/liyuanbhu/article/details/49387483

下面是C++结合OpenCV2.x实现的代码:

int otsu(Mat image)

	int width = image.cols;
	int height = image.rows;
	int x = 0, y = 0;
	int pixelCount[256];
	float pixelPro[256];
	int i, j, pixelSum = width * height, threshold = 0;

	uchar* data = (uchar*)image.data;

	//初始化  
	for (i = 0; i < 256; i++)
	
		pixelCount[i] = 0;
		pixelPro[i] = 0;
	

	//统计灰度级中每个像素在整幅图像中的个数  
	for (i = y; i < height; i++)
	
		for (j = x; j<width;i++)
		
			pixelCount[data[i * image.step+ j]]++;
		
	


	//计算每个像素在整幅图像中的比例  
	for (i = 0; i < 256; i++)
	
		pixelPro[i] = (float)(pixelCount[i]) / (float)(pixelSum);
	

	//经典ostu算法,得到前景和背景的分割  
	//遍历灰度级[0,255],计算出方差最大的灰度值,为最佳阈值  
	float w0, w1, u0tmp, u1tmp, u0, u1, u, deltaTmp, deltaMax = 0;
	for (i = 0; i < 256; i++)
	
		w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;

		for (j = 0; j < 256; j++)
		
			if (j <= i) //背景部分  
			
				//以i为阈值分类,第一类总的概率  
				w0 += pixelPro[j];
				u0tmp += j * pixelPro[j];
			
			else       //前景部分  
			
				//以i为阈值分类,第二类总的概率  
				w1 += pixelPro[j];
				u1tmp += j * pixelPro[j];
			
		

		u0 = u0tmp / w0;        //第一类的平均灰度  
		u1 = u1tmp / w1;        //第二类的平均灰度  
		u = u0tmp + u1tmp;      //整幅图像的平均灰度  
		//计算类间方差  
		deltaTmp = w0 * (u0 - u)*(u0 - u) + w1 * (u1 - u)*(u1 - u);
		//找出最大类间方差以及对应的阈值  
		if (deltaTmp > deltaMax)
		
			deltaMax = deltaTmp;
			threshold = i;
		
	
	//返回最佳阈值;  
	return threshold;

利用这个方法计算出的阈值做了二值化后得到图像如下:


可以看到效果很好。
Otsu 方法也不是万能的。当目标与背景的大小比例悬殊时,类间方差准则函数可能呈现双峰或多峰,此时效果不好。这时就要考虑其他的办法了。
其实,我们可以仔细观察 ICV 的计算公式。 


这里面 PA 和 PB 相当于是个前景色和背景色部分做个加权。当前景色或背景色有一个区域很小时。比如 PA 非常的小。那么这时计算出来的 t 就会和 B 区域很接近,这时的分割效果就会比较差。我们可以对ICV的公式进行一点小小的改造。


这里的 α 可以取一个 0-1之间的值。比如上面的例子图片,如果我们取 α=0.8 计算出的效果会更好一些。当然这个 α 值就要全凭经验来定了


以上是关于otsu结合OpenCV实现灰度图像自动阈值处理的主要内容,如果未能解决你的问题,请参考以下文章

利用OpenCV的函数threshold()对图像作基于OTSU的阈值化处理---并附比较好的介绍OTSU原理的博文链接

OpenCV计算机视觉 —— 图像的阈值处理与自适应阈值Otsu

opencv-阈值分割

灰度图像的自动阈值分割(Otsu 法)(转载)

OpenCV实战 | 一文剖析图像阈值化方法——adaptiveThreshold thresholdTHRESH_OTSU

OpenCV实战 | 一文剖析图像阈值化方法——adaptiveThreshold thresholdTHRESH_OTSU