大家好,我们是大黄蜂队,编号是 CICC1948。前一篇帖子(https://www.rvmcu.com/community-topic-id-856.html)分享了模板匹配的目标追踪算法,其中提到了需要利用心理学公式将 RGB 图像转为灰度图像。这里就分享一种能够从灰度图象中求出一个灰度阈值,再根据这个阈值将图片进行二值化(所有像素点只包含纯黑和纯白)的算法 —— Otsu 阈值算法。
Otsu 算法是由日本学者OTSU于1979年提出的一种对图像进行二值化的高效算法,又称“最大类间方差法”。当我们对一个图象进行二值化操作的时候,需要根据一项灰度阈值来判决每个像素点应该被视作纯黑还是纯白,即利用此阈值将原图像分成前景、背景两个图象。不同阈值对于前景和背景图象的划分不同,一般来说,前景和背景图象的差别越大,表明划分的效果越好。在 Otsu 算法中,以类间方差来衡量背景和前景图象的差别:类间方差越大,表示背景和前景图象的差别越大。因此,Otsu算法的最终目的就是求得一个最佳灰度阈值,使背景与前景的类间方差最大。
为了找到这个灰度阈值,我们只需要遍历从0到255之间的每一种阈值,计算出根据此阈值划分得到的前景和背景图象的类间方差(共256个),找到其中最大的类间方差,其对应阈值即划分图象的最佳灰度阈值。
对于图象I(x,y),前景和背景的分割阈值记为T,属于前景的像素点数占整幅图像的比例记为ω0,其平均灰度为μ0;属于背景的像素点数占整幅图像的比例记为ω1,其平均灰度为μ1。图象的总平均灰度记为μ,类间方差记为g。根据以下公式,我们可以将类间方差g计算出来:
在用程序实现图像的灰度阈值计算时,就是用上述遍历的方式,这里贴上一段代码,仅供参考:
int otsuThreshold(IplImage *frame)
{
const int GrayScale = 256;
int width = frame->width;
int height = frame->height;
int pixelCount[GrayScale]; // 256种灰度值;
float pixelPro[GrayScale];
int i, j, pixelSum = width * height, threshold = 0; // 用于循环
uchar* data = (uchar*)frame->imageData;
// uchar是无符号的,于是数值范围为0~255,
for(i = 0; i < GrayScale; i++) // 所有元素置零
{
pixelCount[i] = 0;
pixelPro[i] = 0;
}
//统计灰度级中每个像素在整幅图像中的个数
for(i = 0; i < height; i++)
{
for(j = 0;j < width;j++)
{
pixelCount[(int)data[i * width + j]]++;
}
}
//计算每个像素在整幅图像中的比例
for(i = 0; i < GrayScale; i++)
{
pixelPro[i] = (float)pixelCount[i] / pixelSum;
}
//遍历灰度级[0,255]
float w0, w1, u0tmp, u1tmp, u0, u1, u,
deltaTmp, deltaMax = 0;
for(i = 0; i < GrayScale; i++)
{
w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;
for(j = 0; j < GrayScale; j++)
{
if(j <= i) //背景部分
{
w0 += pixelPro[j];
u0tmp += j * pixelPro[j];
}
else //前景部分
{
w1 += pixelPro[j];
u1tmp += j * pixelPro[j];
}
}
u0 = u0tmp / w0;
u1 = u1tmp / w1;
u = u0tmp + u1tmp;
deltaTmp =
w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2); // pow(x,y) 返回x的y次幂
if(deltaTmp > deltaMax)
{
deltaMax = deltaTmp;
threshold = i;
}
}
return threshold;
}