OTSU算法原理简述:

最大类间方差是由日本学者大津(Nobuyuki Otsu)于1979年提出,是一种自适应的阈值确定方法。算法假设图像像素能够根据阈值,被分成背景[background]和目标[objects]两部分。然后,计算该最佳阈值来区分这两类像素,使得两类像素区分度最大。

公式:  记 M = 256 单通道灰度分级 Sum = 像素总数

  1. 背景像素占比 \omega1 = \frac{N1}{Sum}  
  2. 前景像素占比\omega2 = 1- \omega1 = \frac{N2}{Sum} =1- \frac{N1}{Sum}
  3. 背景的平均灰度值\mu 1 = \sum_{i = 0}^{t} i *Pr(i | C_{0}) = \sum_{i = 0}^{t} i *Pi / \sum_{i = 0}^{t} Pi = \frac{\mu(t))}{\omega_{1}}
  4. 前景的平均灰度值\mu 2 = \sum_{i = t+1}^{M - 1} i *Pr(i | C_{1}) = \sum_{i = t+1}^{M - 1} i *Pi / \sum_{i = t+1}^{M - 1} Pi = \frac{\mu - \mu(t))}{\omega _{2}}
  5. 0~M灰度区间的灰度累计值\mu = \mu1*\omega 1 + \mu2*\omega 2
  6. 类间方差:g = \omega 1 * (\mu - \mu1)^{2} + \omega 2 * (\mu - \mu2)^{2}
  7. 将公式3.4.5带入公式6 可得最终简化公式: g = \omega 1 * \omega2 * (\mu1 - \mu2)^{2}

代码:

#include <opencv2\opencv.hpp>
#include <iostream>
#include <time.h>
using namespace std;
using namespace cv;
 
int myOtsu(Mat & src)
{
	int th;
	const int GrayScale = 256;	//单通道图像总灰度256级
	int pixCount[GrayScale] = {0};//每个灰度值所占像素个数
	int pixSum = src.cols * src.rows;//图像总像素点
	float pixPro[GrayScale] = {0};//每个灰度值所占总像素比例
	float w0, w1, u0tmp, u1tmp, u0, u1, deltaTmp, deltaMax = 0; 
 
	for(int i = 0; i < src.cols; i++)
	{
		for(int j = 0; j < src.rows; j++)
		{
			pixCount[src.at<uchar>(j,i)]++;//统计每个灰度级中像素的个数  
		}
	}
 
	for(int i = 0; i < GrayScale; i++)
	{
		pixPro[i] = pixCount[i] * 1.0 / pixSum;//计算每个灰度级的像素数目占整幅图像的比例  
	}
 
	for(int i = 0; i < GrayScale; i++)//遍历所有从0到255灰度级的阈值分割条件,测试哪一个的类间方差最大
	{
		w0 = w1 = u0tmp = u1tmp = u0 = u1 = deltaTmp = 0;  
		for(int j = 0; j < GrayScale; j++)
		{
			if(j <= i)//背景
			{
				w0 += pixPro[j];
				u0tmp += j * pixPro[j]; 
			}
			else//前景
			{
				w1 += pixPro[j];
				u1tmp += j * pixPro[j];
			}
		}
		u0 = u0tmp / w0;
		u1 = u1tmp / w1;
		deltaTmp = (float)(w0 *w1* pow((u0 - u1), 2)); //类间方差公式 g = w1 * w2 * (u1 - u2) ^ 2
		if(deltaTmp > deltaMax) 
		{
			deltaMax = deltaTmp;
			th = i;  
		}  
	}
	return th;
}
 
int main()
{
	Mat src = imread("lena.jpg",IMREAD_GRAYSCALE);//单通道读取图像
	/*my_dst: 自己实现的大津法 得到的处理图像
	otsu_dst:opencv自带的大津法 得到的处理图像
	sub:两个处理图像相差图
	*/
	Mat my_dst, otsu_dst, sub;							
	/*my_th: 自己实现的大津法 得到的最大类件方差 即阈值
	th:opencv自带的大津法 得到的最大类件方差 即阈值
	*/
	int my_th, th;
 
	/*计算开销时间,对比两个算法效率*/
	long my_start = clock();  //开始时间
	{
		my_th = myOtsu(src);
		threshold(src,my_dst,my_th,255,CV_THRESH_BINARY);
	}
	long my_finish = clock();   //结束时间
	long my_t = my_finish-my_start;
	printf("The run time is:%9.3lf\n", my_t, "ms!\n"); //输出时间
	cout << "myOtsu threshold >> " << my_th << endl;
 
	long otsu_start = clock();  //开始时间
	{
		th = threshold(src,otsu_dst,0,255,CV_THRESH_OTSU);
	}
	long otsu_finish = clock();   //结束时间
	long t = my_finish-my_start;
	printf("The run time is:%9.3lf\n",  (double) t / CLOCKS_PER_SEC, "ms!\n"); //输出时间
	cout << "Otsu threshold >> " << th << endl;
 
	subtract(otsu_dst,my_dst,sub);//两图像相减
	imshow("src",src);
	imshow("myOtsu",my_dst);
	imshow("Otsu",otsu_dst);
	imshow("Sub",sub);
	waitKey();
	system("pause");
	return 0;
}

原图:用的是lena

得到测试结果和图像

结论:可以看到自己实现的otsu和opencv自带的自适应阈值算法所得效果和效率相同。

最后:欢迎大家的批评,很高心与大家分享,谢谢大家。

 

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐