Otsu算法(大律法或最大类间方差法)
一、Otsu最大类间方差法原理
利用阈值将原图像分成前景,背景两个图象。
前景:用n1,csum,m1来表示在当前阈值下的前景的点数,质量矩,平均灰度
后景:用n2, sum-csum,m2来表示在当前阈值下的背景的点数,质量矩,平均灰度
当取最佳阈值时,背景应该与前景差别最大,关键在于如何选择衡量差别的标准,而在otsu算法中这个衡量差别的标准就是最大类间方差(英文简称otsu,这也就是这个算法名字的来源),在本程序中类间方差用sb表示,最大类间方差用fmax。
关于最大类间方差法(otsu)的性能:
类间方差法对噪音和目标大小十分敏感,它仅对类间方差为单峰的图像产生较好的分割效果。
当目标与背景的大小比例悬殊时,类间方差准则函数可能呈现双峰或多峰,此时效果不好,但是类间方差法是用时最少的。
最大类间方差法(otsu)的公式推导:
记t为前景与背景的分割阈值,前景点数占图像比例为w0, 平均灰度为u0;背景点数占图像比例为w1,平均灰度为u1。
则图像的总平均灰度为:u=w0*u0+w1*u1。
前景和背景图象的方差:g=w0*(u0-u)*(u0-u)+w1*(u1-u)*(u1-u)=w0*w1*(u0-u1)*(u0-u1),此公式为方差公式。
可参照概率论课本上面的g的公式也就是下面程序中的sb的表达式。当方差g最大时,可以认为此时前景和背景差异最大,此时的灰度t是最佳阈值sb = w1*w2*(u1-u0)*(u0-u1)。
二、Otsu算法步骤如下:
设图象包含L个灰度级(0,1…,L-1),灰度值为i的的象素点数为Ni ,图象总的象素点数为N=N0+N1+...+N(L-1)。灰度值为i的点的概率为:P(i) = N(i)/N.
门限t将整幅图象分为暗区c1和亮区c2两类,则类间方差σ是t的函数:σ=a1*a2(u1-u2)^2 (2)
式中,aj 为类cj的面积与图象总面积之比,a1=sum(P(i)) i->t, a2 =1-a1;
uj为类cj的均值,u1 =sum(i*P(i))/a1 0->t, u2 = sum(i*P(i))/a2, t+1->L-1,该法选择最佳门限t^使类间方差最大,即:令Δu=u1-u2,σb = max{a1(t)*a2(t)Δu^2}
/*****************************************************************************
* 函数名称: cvOtsu2D()
* 函数参数: CvMat* pGrayMat:灰度图形相对应的矩阵
* 返回值: int nThreshold
* 函数说明:实现灰度图的二值化分割——最大类间方差法(二维Otsu算法)
* 备注:在构建二维直方图的时候,采用灰度点的3*3邻域均值
******************************************************************************/
int cvOtsu2D(CvMat *pGrayMat)
{
double dHistogram[256][256]; //建立二维灰度直方图
double dTrMatrix = 0.0; //离散矩阵的迹
int height = pGrayMat->height;
int width = pGrayMat->width;
int N = height*width; //总像素数
int i, j;
for(i = 0; i < 256; i++)
{
for(j = 0; j < 256; j++)
dHistogram[i][j] = 0.0; //初始化变量
}
for(i = 0; i < height; i++)
{
for(j = 0; j < width; j++)
{
unsigned char nData1 = (unsigned char)cvGetReal2D(pGrayMat, i, j);//当前的灰度值
unsigned char nData2 = 0;
int nData3 = 0;//注意9个值相加可能超过一个字节
for(int m = i-1; m <= i+1; m++)
{
for(int n = j-1; n <= j+1; n++)
{
if((m >= 0) && (m < height) && (n >= 0) && (n < width))
nData3 += (unsigned char)cvGetReal2D(pGrayMat, m, n); //当前的灰度值 }
}
nData2 = (unsigned char)(nData3 / 9); //对于越界的索引值进行补零,邻域均值
dHistogram[nData1][nData2]++;
}
}
for(i = 0; i < 256; i++)
for(j = 0; j < 256; j++)
dHistogram[i][j] /= N; //得到归一化的概率分布
double Pai = 0.0; //目标区均值矢量i分量
double Paj = 0.0; //目标区均值矢量j分量
double Pbi = 0.0; //背景区均值矢量i分量
double Pbj = 0.0; //背景区均值矢量j分量
double Pti = 0.0; //全局均值矢量i分量
double Ptj = 0.0; //全局均值矢量j分量
double W0 = 0.0; //目标区的联合概率密度
double W1 = 0.0; //背景区的联合概率密度
double dData1 = 0.0;
double dData2 = 0.0;
double dData3 = 0.0;
double dData4 = 0.0; //中间变量
int nThreshold_s = 0;
int nThreshold_t = 0;
double temp = 0.0; //寻求最大值
for(i = 0; i < 256; i++)
{
for(j = 0; j < 256; j++)
{
Pti += i*dHistogram[i][j];
Ptj += j*dHistogram[i][j];
}
}
for(i = 0; i < 256; i++)
{
for(j = 0; j < 256; j++)
{
W0 += dHistogram[i][j];
dData1 += i*dHistogram[i][j];
dData2 += j*dHistogram[i][j];
W1 = 1-W0;
dData3 = Pti-dData1;
dData4 = Ptj-dData2;
Pai = dData1 / W0;
Paj = dData2 / W0;
Pbi = dData3 / W1;
Pbj = dData4 / W1; // 得到两个均值向量,用4个分量表示
dTrMatrix = ((W0 * Pti - dData1) * (W0 * Pti - dData1) + (W0 * Ptj - dData2) * (W0 * Ptj- dData2)) / (W0 * W1);
if(dTrMatrix > temp)
{
temp = dTrMatrix;
nThreshold_s = i;
nThreshold_t = j;
}
}
}
int nThreshold = (nThreshold_s + nThreshold_t) / 2;//返回阈值,有多种形式,可以单独某一个,也可是两者的均值
return nThreshold;
}