一、基本介绍
应用:求图像全局阈值,适用于大多需要求图像全局阈值的需求
优点:计算简单、快速,不受图像亮度和对比度影响
缺点:对图像噪声敏感;只能针对单一目标分割;当目标和背景大小比例悬殊、类间方差函数可能呈现双峰或者多峰,效果不好
二、原理
OTSU算法假设存在阈值TH,将图像所有像素分为两类C1(小于TH)、C2(大于TH)。这两类像素各自的均值分别为m1、m2,图像全局均值为mG。同事像素被分为C1、C2类的概率分别为p1、p2.因此有:
p1*m1+p2*m2=mG (1)
p1+p2=1 (2)
根据方差概念,类间方差表达式为:
σ^2=p1*(m1-mG)^2+p2*(m2-mG)^2 (3)
将(1)代入(3)得:
σ^2=p1*p2*(m1-m2)^2 (4)
其中:
根据文献,对式(4)进行变形
首先灰度级K的累加均值m和图像全局均值mG分别为:
根据式(6),m1、m2变形为:
m1=1/p1m (10)
m2=1/p2(mG-m) (11)
式(10)、(11)代入(4),可得类间方差公式:
根据式(5)、(8)、(9)求得式上式(12)最大化的灰度级k就是OTSU阈值。
三、代码实现
//自适应阈值 传入图像为灰度图
int Otsu(Mat src)
{
//传入的图像为灰度图
//图像宽、高
int width = src.cols;
int height = src.rows;
//对应像素值数量
int pixelCount[256] = { 0 };
//对应像素值占比
float pixelPro[256] = { 0 };
//图像像素数
int pixelSum = width * height;
int threshold = 0;
//统计灰度级中每个像素在整幅图像中的个数
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int pixel = (int)src.at<uchar>(i, j);
pixelCount[pixel]++;
}
}
//计算每个像素在整幅图像中的比例
for (int i = 0; i < 256; i++)
{
pixelPro[i] = (float)(pixelCount[i]) / (float)(pixelSum);
}
//经典ostu算法,得到前景和背景的分割
//遍历灰度级[0,255],计算出方差最大的灰度值,为最佳阈值
/*
w0,w1:前景和背景所占整幅图像的比例
u0,u1:前景和背景的平均灰度
u:整幅图像平均灰度
*/
float w0, w1, u0tmp, u1tmp, u0, u1, u, deltaTmp, deltaMax = 0;
for (int i = 0; i < 256; i++)
{
w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;
for (int j = 0; j < 256; j++)
{
if (j <= i) //背景部分
{
//以i为阈值分类,第一类总的概率
w0 += pixelPro[j];
u0tmp += j * pixelPro[j];
}
else
//前景部分
{
//以i为阈值分类,第二类总的概率
w1 += pixelPro[j];
u1tmp += j * pixelPro[j];
}
}
u0 = u0tmp / w0; //第一类的平均灰度
u1 = u1tmp / w1; //第二类的平均灰度
u = u0tmp + u1tmp; //整幅图像的平均灰度
//计算类间方差
deltaTmp = w0 * (u0 - u)*(u0 - u) + w1 * (u1 - u)*(u1 - u);
//找出最大类间方差以及对应的阈值
if (deltaTmp > deltaMax)
{
deltaMax = deltaTmp;
threshold = i;
}
}
//返回最佳阈值;
return threshold;
}