一、大津算法
大津算法(Ostu)也称最大类间方差法。顾名思义,就使两个类别之间某个属性的方差最大的方法。在图像处理中,灰度分布均匀性作为区分图像各个区域的一种度量,背景和目标之间的灰度均值的方差越大,说明目标与背景差别越大。由于其不受图像对比度的影响,大津二值化常被用来分割目标与背景。
二、原理
对于一副大小RC的图像,目标和背景的分割阈值记作T,图像中像素的灰度值小于阈值T的像素个数记作N0。
1.目标的像素点数占整幅图像的比例记为ω0,ω0=N0/ C×R,平均灰度μ0。
2.背景像素点数占整幅图像的比例为ω1, ω1=N0/ C×R,平均灰度μ1。
3.图像的总平均灰度记为μ, μ=ω0μ0+ω1*μ1 (1)
4.类间方差记为g, g = w0 * (u0 - u)^2 + w1 * (u1 - u)^2。 (2)
5. N0+N1=M×N
ω0+ω1=1
将式(1)代入式(2),得到等价公式:g=ω0ω1(μ0-μ1)^2 (3)
公式(3)便是最大类间方差法。
三.算法实现步骤
1.统计图像中各个灰度值的个数:
const int nGrayScale = 256;//灰度
int nPixelCount[nGrayScale] = {
0 };//灰度直方图
//统计图片中各个灰度值的个数
for (int y = 0; y < src.rows; ++y)
{
for (int x = 0; x < src.cols; ++x)
{
int val = src.at<uchar>(y, x);
nPixelCount[val]++;
}
}
2.统计图片中各个灰度值所占的比例:
//统计图片中各个灰度值所占的比例
int nPixelSum = src.rows * src.cols;//总像素值
float fPixelPct[nGrayScale] = {
0 };//各个灰度值占总体的比例
for (int i = 0; i < nGrayScale; ++i)
{
fPixelPct[i] = 1.0 *nPixelCount[i] / nPixelSum;
}
3.从灰度0迭代到灰度255,每次迭代计算能使公式(3)最大的阈值Threshed:
double w0, w1;//背景/目标像素占比
double u0, u1;//目标/背景平均灰度值
double fTempVar = 0;//类间方差
double fMaxVar = 0;//最大类间方差
double fBestValue = 0;//最优阈值
double fTemp0, fTemp1;
for (int k = 0; k < nGrayScale; ++k)
{
w0 = w1 = u0 = u1 = fTempVar = 0;
fTemp0 = fTemp1 = 0;
//前景,背景区分 [0-k][k+1-255]
for (int i = 0; i < nGrayScale; ++i)
{
//如果当前像素值小于阈值k则属于背景,反之属于目标
if (i <= k)
{
//计算背景像素占比
w0 += fPixelPct[i];
//计算当前灰度值发生的概率:灰度值*灰度值发生的概率
fTemp0 += (i * fPixelPct[i]);
}
else
{
//计算背景像素占比
w1 += fPixelPct[i];
fTemp1 += (i * fPixelPct[i]);
}
}
//计算平均灰度值:p0/w0
u0 = fTemp0 / w0;
u1 = fTemp1 / w1;
//计算类内方差
fTempVar = (float)(w0 * w1 * pow((u0 - u1), 2));
if (fTempVar > fMaxVar)
{
fMaxVar = fTempVar;
fBestValue = k;
}
}
四.测试结果
font color=#999AAA >测试采用了三张目标与背景对比度不一样的图片。
图片1:
图片2:
图片3:
实验证明,大津二值化可以不受图像对比度的影响,自动分割目标与背景。
五.完整代码
//原图
Mat src = imread("image4.PNG", IMREAD_GRAYSCALE);
//输出图
Mat dst = Mat::zeros(src.size(), src.type());
const int nGrayScale = 256;//灰度
int nPixelCount[nGrayScale] = {
0 };//灰度直方图
//统计图片中各个灰度值的个数
for (int y = 0; y < src.rows; ++y)
{
for (int x = 0; x < src.cols; ++x)
{
int val = src.at<uchar>(y, x);
nPixelCount[val]++; //int nPixelCount[nGrayScale] = { 0 };//灰度直方图
}
}
//统计图片中各个灰度值所占的比例
int nPixelSum = src.rows * src.cols;//总像素值
float fPixelPct[nGrayScale] = {
0 };//各个灰度值占总体的比例
for (int i = 0; i < nGrayScale; ++i)
{
fPixelPct[i] = 1.0 *nPixelCount[i] / nPixelSum;
}
double w0, w1;//背景/目标像素占比
double u0, u1;//目标/背景平均灰度值
double fTempVar = 0;//类间方差
double fMaxVar = 0;//最大类间方差
double fBestValue = 0;//最优阈值
double fTemp0, fTemp1;
for (int k = 0; k < nGrayScale; ++k)
{
w0 = w1 = u0 = u1 = fTempVar = 0;
fTemp0 = fTemp1 = 0;
//前景,背景区分 [0-k][k+1-255]
for (int i = 0; i < nGrayScale; ++i)
{
//如果当前像素值小于阈值k则属于背景,反之属于目标
if (i <= k)
{
//计算背景像素占比
w0 += fPixelPct[i];
//计算当前灰度值发生的概率:灰度值*灰度值发生的概率
fTemp0 += (i * fPixelPct[i]);
}
else
{
//计算背景像素占比
w1 += fPixelPct[i];
fTemp1 += (i * fPixelPct[i]);
}
}
//计算平均灰度值:p0/w0
u0 = fTemp0 / w0;
u1 = fTemp1 / w1;
//计算类内方差
fTempVar = (float)(w0 * w1 * pow((u0 - u1), 2));
if (fTempVar > fMaxVar)
{
fMaxVar = fTempVar;
fBestValue = k;
}
}
threshold(src, dst, fBestValue, 255, THRESH_BINARY);