大津法
大津法又叫最大类间方差法,由日本学者大津展之在1979年提出的对图像二值化高效算法.
对于一帧灰度图像,一个像素由八位0~255表示,单片机是很难实时处理这么大的数据的,我们需要通过一个阈值将其二值化,小于这个阈值的像素判定黑色,大于的判定白色.
摄像头视野内,最主要的颜色就是赛道白色和蓝布蓝色,对于总钻风获取的灰度图也可以通过灰度直方图来区分,下图是photoshop提取的灰度直方图,可以非常明显地看到两个峰,我们需要找到一个值(一般在两峰之间),使这个值两边间类方差最大.
原理
将直方图在一阈值分割为两组,使这两组之间方差最大.
对一张图像
-
m
m
m: 灰度值为
1
~
m
1\sim m
1~m级
-
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=0n?xi?
-
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?=∣1~k∣和背景
C
1
=
∣
k
+
1
~
m
∣
C_1=|k+1\sim m|
C1?=∣k+1~m∣两组,得到
-
ω
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=1k?pi?
-
μ
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?ω0?i?pi??
-
ω
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+1m?pi?
-
μ
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?ω1?i?pi??
-
μ
\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;
}
|