OTSU算法/大津法/最大类间方差法 python实现
1. 介绍
OTSU算法是由日本学者大津于1979年提出的一种对图像进行二值化的高效算法,也称为大津法,最大类间方差法。它根据图像的灰度特性而分为背景和前景两部分,背景和前景之间的像素值波动性越大,说明这两部分的差别越大,反之,差别越小。简而言之,算法根据图像的信息自适应地确定二值化阈值。
2. 算法原理
假设输入图像为灰度图像
I
I
I ,图像宽高分别为
w
w
w 和
h
h
h ,灰度级为
L
L
L 。
则图像包含的像素总数为
N
=
w
×
h
N=w \times h
N=w×h
灰度范围
i
=
0
,
1
,
2
,
.
.
.
,
L
−
1
i=0,1,2,...,L-1
i=0,1,2,...,L−1
根据图像像素数
N
N
N 和灰度范围
[
0
,
L
−
1
]
[0,L-1]
[0,L−1] 求出每个灰度级的概率
P
i
=
n
i
N
,
i
=
0
,
1
,
2
,
.
.
.
,
L
−
1
P_i=\frac{n_i}{N},i=0,1,2,...,L-1
Pi=Nni,i=0,1,2,...,L−1
n
i
n_i
ni表示灰度级i的像素个数。
假设一个阈值
T
T
T ,我们用这个阈值把图像分为了两类
C
0
C_0
C0 ,
C
1
C_1
C1,其中
C
0
C_0
C0 由
[
0
,
T
]
[0,T]
[0,T] 之间的像素组成,
C
1
C_1
C1 由
[
T
+
1
,
L
−
1
]
[T+1,L-1]
[T+1,L−1] 之间的像素组成。
设
N
0
N_0
N0 为小于阈值的像素的个数,
N
1
N_1
N1 为大于阈值的像素的个数,则有
N
=
N
0
+
N
1
N=N_0+N_1
N=N0+N1
定义图像均值为
u
=
∑
i
=
0
L
−
1
i
n
i
N
=
(
∑
i
=
0
L
−
1
i
n
i
)
/
N
N
/
N
=
∑
i
=
0
L
−
1
i
P
i
u=\frac{\sum_{i=0}^{L-1}in_i}{N}=\frac{(\sum_{i=0}^{L-1}in_i)/N}{N/N}=\sum_{i=0}^{L-1}iP_i
u=N∑i=0L−1ini=N/N(∑i=0L−1ini)/N=i=0∑L−1iPi
同理我们可以得到
C
0
C_0
C0 和
C
1
C_1
C1 的均值
u
0
u_0
u0 和
u
1
u_1
u1
u
0
=
∑
i
=
0
T
i
n
i
N
0
=
∑
i
=
0
T
i
P
i
∑
i
=
0
T
P
i
=
∑
i
=
0
T
i
P
i
w
0
u_0=\frac{\sum_{i=0}^{T}in_i}{N_0}=\frac{\sum_{i=0}^{T}iP_i}{\sum_{i=0}^{T}P_i}=\frac{\sum_{i=0}^{T}iP_i}{w_0}
u0=N0∑i=0Tini=∑i=0TPi∑i=0TiPi=w0∑i=0TiPi
u
1
=
∑
i
=
T
+
1
L
−
1
i
n
i
N
1
=
∑
i
=
T
+
1
L
−
1
i
P
i
∑
i
=
T
+
1
L
−
1
P
i
=
∑
i
=
T
+
1
L
−
1
i
P
i
w
1
u_1=\frac{\sum_{i=T+1}^{L-1}in_i}{N_1}=\frac{\sum_{i=T+1}^{L-1}iP_i}{\sum_{i=T+1}^{L-1}P_i}=\frac{\sum_{i=T+1}^{L-1}iP_i}{w_1}
u1=N1∑i=T+1L−1ini=∑i=T+1L−1Pi∑i=T+1L−1iPi=w1∑i=T+1L−1iPi
w 0 + w 1 = 1 w_0+w_1=1 w0+w1=1
初看定义图像均值的公式是不是感觉有点云里雾里?又是概率的又是 w 0 w_0 w0 和 w 1 w_1 w1 之类的。但是仔细观察中间步骤,你会发现所谓图像均值 u u u ,其实就是所有像素值之和再除以像素总数。 u 0 u_0 u0 和 u 1 u_1 u1也是同理,只是我们换了形式(用概率)表达而已。
由上式我们也可以得到
u
=
w
0
u
0
+
w
1
u
1
u=w_0u_0+w_1u_1
u=w0u0+w1u1
定义类间方差
δ
2
=
w
0
(
u
0
−
u
)
2
+
w
1
(
u
1
−
u
)
2
=
w
0
w
1
(
u
0
−
u
1
)
2
\delta ^2=w_0(u_0-u)^2+w_1(u_1-u)^2=w_0w_1(u_0-u_1)^2
δ2=w0(u0−u)2+w1(u1−u)2=w0w1(u0−u1)2
代入 u = w 0 u 0 + w 1 u 1 u=w_0u_0+w_1u_1 u=w0u0+w1u1 以及 w 0 + w 1 = 1 w_0+w_1=1 w0+w1=1 可推出等式右边。
选取合适的阈值 T T T ,使得类间方差 δ 2 \delta ^2 δ2最大,则 T T T 为最佳阈值。
像素值小于这个阈值的像素我们设置为0(黑),否则为1(白)。这样我们就得到了二值化图像。
3. Python代码实现
import cv2
import numpy as np
# 大津二值化算法
def otsu(gray_img):
h = gray_img.shape[0]
w = gray_img.shape[1]
N = h * w
threshold_t = 0
max_g = 0
# 遍历每一个灰度级
for t in range(256):
# 使用numpy直接对数组进行运算
n0 = gray_img[np.where(gray_img < t)]
n1 = gray_img[np.where(gray_img >= t)]
w0 = len(n0) / N
w1 = len(n1) / N
u0 = np.mean(n0) if len(n0) > 0 else 0.
u1 = np.mean(n1) if len(n1) > 0 else 0.
g = w0 * w1 * (u0 - u1) ** 2
if g > max_g:
max_g = g
threshold_t = t
print('类间方差最大阈值:', threshold_t)
gray_img[gray_img < threshold_t] = 0
gray_img[gray_img >= threshold_t] = 255
return gray_img
# 读入灰度图
gray_img = cv2.imread('lena.jpg', 0)
otsu_img = otsu(gray_img)
ret, threshold = cv2.threshold(gray_img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
print(np.all(otsu_img == threshold)) # ture,说明和opencv底层实现的大津法效果一致
cv2.imshow('otsu_img ', otsu_img)
cv2.imshow("opencv-python otsu_img", threshold)
cv2.waitKey(0)
cv2.destroyAllWindows()
参考: