opencv中有多种方法进行图像的二值化,前面的3中直接设置二值化的阈值,比较粗暴无脑,而且用人眼看的话根本看不出来最佳阈值,因此人为的设置阈值是一种很不科学不严谨的方法,在opencv中ostu二值化应用很多。它是二值化中的一种高效算法,如果不了解ostu这种经典的二值化法就不能说是学习过opencv。
算法原理
首先从原理说起,这个ostu并不是完成最终的图像二值化,而是计算出二值化最合理最优化的分割阈值,从而实现完美二值化。首先需要将图片分为前景和背景,也就是将图片处理为非黑即白的二值化,白色定为前景,黑色定为背景,关键是要找出划分前景背景的分割值,因此将图像分为0~255共256个灰度级,这里可以将图像理解为一个256层像素平面叠加而成的整体,每一层代表一个灰度级即像素值。这里引入方差的概念,方差是各个数据与平均数之差的平方的平均数,反映的是数据之间的偏离程度,方差越大,偏离程度就越大,从而容易区分不会混淆,使得二值化图像黑白分明易于分析,这也是最优二值化的目的。而算法的关键就是找出方差最大时的像素值,定为二值化的分割阈值,并返回。
算法步骤
- 确定0~255各个像素值的数量
- 计算各灰度级的比例,也叫作归一化
- 定义一个变量th表示分割的阈值,从0开始迭代,计算0~th灰度级像素比例和,定义为u1,0~th灰度级的像素组合定为前景,计算前景像素值的平均灰度ave1,同样计算th~255(背景)的比例和u2,并计算平均灰度ave2。(比例也可以说成概率)
- 计算前景与背景的方差var,公式为:var=u1*u2*(ave1-ave2)^2。
- 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的算法可以得到最佳分割阈值。