端午節快樂呀。
閒來無事,想着分割一下標線點雲,但是閾值怎麼確定呢?感覺OTSU應該可以。
一、OTSU簡介
OTSU(大津法、最大類間方差法)算法是由日本學者OTSU於1979年提出的一種對圖像進行二值化的高效算法。
1.1 原理
利用閾值將原圖像分成前景、背景。當取最佳閾值時,背景應該與前景差別最大。otsu中衡量差別的標準是最大類間方差。
1.2 性能
類間方差法對噪音和目標大小十分敏感,它僅對類間方差爲單峯的圖像產生較好的分割效果。
當目標與背景的大小比例懸殊時,類間方差準則函數可能呈現雙峯或多峯,此時效果不好,但是類間方差法是用時最少的。
1.3 公式推導:
記k爲前景與背景的分割閾值,前景點數佔圖像比例爲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最大時,認爲此時前景和背景差異最大,此時的灰度k是最佳閾值。
二、分享給有需要的人,代碼質量勿噴
/* The returned threshold of intensity is obtained by OTSU. */
uint xjGetIntensityByOTSU(ccPointCloud* cloud)
{
uint thrIntensity = 1;
/* 1 強度直方圖 */
uint histogramIntensity[65536] = { 0 };
uint maxIntensity = 0, minIntensity = 666666;//最大最小強度值
uint pcCount = cloud->size();
ccScalarField* ccSF = static_cast<ccScalarField*>(cloud->getScalarField(cloud->getScalarFieldIndexByName("Intensity")));
for (int i = 0; i < pcCount; i++)
{
uint vIntensity = ccSF->getValue(i);
if (vIntensity > maxIntensity)
{
maxIntensity = vIntensity;
}
if (vIntensity < minIntensity)
{
minIntensity = vIntensity;
}
++histogramIntensity[vIntensity];
}
/* 2 總質量矩 += 強度 * 點數 */
double sumIntensity = 0.0;
for (int k = minIntensity; k <= maxIntensity; k++)
{
sumIntensity += (double)k * (double)histogramIntensity[k];
}
/* 3 遍歷計算 */
double otsu = -1.0;
int w0 = 0;//小於等於當前閾值的點數(前景點數)
double sumFore = 0.0;//前景質量矩
for (int k = minIntensity; k <= maxIntensity; k++)
{
w0 += histogramIntensity[k];
int w1 = pcCount - w0;//(後景點數)
if (w0 == 0)
continue;
if (w1 == 0)
break;
sumFore += (double)k * histogramIntensity[k];
double u0 = sumFore / w0; //前景的平均灰度
double u1 = (sumIntensity - sumFore) / w1; //背景的平均灰度
double g = (double)w0 * (double)w1 * (u0 - u1) * (u0 - u1); //類間方差
if (g > otsu)
{
otsu = g;
thrIntensity = k;
}
}
return thrIntensity;
}
三、實驗測試
截取一段標線點雲,通過OTSU計算得到閾值爲44,與統計規律相符合。原圖、強度統計直方圖、分割結果如下。