OTSU算法是由日本学者大津于1979年提出的一种对图像进行二值化的高效算法,也称为大津法,最大类间方差法。它根据图像的灰度特性而分为背景和前景两部分,背景和前景之间的像素值波动性越大,说明这两部分的差别越大,反之,差别越小。简而言之,算法根据图像的信息自适应地确定二值化阈值。
假设输入图像为灰度图像 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(白)。这样我们就得到了二值化图像。
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()
参考: