Bootstrap

opencv学习5——大律二值化(ostu)

opencv中有多种方法进行图像的二值化,前面的3中直接设置二值化的阈值,比较粗暴无脑,而且用人眼看的话根本看不出来最佳阈值,因此人为的设置阈值是一种很不科学不严谨的方法,在opencv中ostu二值化应用很多。它是二值化中的一种高效算法,如果不了解ostu这种经典的二值化法就不能说是学习过opencv。

算法原理

首先从原理说起,这个ostu并不是完成最终的图像二值化,而是计算出二值化最合理最优化的分割阈值,从而实现完美二值化。首先需要将图片分为前景和背景,也就是将图片处理为非黑即白的二值化,白色定为前景,黑色定为背景,关键是要找出划分前景背景的分割值,因此将图像分为0~255共256个灰度级,这里可以将图像理解为一个256层像素平面叠加而成的整体,每一层代表一个灰度级即像素值。这里引入方差的概念,方差是各个数据与平均数之差的平方的平均数,反映的是数据之间的偏离程度,方差越大,偏离程度就越大,从而容易区分不会混淆,使得二值化图像黑白分明易于分析,这也是最优二值化的目的。而算法的关键就是找出方差最大时的像素值,定为二值化的分割阈值,并返回。

算法步骤

  1. 确定0~255各个像素值的数量
  2. 计算各灰度级的比例,也叫作归一化
  3. 定义一个变量th表示分割的阈值,从0开始迭代,计算0~th灰度级像素比例和,定义为u1,0~th灰度级的像素组合定为前景,计算前景像素值的平均灰度ave1,同样计算th~255(背景)的比例和u2,并计算平均灰度ave2。(比例也可以说成概率)
  4. 计算前景与背景的方差var,公式为:var=u1*u2*(ave1-ave2)^2。
  5. th从0开始遍历,th每取一个值就会产生一个方差,找到方差最大时对应的th,该th就是要返回的分割阈值。

代码如下

#include"iostream"
#include"opencv2/core/core.hpp"
#include"opencv2/highgui/highgui.hpp"
#include<opencv2/opencv.hpp>
#include<math.h>
using namespace cv;
using namespace std;
int GetThreshold(Mat img){
	int imgrow = img.rows;
	int imgcol = img.cols;
	int threshold = 0;
	int pixnum[256];
	float pixpro[256];
	//初始化为0
	for(int i=0;i<256;i++){
		pixnum[i] = 0;
		pixpro[i] = 0;
	}
	//计算每个像素值的数量
	for(int j=0;j<imgrow;j++){
		for(int i=0;i<imgcol;i++){
			pixnum[(int)img.at<uchar>(j,i)]++;
		}
	}
	//计算各个像素值所占的比例
	for(int i=0;i<256;i++){
		pixpro[i] = (float)pixnum[i]/(imgcol*imgrow);
	}
	//计算方差,对应步骤3,4
	float u1,u2,ave1,ave2,mathexp1,mathexp2,vartemp;
	double varmax=0;
	for(int th=0;th<256;th++){    //th定义为最佳分割阈值,开始遍历
		u1=u2=ave1=ave2=mathexp1=mathexp2=0;
		for(int i=0;i<256;i++){
			if(i<th){     //归为前景
				u1 += pixpro[i];  //计算前景概率之和
				mathexp1 += i*pixpro[i];   //数学期望
			}
			else{	
				u2 += pixpro[i];
				mathexp2 += i*pixpro[i];
			}
		}
		ave1 = mathexp1/u1;
		ave2 = mathexp2/u2;
		vartemp=(float)(u1*u2*(ave1-ave2)*(ave1-ave2));
		if(vartemp>varmax){
			varmax = vartemp;
			threshold = i;
		}
	}
	return threshold;
}

这里仅是返回值的函数,得到分割阈值,完整的代码需要加入另外的代码,https://blog.csdn.net/yuan123890/article/details/107238610,将该篇文章中的灰度化函数和二值化函数加入,主程序如下

void main(){
	Mat img = imread("ck567.jpg",IMREAD_COLOR);
	Mat gray = BGR2GRAY(img);
	Mat out = Binaryzation(gray,GetThreshold(gray));
	imshow("sample",out);
	waitKey(0);
	destroyAllWindows();
}

效果如下,可与上述链接中的图片进行对比,ostu的算法可以得到最佳分割阈值。

;