二值化是图像分割的一种方法。在二值化图象的时候把大于某个临界灰度值的像素灰度设为灰度极大值,把小于这个值的像素灰度设为灰度极小值,从而实现二值化。
根据阈值选取的不同,二值化的算法分为固定阈值和自适应阈值。 比较常用的二值化方法则有:双峰法、P参数法、迭代法和OTSU法等。
1、双峰法
在一些简单的图像中,物体的灰度分布比较有规律,背景与目标在图像的直方图各自形成一个波峰,即区域与波峰一一对应,每两个波峰之间形成一个波谷。那么,选择双峰之间的波谷所代表的灰度值T作为阈值,即可实现两个区域的分割。如图所示
在直方图中我们可以明显的看到两个山峰状的图像分布,山峰的顶点我们记为Hmax1和Hmax2,他们对应的灰度值分别为T1和T2,那么双峰法图像分割的思想就是找到图像两个山峰之间的谷地最低值,即在[T1,T2]的灰度范围内寻找阈值T,使其满足对应的像素数目最少,表现在图像上就是高度最低,用T对图像进行分割或二值化。
2、P参数法
2.1 原理
所谓P分位法图像分割,就是在知道图像中目标所占的比率Ratio时,循环不同的灰度值对图像进行分割,并计算对应的目标所占的比率,如果该比率与Ratio的差值足够小,那么该阈值就是所求的最佳分割阈值。
2.2 算法
算法过程如下:
- 已知目标图像所占比率P;
- 设定一阈值Th,它将图像分割为两部分,目标部分A和背景部分B,统计两部分所包含的像素数目分别为Na和Nb;
-
将Th从1-254迭代,每改变一次Th ,计算一次Na,Nb,根据Na,Nb计算目标所占的比率P,公式如下:
-
计算当前阈值对应的分割比率与已知比率的差值,若小于某阈值则停止迭代,否则,转至3继续进行,公式如下:
其中T为某一小数
3、迭代法
3.1 原理
迭代选择法是首先猜测一个初始阈值,然后再通过对图像的多趟计算对阈值进行改进的过程。重复地对图像进行阈值操作,将图像分割为对象类和背景类,然后来利用每一个类中的灰阶级别对阈值进行改进。
3.2 算法
- 为全局阈值选择一个初始估计值T(图像的平均灰度)。
- 用T分割图像。产生两组像素:G1有灰度值大于T的像素组成,G2有小于等于T像素组成。
- 计算G1和G2像素的平均灰度值m1和m2;
- 计算一个新的阈值:T = (m1 + m2) / 2;
- 重复步骤2和4,直到连续迭代中的T值间的差小于一个预定义参数为止。
3.3 代码
/**
* 迭代法
*
* @param srcImg 灰度图像
* @param maxIter 最大迭代次数
*/
+ (int)detechThreshold:(IplImage *)srcImg maxIterat:(int)maxIter{
//图像信息
int height = srcImg->height;
int width = srcImg->width;
int step = srcImg->widthStep/sizeof(uchar);
uchar *data = (uchar*)srcImg->imageData;
int iDiffRec =0; //使用给定阀值确定的亮区与暗区平均灰度差异值, 根据需要返回
int F[256]={ 0 }; //直方图数组
int iTotalGray=0;//灰度值和
int iTotalPixel =0;//像素数和
Byte bt;//某点的像素值
uchar iThrehold,iNewThrehold;//阀值、新阀值
uchar iMaxGrayValue=0,iMinGrayValue=255;//原图像中的最大灰度值和最小灰度值
uchar iMeanGrayValue1,iMeanGrayValue2;
//获取(i,j)的值,存于直方图数组F
for(int i=0;i<width;i++){
for(int j=0;j<height;j++){
bt = data[i*step+j];
if(bt<iMinGrayValue)
iMinGrayValue = bt;
if(bt>iMaxGrayValue)
iMaxGrayValue = bt;
F[bt]++;
}
}
iThrehold =0;
iNewThrehold = (iMinGrayValue+iMaxGrayValue)/2;//初始阀值
iDiffRec = iMaxGrayValue - iMinGrayValue;
for(int a=0;(abs(iThrehold-iNewThrehold)>0.5)&&a<maxIter;a++){ //迭代中止条件
iThrehold = iNewThrehold;
//小于当前阀值部分的平均灰度值
for(int i=iMinGrayValue;i<iThrehold;i++){
iTotalGray += F[i]*i;//F[]存储图像信息
iTotalPixel += F[i];
}
iMeanGrayValue1 = (uchar)(iTotalGray/iTotalPixel);
//大于当前阀值部分的平均灰度值
iTotalPixel =0;
iTotalGray =0;
for(int j=iThrehold+1;j<iMaxGrayValue;j++){
iTotalGray += F[j]*j;//F[]存储图像信息
iTotalPixel += F[j];
}
iMeanGrayValue2 = (uchar)(iTotalGray/iTotalPixel);
iNewThrehold = (iMeanGrayValue2+iMeanGrayValue1)/2; //新阀值
iDiffRec = abs(iMeanGrayValue2 - iMeanGrayValue1);
}
return iThrehold;
}
4. OTSU法
4.1 原理
最大类间方差法是由日本学者大津于1979年提出的,是一种自适应的阈值确定的方法,又叫大津法,简称OTSU。它是按图像的灰度特性,将图像分成背景和目标2部分。背景和目标之间的类间方差越大,说明构成图像的2部分的差别越大,当部分目标错分为背景或部分背景错分为目标都会导致2部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。对于图像I(x,y),前景(即目标)和背景的分割阈值记作T,属于前景的像素点数占整幅图像的比例记为ω0,其平均灰度μ0;背景像素点数占整幅图像的比例为ω1,其平均灰度为μ1。图像的总平均灰度记为μ,类间方差记为g。
4.2 算法
假设图像的背景较暗,并且图像的大小为M×N,图像中像素的灰度值小于阈值T的像素个数记作N0,像素灰度大于阈值T的像素个数记作N1,则有:
ω0=N0/ M×N (1)
ω1=N1/ M×N (2)
N0+N1=M×N (3)
ω0+ω1=1 (4)
μ=ω0μ0+ω1μ1 (5)
g=ω0(μ0-μ)2+ω1(μ1-μ)2 (6)
将式(5)代入式(6),得到等价公式: g=ω0ω1(μ0-μ1)^2 (7)采用遍历的方法得到使类间方差最大的阈值T,即为所求。
4.3 代码实现
代码实现如下:
int OTSU(unsigned char* pGrayImg , int iWidth , int iHeight)
{
if((pGrayImg==0)||(iWidth<=0)||(iHeight<=0))return -1;
int ihist[256];
int thresholdValue=0; // „–÷µ
int n, n1, n2 ;
double m1, m2, sum, csum, fmax, sb;
int i,j,k;
memset(ihist, 0, sizeof(ihist));
n=iHeight*iWidth;
sum = csum = 0.0;
fmax = -1.0;
n1 = 0;
for(i=0; i < iHeight; i++)
{
for(j=0; j < iWidth; j++)
{
ihist[*pGrayImg]++;
pGrayImg++;
}
}
pGrayImg -= n;
for (k=0; k <= 255; k++)
{
sum += (double) k * (double) ihist[k];
}
for (k=0; k <=255; k++)
{
n1 += ihist[k];
if(n1==0)continue;
n2 = n - n1;
if(n2==0)break;
csum += (double)k *ihist[k];
m1 = csum/n1;
m2 = (sum-csum)/n2;
sb = (double) n1 *(double) n2 *(m1 - m2) * (m1 - m2);
if (sb > fmax)
{
fmax = sb;
thresholdValue = k;
}
}
return(thresholdValue);
}
5、一维最大熵法
5.1 原理
一维最大熵法图像分割就是利用图像的灰度分布密度函数定义图像的信息熵,通过优化一定的熵准则得到熵最大时对应的阈值,从而进行图像分割的方法。
5.2 算法
算法过程:
- 对于一幅灰度图像,灰度范围为[0,L-1],求取图像的最小灰度级min,最大灰度级max;
-
按照如下熵的公式求取灰度t对应的熵值;
其中,pi表示灰度级i出现的概率。
- 计算t从最小灰度min到最大灰度max之间不同灰度级所对应的熵值E(t),求取E(t)最大时所对应的灰度级t,该灰度级即为所求的阈值Th。
5.3 代码
代码实现如下:
// 计算当前位置的能量熵
#define cvQueryHistValue_1D( hist, idx0 ) \((float)cvGetReal1D( (hist)->bins, (idx0)))
+ (double)caculateCurrentEntropy:(CvHistogram *)histogram currentThreshold:(int)threshold state:(EntropyState)state{
int start,end;
int total =0;
double cur_entropy =0.0;
if(state == Back){
start =0;
end = threshold;
}else{
start = threshold;
end =256;
}
for(int i=start;i<end;i++){
total += (int)cvQueryHistValue_1D(histogram,i);//查询直方块的值 P304
}
for(int j=start;j<end;j++)
{
if((int)cvQueryHistValue_1D(histogram,j)==0)
continue;
double percentage = cvQueryHistValue_1D(histogram,j)/total;
/*熵的定义公式*/
cur_entropy +=-percentage*logf(percentage);
/*根据泰勒展式去掉高次项得到的熵的近似计算公式
cur_entropy += percentage*percentage;*/
}
return cur_entropy;
}
//寻找最大熵阈值并分割
+ (int)maxEntropy:(IplImage *)srcImg{
/**
* 创建直方图
*
* @param dims 直方图维数的数目
* @param sizes 直方图维数尺寸的组数
* @param type 直方图的表示格式: CV_HIST_ARRAY 意味着直方图数据表示为多维密集数组 CvMatND; CV_HIST_TREE 意味着直方图数据表示为多维稀疏数组 CvSparseMat.
*/
int hist_size = 256;
CvHistogram *hist = cvCreateHist(1, &hist_size, CV_HIST_ARRAY);
cvCalcHist(&srcImg, hist);
double maxentropy = -1.0;
int max_index = -1;
// 循环测试每个分割点,寻找到最大的阈值分割点
for (int i=0; i < hist_size; i++) {
double backValue = [[self class] caculateCurrentEntropy:hist currentThreshold:i state:Back];
double frontValue = [[self class] caculateCurrentEntropy:hist currentThreshold:i state:Object];
double cur_entropy = backValue + frontValue;
if(cur_entropy>maxentropy){
maxentropy = cur_entropy;
max_index = i;
}
}
return max_index;
}
参考文章:
图像二值化----otsu(最大类间方差法、大津算法)
图像基本变换---图像二值化(包含OSTU/迭代法/统计法/双峰法/P分位法/最大熵法)
七种常见阈值分割代码(Otsu、最大熵、迭代法、自适应阀值、手动、迭代法、基本全局阈值法)