跳轉到

5.3 小波轉換簡介#

概念說明#

傅立葉轉換有個根本限制:基底是無限長的正弦波,轉換結果告訴你「影像含有哪些頻率」,卻完全不說「這些頻率出現在哪裡」。一張左半有條紋、右半平緩的影像,與整張都是條紋的影像,頻譜可能非常相似。這就是時頻局部性問題。

小波轉換 (Wavelet Transform) 用「有限長度、可伸縮、可平移」的小波形 (wavelet) 取代無限長的正弦波:把母小波 ψ(x) 拉伸得寬 → 分析低頻;壓縮得窄 → 分析高頻;平移到不同位置 → 查不同區域。於是小波同時擁有空間位置與頻率兩種資訊,解析度隨尺度自適應:低頻看得粗但久、高頻看得細且短——這正是分析影像需要的(輪廓是大尺度、雜訊是小尺度)。

影像處理用的是離散小波轉換 (DWT),最常用 Haar 小波。一維 Haar 轉換極簡單,對長度 N 的訊號:

  • 平均(低通):aᵢ = (x₂ᵢ + x₂ᵢ₊₁)/√2
  • 差分(高通):dᵢ = (x₂ᵢ − x₂ᵢ₊₁)/√2

輸出長度不變:一半是「粗略概要」(approximation),一半是「細節」(detail)。對概要遞迴再做一次,就得到多層分解。二維版本對列、行各做一次,把影像拆成四塊:

  • LL:行列皆低通——縮小一半的模糊影像
  • HL / LH:一個方向低通、另一個高通——垂直/水平邊緣
  • HH:對角細節

LL 可再分解,形成金字塔結構。DWT 是完全可逆的(正交轉換),且天然多解析度:LL+細節 = 原影像,一個係數都不丟。

應用:(1) JPEG 2000 用小波取代 DCT,沒有 8×8 方塊效應,且支援漸進式解碼(先傳 LL,畫面由糊到清);(2) 去雜訊:雜訊能量散在所有小細節係數,把小於門檻的細節係數「縮掉」(shrinkage/thresholding, Donoho 1995) 再逆轉換,能在保留邊緣的同時除雜——這是小波最漂亮的應用;(3) 影像融合、指紋壓縮(FBI 標準)。

演算法步驟(Haar 二維一層分解)#

  1. 對影像每一列做 Haar:相鄰兩像素求平均(低頻)與差分(高頻)。
  2. 對結果每一行再做一次 Haar。
  3. 得到 LL、HL、LH、HH 四個子帶。
  4. (多層)對 LL 遞迴重複。
  5. 逆轉換:由四個子帶反推(加減還原),逐層回溯。

Python 程式碼範例#

import numpy as np
from PIL import Image

def haar2d(img):
    img = img.astype(np.float64)
    H, W = img.shape
    L = (img[:, 0::2] + img[:, 1::2]) / np.sqrt(2)   # 列低通
    Hd = (img[:, 0::2] - img[:, 1::2]) / np.sqrt(2)  # 列高通
    LL = (L[0::2, :] + L[1::2, :]) / np.sqrt(2)
    LH = (L[0::2, :] - L[1::2, :]) / np.sqrt(2)
    HL = (Hd[0::2, :] + Hd[1::2, :]) / np.sqrt(2)
    HH = (Hd[0::2, :] - Hd[1::2, :]) / np.sqrt(2)
    return LL, HL, LH, HH

def ihaar2d(LL, HL, LH, HH):
    H = np.concatenate([LL + HL, LL - HL], axis=0) if False else None
    L = np.zeros((LL.shape[0]*2, LL.shape[1]))
    L[0::2], L[1::2] = LL + LH, LL - LH
    Hd = np.zeros_like(L)
    Hd[0::2], Hd[1::2] = HL + HH, HL - HH
    out = np.concatenate([L, Hd], axis=1)
    return None

def inv_haar2d(LL, HL, LH, HH):
    n = LL.shape[0]*2
    L = np.empty((n, LL.shape[1])); L[0::2] = LL + LH; L[1::2] = LL - LH
    Hd = np.empty_like(L); Hd[0::2] = HL + HH; Hd[1::2] = HL - HH
    out = np.empty((n, n*1)); out = np.empty((n, LL.shape[1]*2))
    out[:, 0::2] = L + Hd; out[:, 1::2] = L - Hd
    return out / np.sqrt(2)

rng = np.random.default_rng(9)
img = np.full((64, 64), 120.0)
img[20:40, 20:40] = 220
img += rng.normal(0, 8, (64, 64))

LL, HL, LH, HH = haar2d(img)
print("LL 尺寸:", LL.shape, " 平均:", LL.mean().round(1), "(=原圖平均)")
print("HL(垂直邊緣) 最大響應:", np.abs(HL).max().round(1))
print("HH(對角) 平均絕對值:", np.abs(HH).mean().round(2), "(多為雜訊)")

# 無損驗證
rec = inv_haar2d(LL, HL, LH, HH)
print("逆轉換最大誤差:", np.abs(rec - img).max())

# 小波去雜訊:縮小 HH/HL/LH 的小係數
T = 8.0
LL2, HL2, LH2, HH2 = LL.copy(), HL.copy(), LH.copy(), HH.copy()
for D in (HL2, LH2, HH2):
    D[np.abs(D) < T] = 0
den = inv_haar2d(LL2, HL2, LH2, HH2)
print("去雜訊前誤差:", np.abs(img - (np.full_like(img,120)+rng.normal(0,8,(64,64)))).std().round(1))
print("去雜訊後與乾淨影像的誤差標準差:", np.abs(den - (np.where((np.mgrid[0:64,0:64][1]>=20)&(np.mgrid[0:64,0:64][0]>=20)&(np.mgrid[0:64,0:64][0]<40)&(np.mgrid[0:64,0:64][1]<40),220,120))).std().round(2))

執行後:LL 是原圖的一半縮圖且平均值守恆;HH 幾乎只剩雜訊;逆轉換誤差為浮點級的 0(證明可逆);軟門檻去雜訊後誤差標準差明顯低於雜訊本身,且方塊邊緣仍在。

複雜度分析#

一層 Haar 分解:每個像素參與一次加減,O(H·W),且不需乘法(除 √2 可用位移近似)——這是 Haar 的最大賣點。多層分解(L 層):O(H·W·(1 + 1/4 + …)) < O(H·W·4/3),仍是線性。逆轉換對稱。空間 O(H·W)。與 FFT 的比較:FFT 的 O(N log N) 對大訊號較慢於 DWT 的 O(N);DWT 還支援局部解碼(只需 LL 的部分係數即可還原局部區域),這是漸進傳輸的基礎。更高等的小波(Daubechies、雙正交 9/7)在 JPEG 2000 中使用,計算仍是線性濾波器組。

練習#

  1. 對一張影像做三層 Haar 分解,輸出每層 LL 的平均值,驗證各層能量守恆(LL²+HL²+LH²+HH² 總和 = 原影像平方和)。
  2. 實作軟門檻 (soft threshold):sign(D)·max(|D|−T, 0),與硬門檻比較去雜訊後的邊緣剖面上有無階梯狀偽影。
  3. 說明為何 JPEG 2000 用小波就沒有方塊效應。用 5.2 節的 DCT 程式與本節小波壓縮(丟棄小係數)各壓縮一張漸層影像,比較跨邊界的梯度。