Python-Pillow-OTSU算法

OTSU算法介绍

大津法(OTSU),由日本学者大津于1979年提出。
确定图像二值化分割的阈值,将一个灰度图像退化为二值图像。

算法假定该图像根据双模直方图(前景像素和背景像素)把包含两类像素,于是它要计算能将两类分开的最佳阈值,使得它们的类内方差最小;由于两两平方距离恒定,所以即它们的类间方差最大。

按图像的灰度特性,将图像分成背景和前景两部分。
因方差是灰度分布均匀性的一种度量,背景和前景之间的类间方差越大,说明构成图像的两部分的差别越大.

对于图像Image,图像的大小为M×N
前景(即目标)和背景的分割阈值记作T
属于前景的像素点数占整幅图像的比例为w0,其平均灰度u0
属于背景的像素点数占整幅图像的比例为w1,其平均灰度u1
图像中像素的灰度值小于阈值T的像素个数记作n0
图像中像素的灰度值大于阈值T的像素个数记作n1
图像的总平均灰度记为u,类间方差记为g。

–算法公式推导步骤:

(1) w0=n0/ M×N #前景像素比例
(2) w1=n1/ M×N #背景像素比例
(3) n0+n1=M×N #总像素个数
(4) w0+w1=1 #总比例
(5) u=w0 * u0 + w1 * u1 #图像总平均灰度值
(6) g=w0 * (u0-u)2+w1*(u1-u)2 #类间方差
将式(5)代入式(6),得到等价公式:
(7)g=w0 * w1 * (u0-u1)^2   #类间方差

找出最佳阈值的方式:采用遍历测试
将0~255这256个阈值都试用一遍,找到使类间方差值g最大的,即是最佳阈值T。


opencv实现代码参考

int CLxrImage::Otsu(IplImage* src)  
{  
	int height=src->height;  
	int width=src->width;      
 
	//统计直方图  
	int   nHistogram[256] = {0};
	float fHistogram[256] = {0};  
 
	for(int i=0; i < height; i++)
	{  
		for(int j = 0; j < width; j++) 
		{  
			nHistogram[(unsigned char)src->imageData[i*src->widthStep+j]]++;  
		}  
	}
 
	//归一化直方图 
	int size = height * width;  
	for(int i = 0; i < 256; i++)
	{  
		fHistogram[i] = nHistogram[i] / (float)size;  
	}  
 
	//average pixel value  
	float avgValue=0;  
	for(int i=0; i < 256; i++)
	{  
		avgValue += i * fHistogram[i];  //整幅图像的平均灰度
	}   
 
	int threshold;    
	float maxVariance=0;  
	float w = 0, u = 0;  
	for(int i = 0; i < 256; i++) 
	{  
		w += fHistogram[i];			//假设当前灰度i为阈值, 0~i 灰度的像素(假设像素值在此范围的像素叫做前景像素) 所占整幅图像的比例
		u += i * fHistogram[i];     // 灰度i 之前的像素(0~i)的平均灰度值: 前景像素的平均灰度值
 
		float t = avgValue * w - u;  
		float variance = t * t / (w * (1 - w) );  
		if(variance > maxVariance) 
		{  
			maxVariance = variance;  
			threshold = i;  
		}  
	}  
	return threshold;  
}
int CLxrImage::Otsu2(IplImage* src)
{
	int i,j,nThresh;
	int nHistogram[256] = {0};
	double fStdHistogram[256] = {0.0};
	double fGrayAccu[256] = {0.0};
	double fGrayAve[256] = {0.0};
	double fAverage = 0;
	double fTemp = 0;
	double fMax = 0;
	//统计直方图
	for(i = 0; i height; i++)
	{
		for(j = 0; j width; j++)
		{
			nHistogram[(unsigned char)src->imageData[i*src->width+j]] ++;
		}
	}
	//归一化直方图
	for(i = 0; i <= 255;i++)
	{
		fStdHistogram[i] = nHistogram[i]/(double)(src->width * src->height);
	}
	for(i=0;i<=255;i++)
	{
		for(j=0;j<=i;j++)
		{
			fGrayAccu[i] += fStdHistogram[j];//所有灰度级,关于w0的数组						
			fGrayAve[i] += j*fStdHistogram[j];//所有灰度级,关于u(t)的数组
		}
		fAverage += i*fStdHistogram[i];	
	}
 
	//计算OSTU
	for(i=0;i<=255;i++)
	{
		fTemp=(fAverage*fGrayAccu[i]-fGrayAve[i])*(fAverage*fGrayAccu[i]-fGrayAve[i])/(fGrayAccu[i]*(1-fGrayAccu[i]));
		if(fTemp>fMax)
		{
			fMax=fTemp;
			nThresh=i;
		}
	}
	return nThresh;
}


Pytho+Pillow 实现OTSU

Pillow-图像灰度化
from PIL import Image
#图像二值化
#黑白化处理,自定义灰度界限,大于这个阀值为黑色,小于这个值为白色	

	pixeldata = img.load()
	width,height = img.size
	
	for x in range(width):
		for y in range(height):
			if pixeldata[x,y] < threshold:
				pixeldata[x,y] = 0
			else:
				pixeldata[x,y] = 255
–OTSU实现
max_g = 0.0
best_threshold = 0.0
for threshold in range(256):
	n0 = pixel_counts[:threshold].sum() #阈值以下像素总数(前景)
	n1 = pixel_counts[threshold:].sum() #阈值以上像素总数(背景)

	w0 = n0/total_pixel #阈值以下像素数量占的比例(前景)
	w1 = n1/total_pixel #阈值以上像素数量占的比例(背景)

	#阈值以下平均灰度(前景)
	u0 = 0.0
	for i in range(threshold):
		u0 += i*pixel_counts[i]

	#阈值以上平均灰度(背景)
	u1 = 0.0
	for i in range(threshold,256):
		u1 += i*pixel_counts[i]
	
	#u = u0 * w0 + u1 * w1
	g = w0 * w1 * np.power((u0-u1),2)

	if g > max_g:
		best_threshold = threshold
		max_g = g

Matplotlib-图像总像素统计图
#图像的灰度直方图
def show_img_hist(pixel_counts):
    plt.xlabel('Image_pixels')
    plt.ylabel('Counts')
    plt.title('OTSU')
    
    X = np.linspace(0,255,256)
    plt.bar(X,pixel_counts,color='g')

    plt.show()

你可能感兴趣的:(Python,-,Pillow)