端午节快乐呀。
闲来无事,想着分割一下标线点云,但是阈值怎么确定呢?感觉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,与统计规律相符合。原图、强度统计直方图、分割结果如下。