C++:OTSU確定標線點雲分割閾值

端午節快樂呀。

閒來無事,想着分割一下標線點雲,但是閾值怎麼確定呢?感覺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,與統計規律相符合。原圖、強度統計直方圖、分割結果如下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章