OTSU算法/大津法/最大类间方差法 python实现

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,...,L1
根据图像像素数 N N N 和灰度范围 [ 0 , L − 1 ] [0,L-1] [0,L1] 求出每个灰度级的概率
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,...,L1
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,L1] 之间的像素组成。
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=Ni=0L1ini=N/N(i=0L1ini)/N=i=0L1iPi
同理我们可以得到 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=N0i=0Tini=i=0TPii=0TiPi=w0i=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=N1i=T+1L1ini=i=T+1L1Pii=T+1L1iPi=w1i=T+1L1iPi

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(u0u)2+w1(u1u)2=w0w1(u0u1)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()


参考:

  • 大津二值化算法OTSU的理解
  • 详细及易读懂的 大津法(OTSU)原理 和 比opencv自带更快的算法实现

你可能感兴趣的:(计算机视觉,图像处理,python)