跳轉到

2.3 影像二值化與Otsu法#

概念說明#

二值化 (binarization) 是把灰階影像壓縮成只剩 0 與 255 兩種值的操作:

s = 255(若 r ≥ T);s = 0(若 r < T)

其中 T 是門檻值 (threshold)。二值化後的影像叫二值影像 (binary image),是文件掃描(OCR 的前置)、條碼辨識、工件檢測、物件分割的第一步。二值化同時也是一種極端壓縮:每個像素從 8 bits 變成 1 bit。

關鍵問題是:T 要選多少? 門檻太低,背景會被誤判成前景(雜點多);太高,前景會被吃掉(字變細、斷裂)。對簡單影像,人眼可試出合適的 T;但自動化系統需要演算法自己決定。

大津法 (Otsu's method, 1979) 給出經典解答。把影像灰階值想成兩個類別:前景(暗類)與背景(亮類)。給定門檻 t,定義:

  • w₀、w₁:兩類的像素比例(w₀ + w₁ = 1)
  • μ₀、μ₁:兩類的平均灰階值
  • 類間變異數 σ_B²(t) = w₀·w₁·(μ₀ − μ₁)²

σ_B² 衡量「兩個類別分得多開」。Otsu 法窮舉所有可能的門檻 t = 0…255,選出使 σ_B² 最大的那個。直覺上:最好的門檻就是讓「暗的更暗、亮的更亮」分得最開的切點;若影像真的由雙峰分布組成(例如白紙黑字),Otsu 的答案會落在兩峰之間的谷底。

可以證明:σ_B² = σ_T² − σ_W²,總變異數 σ_T² 固定,所以最大化類間變異數等價於最小化類內變異數 σ_W²——也就是讓每個類別內部越均勻越好。兩種視角導出同一答案。

演算法步驟#

  1. 計算影像直方圖 h(v),並算出總像素數 N。
  2. 由直方圖求累積量:對每個 t,算出 w₀(t)、μ₀(t)、μ₁(t)。
  3. 對每個 t = 0…255 計算類間變異數 σ_B²(t)。
  4. 取 σ_B² 最大的 t 為門檻(若並列,取平均)。
  5. 套用門檻:大於等於 T 設 255,其餘設 0。

Python 程式碼範例#

import numpy as np
from PIL import Image

# 建立一張模擬「白紙黑字」的雙峰影像
rng = np.random.default_rng(0)
img = np.concatenate([
    rng.normal(60, 12, (60, 60)),    # 前景:暗、平均 60
    rng.normal(200, 15, (60, 60)),   # 背景:亮、平均 200
]).astype(np.uint8)
print("平均:", img.mean().round(1))

def otsu(img):
    h, _ = np.histogram(img, bins=256, range=(0, 256))
    h = h.astype(np.float64)
    N = h.sum()
    p = h / N
    mu_all = (np.arange(256) * p).sum()          # 全域平均
    w0 = np.cumsum(p)                            # 類別0權重
    mu0 = np.cumsum(np.arange(256) * p)          # 類別0累積平均
    with np.errstate(invalid="ignore", divide="ignore"):
        var_b = (mu_all - mu0) ** 2 * w0 / (1 - w0)  # σ_B²(t)
    var_b[~np.isfinite(var_b)] = 0
    return int(var_b.argmax()), var_b

T, var_b = otsu(img)
print("Otsu 門檻 =", T)
print("最大類間變異數 =", var_b[T].round(1))

binary = np.where(img >= T, 255, 0).astype(np.uint8)
print("二值化後黑像素比例:", (binary == 0).mean().round(3), "(理論約 0.5)")
Image.fromarray(binary).save("binary.png")

# 錯誤門檻的比較
for bad in [20, 100, 240]:
    b = np.where(img >= bad, 255, 0)
    print(f"門檻 {bad}: 黑像素比例 {(b == 0).mean().round(3)}")

執行後 Otsu 會找出門檻約在 130 附近(正好落在 60 與 200 兩個峰之間),二值化後黑白比例接近真實的一半一半;而門檻 20 或 240 會把幾乎全部像素判成同一類,完全失去分割意義。

複雜度分析#

計算直方圖 O(N)。因為利用累積和,掃過 256 個門檻只需 O(256) = O(1),總複雜度 O(N),與影像大小線性。若不累積而對每個門檻重算兩類統計,會退化成 O(256·N);累積和技巧是讓它可行的關鍵。空間上只需直方圖 256 個數,O(1)。注意 Otsu 假設直方圖呈雙峰且雜訊為高斯型;若影像受強不均勻光照(單峰被拉寬),全域門檻會失效,需改用局部自適應門檻(每個小區域各自跑 Otsu)。

練習#

  1. 對一張被光碟陰影弄暗一半的影像跑全域 Otsu,觀察失敗模式;再把影像切成 8×8 區塊逐塊做 Otsu,比較結果。
  2. 修改程式輸出 σ_B²(t) 對 t 的曲線(文字長條即可),確認它只有一個明顯峰。什麼樣的直方圖會讓 σ_B² 出現多個接近的峰?
  3. Otsu 假設兩類服從高斯分布。若前景只佔影像 1%(例如缺陷檢測),推測 Otsu 會怎麼失誤,並驗證你的猜測。