2.2 直方圖等化#
概念說明#
有些影像明明包含豐富的資訊,但因為曝光或感光元件特性的問題,像素值集中在很窄的範圍內,看起來灰灰的抬不起頭。直方圖等化 (Histogram Equalization) 的目標是:重新安排像素值,讓新的直方圖盡可能均勻分布在 0~255 的整個範圍。
直官地說:如果原影像中 30% 的像素都擠在值 120 附近,等化會把這 30% 攤開到很大的一段範圍,於是這群本來彼此幾乎相同灰階的區域,變出細微的差異層次,影像看起來細節變多。
其數學核心是累積分布函式 (CDF)。設 p(v) 為像素值 v 出現的機率,則
cdf(v) = Σ_{i=0..v} p(i),累積分布
等化使用的對映函式就是 CDF 本身(乘上 255 取整):
T(v) = floor(255 × cdf(v))
結果的直方圖在連續情況下確實會變成均勻分布;在離散情況下「接近均勻」但並非完美,因為個別值只能整體搬移而無法插值拆分。
本節也要介紹兩個重要變形:
局部/自適應等化 (CLAHE, Contrast-Limited Adaptive Histogram Equalization):把影像切成小磚塊,每塊獨立等化,但限制任何一個 bin 的累計值上限(避免放大雜訊)。CLAHE 在醫學 X 光、水下影像與紅外線影像等區域性對比變化劇烈的情況特別有效。
演算法步驟#
- 統計直方圖 h(v),v = 0…255。
- 計算累積分布 cdf(v) = Σ h(i),將 cdf 正規化到 0…255:cdf_norm = cdf / N × 255。
- 建立查找表 LUT:LUT[v] = floor(cdf_norm(v))。
- 用 LUT 對影像逐像素映射:out[y,x] = LUT[in[y,x]]。
Python 程式碼範例#
import numpy as np
from PIL import Image
def hist_eq(img):
h, _ = np.histogram(img, bins=256, range=(0, 256))
cdf = h.cumsum() # 累積分布
cdf_min = cdf[cdf > 0].min() # 跳過空 bin 使對映更均勻
n = img.size
lut = ((cdf - cdf_min) * 255.0 / (n - cdf_min)).astype(np.uint8)
return lut[img] # 用 LUT 逐像素映射
def clahe_like(img, tiles=(4, 4), clip=400):
H, W = img.shape
rh, rw = H // tiles[0], W // tiles[1]
out = np.zeros_like(img)
for ty in range(tiles[0]):
for tx in range(tiles[1]):
t = img[ty*rh:(ty+1)*rh, tx*rw:(tx+1)*rw]
h, _ = np.histogram(t, bins=256, range=(0, 256))
h = np.minimum(h, clip) # 限制每 bin 上限
cdf = h.cumsum()
lut = (cdf * 255.0 / cdf.max()).astype(np.uint8)
out[ty*rh:(ty+1)*rh, tx*rw:(tx+1)*rw] = lut[t]
return out
img = np.array(Image.open("gradient2.png")) if False else \
np.array((np.linspace(0,255,160).reshape(1,-1) + np.zeros((40,1))).astype(np.uint8))
# 上面造一張水平漸層影像,每列相同,方便觀察等化效果
eq = hist_eq(img)
Image.fromarray(eq).save("eq.png")
h0, _ = np.histogram(img, bins=256, range=(0, 256))
h1, _ = np.histogram(eq, bins=256, range=(0, 256))
print("等化前: 使用 bin 數 =", (h0>0).sum(), "平均 =", f"{img.mean():.1f}")
print("等化後: 使用 bin 數 =", (h1>0).sum(), "平均 =", f"{eq.mean():.1f}")
print("等化後直方圖標準差(越小越均勻):", f"{h1.std():.1f}")
cl = clahe_like(img)
Image.fromarray(cl).save("clahe.png")
print("CLAHE 完成, 尺寸:", cl.shape)
執行後會看到:等化使用到的灰階 bin 數增加,直方圖變得接近均匀(標準差下降);CLAHE 則在局部塊中避免大面積的純色被等化後「翻轉」到錯誤的一對比關係,是處理有陰影照片時更穩的選擇。
複雜度分析#
直方圖統計:O(N)。CDF 與 LUT:O(256) = O(1)。應用 LUT:O(N)。總時間 O(N)、額外空間 O(256),非常有效。CLAHE:分為 t² 塊,每塊 O(tile 面積),加邊緣插值後仍為 O(N)量級。
練習#
- 對你自己的暗照片跑直方圖等化,比較等化前後的直方圖,並解釋為什麼等化可能會放大雜訊。
- 修改 clahe_like,加入「用相鄰塊的 LUT 做雙線性插值」以消除塊狀邊界。
- 彩色影像應該對 RGB 各通道分別等化,還是先轉 HSV/YCbCr 後只對亮度通道等化?寫一個實驗程式驗證你的答案。