详解OTSU(大津法)原理及C语言代码实现
详解OTSU(大津法)原理及C语言代码实现
OTSU(大津法)是一种常用的图像二值化算法,通过计算类间方差来自动选择最佳阈值,实现图像的前景和背景分离。本文将详细介绍OTSU算法的原理,并给出完整的C语言实现代码。
灰度图二值化
在对灰度图像进行处理时,为了便于观察和分析,经常需要将图像中的目标主体和背景分割开来,变成二值化图像(只有黑和白)。灰度图像是由256个灰度级组成的,其中255代表全白,0表示全黑。在进行二值化时,需要设定一个阈值,根据灰度值大于或小于阈值进行黑白显示。背景通常用白色(0)表示,目标物体用黑色(1)表示。阈值的选取对图像二值化的效果影响非常大。
从上图可以看出,阈值的选取对于灰度图二值化有着至关重要的作用。为了找到一个合适的阈值,使得背景和前景能够很好地分开,并且最大程度地减少误判,需要对图像的灰度直方图进行分析。
灰度直方图
灰度直方图显示了图像中不同灰度级的像素点分布情况。通过分析灰度直方图,可以确定图像中目标和背景的灰度范围,从而选择合适的阈值进行二值化。
类间方差
方差是一组数据离散程度的度量,方差越大表示离散程度越大。在图像二值化中,背景和前景的灰度值应该有明显的差异,因此可以通过类间方差来衡量二值化效果。假设图像阈值为T,小于T的像素为目标,数量占总图像比例为w0,平均灰度值为u0;大于T的像素为背景,数量占图像比例为w1,平均灰度值为u1。类间方差g的定义为:
目标是找到一个阈值T,使得类间方差g最大。
大津法(OTSU)
大津法(OTSU)是由日本学者大津(Nobuyuki Otsu)于1979年提出的一种确定图像二值化分割阈值的算法。该算法假设图像像素能够根据全局阈值被分成背景和目标两部分,并计算最佳阈值来区分这两类像素,使得两类像素的区分度最大。
大津法适用于图像灰度分布整体呈现“双峰”的情况。当图像中的目标与背景的面积相差很大时,灰度直方图可能没有明显的双峰,或者两个峰的大小相差很大,此时分割效果可能不佳。
代码实现
下面以C语言为例,详细讲解OTSU算法的实现过程。
函数定义
首先定义一个函数,用于实现OTSU二值化算法:
uint8 otsuThreshold(uint8 *image, uint16 col, uint16 row)
{
#define GrayScale 256//定义256个灰度级
uint16 width = col; //图像宽度
uint16 height = row; //图像长度
int pixelCount[GrayScale]; //每个灰度值所占像素个数
float pixelPro[GrayScale]; //每个灰度值所占总像素比例
int i, j;
int sumPixel = width * height;//总像素点
uint8 threshold = 0; //最佳阈值
uint8* data = image; //指向像素数据的指针
数组初始化
局部变量数组需要进行初始化,可以使用以下三种方法:
- for循环赋值
- memset
- 声明时使用 {0} 初始化
实测表明,for循环浪费的时间最多,{0} 与memset 耗时差不多。
统计灰度级像素个数
统计每个灰度级在整个图像中的个数:
for (i = 0; i < height; i++)
{
for (j = 0; j < width; j++)
{
pixelCount[(int)data[i * width + j]]++; //将像素值作为计数数组的下标
}
}
计算像素比例和总平均灰度
计算每个灰度级像素数占图像总像素的比例,以及图像的总平均灰度u:
float w0=0, w1=0, u0Sum=0, u1Sum=0, u0=0, u1=0, u=0, variance=0, maxVariance = 0;
for (i = 0; i < GrayScale; i++)
{
pixelPro[i] = (float)pixelCount[i] / sumPixel;
u += i * pixelPro[i]; //总平均灰度
}
计算类间方差
遍历阈值从1到255,计算每次的目标像素数量占总图像比例w0,平均灰度值u0,背景像素数量占图像比例w1,平均灰度值u1,然后比较256次情况下的类间方差g的最大值:
for (i = 0; i < GrayScale; i++) // i作为阈值 阈值从1-255遍历
{
for (j = 0; j < GrayScale; j++) //求目标前景和背景
{
if (j <= i) //前景部分
{
w0 += pixelPro[j];
u0Sum += j * pixelPro[j];
}
else //背景部分
{
w1 += pixelPro[j];
u1Sum += j * pixelPro[j];
}
}
u0 = u0Sum / w0;
u1 = u1Sum / w1;
variance = w0 * pow((u0 - u), 2) + w1 * pow((u1 - u), 2); //类间方差计算公式
if (variance > maxVariance) //判断是否为最大类间方差
{
maxVariance = variance;
threshold = i;
}
}
return threshold;
}
优化代码
通过利用公式1(w0+w1=1)和公式4,可以将两个for循环叠加,优化代码执行效率:
float maxVariance=0.0; //最大类间方差
float w0 = 0, avgValue = 0; //w0 前景比例 ,avgValue 前景平均灰度
for(int i = 0; i < 256; i++) //每一次循环都是一次完整类间方差计算 (两个for叠加为1个)
{
w0 += pixelPro[i]; //假设当前灰度i为阈值, 0~i 灰度像素所占整幅图像的比例即前景比例
avgValue += i * pixelPro[i];
float variance = pow((avgValue/w0 - u), 2) * w0 /(1 - w0); //类间方差
if(variance > maxVariance)
{
maxVariance = variance;
threshold = i;
}
}
return threshold;
}
使用方法
调用OTSU算法计算阈值:
threshold = otsuThreshold(image[0],COL,ROW); //大津法计算阈值
threshold = threshold > 50?50:threshold;
总结
OTSU算法通过计算类间方差来自动选择最佳阈值,实现图像的前景和背景分离。虽然该算法对噪声敏感,且在目标与背景面积比例悬殊时效果不佳,但在许多场景下仍能取得很好的二值化效果。通过本文的详细讲解和代码实现,读者可以更好地理解和应用OTSU算法。