大津法的C语言实现

大津法

大津法又叫最大类间方差法,由日本学者大津展之在1979年提出的对图像二值化高效算法.

对于一帧灰度图像,一个像素由八位0~255表示,单片机是很难实时处理这么大的数据的,我们需要通过一个阈值将其二值化,小于这个阈值的像素判定黑色,大于的判定白色.

摄像头视野内,最主要的颜色就是赛道白色和蓝布蓝色,对于总钻风获取的灰度图也可以通过灰度直方图来区分,下图是photoshop提取的灰度直方图,可以非常明显地看到两个峰,我们需要找到一个值(一般在两峰之间),使这个值两边间类方差最大.
智能车赛道灰度直方图

原理

将直方图在一阈值分割为两组,使这两组之间方差最大.

对一张图像

  • m m m: 灰度值为 1 ∼ m 1\sim m 1m
  • n i n_i ni: 灰度值 i i i的像素数量
  • N N N: 像素总数 N = ∑ i = 0 n x i N=\sum_{i=0}^n x_i N=i=0nxi
  • p i p_i pi: 各级灰度概率 p i = n i N p_i=\frac{n_i}{N} pi=Nni

用一个灰度阈值 k k k将像素分为前景 C 0 = ∣ 1 ∼ k ∣ C_0=|1 \sim k| C0=1k和背景 C 1 = ∣ k + 1 ∼ m ∣ C_1=|k+1\sim m| C1=k+1m两组,得到

  • ω 0 \omega_0 ω0: 前景 C 0 C_0 C0组像素出现的概率 ω 0 = ω ( k ) = ∑ i = 1 k p i \omega_0=\omega(k)=\sum_{i=1}^kp_i ω0=ω(k)=i=1kpi
  • μ 0 \mu_0 μ0: 前景 C 0 C_0 C0组灰度平均值 μ 0 = μ ( k ) = μ ( k ) ω ( k ) = ∑ i = 1 k i ⋅ p i ω 0 \mu_0=\mu(k)=\frac{\mu(k)}{\omega(k)}=\sum_{i=1}^k\frac{i\cdot p_i}{\omega_0} μ0=μ(k)=ω(k)μ(k)=i=1kω0ipi
  • ω 1 \omega_1 ω1: 背景 C 1 C_1 C1组像素出现的概率 ω 1 = 1 − ω 0 = ∑ i = k + 1 m p i \omega_1=1-\omega_0=\sum_{i=k+1}^m p_i ω1=1ω0=i=k+1mpi
  • μ 1 \mu_1 μ1: 背景 C 1 C_1 C1组灰度平均值 μ 1 = μ − μ ( k ) 1 − ω ( k ) = ∑ i = k + 1 m i ⋅ p i ω 1 \mu_1=\frac{\mu-\mu(k)}{1-\omega(k)}=\sum_{i=k+1}^m\frac{i\cdot p_i}{\omega_1} μ1=1ω(k)μμ(k)=i=k+1mω1ipi
  • μ \mu μ: 整个图像灰度平均值 μ = ω 0 μ 0 + ω 1 μ 1 \mu=\omega_0\mu_0+\omega_1\mu_1 μ=ω0μ0+ω1μ1
  • δ 2 \delta^2 δ2: 两组间方差 δ 2 = ω 0 ( μ 0 − μ ) 2 + ω 1 ( μ 1 − μ ) 2 = ω 0 ω 1 ( μ 1 − μ 0 ) 2 \delta^2=\omega_0(\mu_0-\mu)^2+\omega_1(\mu_1-\mu)^2=\omega_0\omega_1(\mu_1-\mu_0)^2 δ2=ω0(μ0μ)2+ω1(μ1μ)2=ω0ω1(μ1μ0)2

我们需要做的是求出一个 k k k值,使得两组间方差 δ 2 \delta^2 δ2最大,这就是图像进行二值分割的阈值,可以得到最理想的效果.

实现

定义灰度直方图数组grayhist,灰度有16级,32级,64级三种级次,而灰度图一个像素用8位数据(0~255)表示,64级灰度即把256分为64级,故用嵌套for循环遍历整个图像,读取其值再除以4得到灰度值(右移2位),同时求总灰度值和像素数量,这里步长为形参step并传入10可节省计算量.

for~for~
temp_this_pixel = img[n] >> 2;
if (temp_this_pixel < 64)
    grayhist[temp_this_pixel]++;
gray_sum_all += temp_this_pixel;
px_sum_all++;

接下来用迭代法求取令两组间方差fCal_var最大的k

for
    w0 = px_sum / px_sum_all;
    w1 = 1 - w0;
    u0 = gray_sum / px_sum;
    u1 = (gray_sum_all - gray_sum) / (px_sum_all - px_sum);
    fCal_var = w0 * w1 * (u0 - u1) * (u0 - u1);
// 找到使方差最大的k值
if (fCal_var > fTemp_maxvar)
    fTemp_maxvar = fCal_var;
    temp_best_th = k;

这样求得的 k k k值是64灰度级次下的k,需要返回256灰度下的阈值return k*4.

这样得到的阈值可以用if和else来进行二值化了

if(temp >= threshold)
    bitmap = 1;
else
    bitmap = 0;

代码

int img_otsu(uint8_t *img, uint8_t img_v, uint8_t img_h)
{
    uint8_t grayhist[64] = {0}; //灰度直方图
    uint16_t px_sum_all = 0;             //像素点总数
    uint32_t gray_sum_all = 0;           //总灰度积分
    uint16_t px_sum = 0;                 //像素点数量
    uint32_t gray_sum = 0;               //灰度积分
	float fCal_var;

    //生成:1. 灰度直方图 2. 像素点总数 3. 总灰度积分
    for (int i = 0; i < img_v; i += 10)
    {
        for (int j = 0; j < img_h; j += 10)
        {
            temp_this_pixel = img[i * img_h + j] /4;
            grayhist[temp_this_pixel]++;
            gray_sum_all += temp_this_pixel;
            px_sum_all++;
        }
    }
    //迭代求得最大类间方差的阈值
    float fTemp_maxvar = 0;
    uint8_t temp_best_th = 0;
    uint8_t temp_this_pixel = 0;
    float u0, u1, w0, w1;
    for (uint8_t k = 0; k < 64; k++)
    {
        px_sum += grayhist[k];       //该灰度及以下的像素点数量
        gray_sum += k * grayhist[k]; //该灰度及以下的像素点的灰度和
        w0 = 1.0 * px_sum / px_sum_all;
        w1 = 1.0 - w0;
        u0 = 1.0 * gray_sum / px_sum;
        u1 = 1.0 * (gray_sum_all - gray_sum) / (px_sum_all - px_sum);
        fCal_var = w0 * w1 * (u0 - u1) * (u0 - u1);
        if (fCal_var > fTemp_maxvar)
        {
            fTemp_maxvar = fCal_var;
            temp_best_th = k;
        }
    }
    return temp_best_th  * 4;
}

请添加图片描述

猜你喜欢

转载自blog.csdn.net/weixin_46143152/article/details/122623331