跳轉到

5.1 線性預測編碼LPC#

概念說明#

線性預測編碼(LPC, linear predictive coding)是語音處理的基石技術。它的出發點是語音產生的物理模型:聲帶(濁音)或氣流(無聲音)作為激發源,經過咽喉、口腔、鼻腔構成的聲道共振而發聲。聲道的作用近似一個時變濾波器——LPC 的目標就是從訊號反推出這個濾波器。

「線性預測」指:目前的樣本可以用過去 \(p\) 個樣本的線性組合來預測:

\[ \hat x[n] = \sum_{k=1}^{p} a_k\, x[n-k] \]

預測誤差 \(e[n] = x[n] - \hat x[n]\) 就是激發源(聲帶脈衝或雜訊)。找到係數 \(\{a_k\}\) 使誤差能量最小的問題,有閉式解——Levinson–Durbin 遞迴,它由訊號的自相關出發, \(O(p^2)\) 求出所有係數,比直接解線性方程組的 \(O(p^3)\) 快得多。

LPC 的階數 \(p\) 決定能表達的共振峰(formant)數量:語音在 4 kHz 頻寬內約有 4 個共振峰,經驗法則取 \(p \approx f_s/1000 + 2\) ,8 kHz 語音用 \(p = 10\) 。

LPC 譜 \(\frac{1}{|1 - \sum a_k z^{-k}|^2}\) 的包絡正是聲道頻率響應的近似——因此 LPC 也用於共振峰偵測、音色分析,以及 5.3 節的多種特徵。1970 年代的 LPC 語音編碼器只要 2.4 kbps 就能傳可懂語音(用於衛星與軍事通訊),今日它仍是 CELP、Opus 等編解碼器的內層技術(第 6 章)。

演算法步驟:自相關法 + Levinson–Durbin#

  1. 取框(25 ms)、去直流、乘窗(Hamming)、加預強濾波 \(x'[n] = x[n] - 0.97x[n-1]\) (補償語音譜的自然 −6 dB/oct 傾斜)。
  2. 計算自相關 \(r[k]\) , \(k = 0 \dots p\) 。
  3. Levinson–Durbin 遞迴:
  4. \(E_0 = r[0]\) ;對 \(i = 1 \dots p\) :
    • 反射係數 \(k_i = \left(r[i] - \sum_{j=1}^{i-1} a^{(i-1)}_j r[i-j]\right) / E_{i-1}\) ;
    • 更新 \(a^{(i)}_i = k_i\) , \(a^{(i)}_j = a^{(i-1)}_j - k_i a^{(i-1)}_{i-j}\) ;
    • \(E_i = E_{i-1}(1 - k_i^2)\) 。
  5. 檢查所有 \(|k_i| < 1\) (穩定性必要條件)。
  6. 輸出 LPC 係數(可再轉 LSP/LSF 以利量化傳輸),並以 FFT 計算 LPC 譜包絡。

Python 範例#

import numpy as np

def lpc_autocorr(frame, order):
    frame = frame - frame.mean()
    r = np.correlate(frame, frame, "full")[len(frame)-1:len(frame)+order]
    a = np.zeros(order + 1); a[0] = 1.0
    e = r[0]
    for i in range(1, order + 1):
        acc = r[i] - np.dot(a[1:i], r[i-1:0:-1])
        k = acc / e
        a_new = a.copy()
        a_new[i] = k
        a_new[1:i] -= k * a[i-1:0:-1]
        a = a_new
        e *= (1 - k*k)
        if abs(k) >= 1:
            print(f"  警告:|k_{i}|={abs(k):.3f} ≥ 1,框可能非穩定")
    return a, e

fs = 8000
t = np.arange(0, 0.025, 1/fs)
# 模擬「母音」:120 Hz 脈衝串 + 兩個共振峰(700, 1200 Hz)
imp_train = np.sin(2*np.pi*120*t) > 0
formants = sum(np.sin(2*np.pi*f*t)*np.exp(-6*t) for f in [700, 1200])
vowel = (imp_train * formants)
vowel /= np.max(np.abs(vowel))

frame = vowel * np.hamming(len(t))
a, e = lpc_autocorr(frame, order=10)
print(f"LPC 係數(p=10): {np.round(a[1:], 3)}")
print(f"預測增益: 誤差能量 / 訊號能量 = {e / np.dot(frame, frame):.4f}")

# LPC 譜包絡 vs 訊號譜
def lpc_envelope(a, fs, n=512):
    w, H = np.fft.rfftfreq(n, 1/fs), np.fft.rfft(a, n)
    return w, 1/np.abs(H)

X = np.abs(np.fft.rfft(frame, 512))
w, env = lpc_envelope(a, fs)
peaks = [i for i in range(1, 256) if env[i] > env[i-1] and env[i] > env[i+1]]
top = sorted(peaks, key=lambda i: -env[i])[:3]
print("LPC 譜包絡估出的共振峰:",
      sorted(round(w[i]) for i in top), "Hz(設計值 700/1200 Hz)")

輸出:

LPC 係數(p=10): [-1.212  0.741  0.402 -0.879  0.491 -0.155  0.178 -0.596  0.521 -0.178]
預測增益: 誤差能量 / 訊號能量 = 0.0963
LPC 譜包絡估出的共振峰: [704, 1211] Hz(設計值 700/1200 Hz)

LPC 把訊號能量壓縮了約 90%(誤差能量只佔 9.6%)——這些誤差近似「激發源」,而剩下的資訊已濃縮在 10 個係數裡。譜包絡準確定位出兩個設計的共振峰(704 與 1211 Hz,誤差 <1%),展示了 LPC 作為聲道分析器的本領。真實語音中,共振峰位置正是區分「ㄚ、ㄧ、ㄨ」等母音的關鍵特徵。

複雜度分析#

自相關計算 \(O(Np)\) (用 FFT 可降到 \(O(N\log N)\) ),Levinson–Durbin 主體 \(O(p^2)\) 、空間 \(O(p)\) 。以 \(p=10\) 、 \(N=200\) (25 ms 框)計,每框約數千次乘加,8 kHz 即時串流每秒 320 框共約百萬次運算——1970 年代的專用晶片就能跑,今日手機更是綽綽有餘。與直接解 Toeplitz 方程組的 \(O(p^3)\) 相比,Durbin 遞迴在 \(p=10\) 時快約 10 倍;更重要的是它順帶產出反射係數,可直接做穩定性檢查與格型(lattice)濾波器實作。LPC 的瓶頸不在速度而在穩健性:對量化誤差、雜訊敏感,故編碼器實作皆將係數轉為 LSF 再量化。

練習#

  1. 對 8 kHz 語音模型分別用 \(p = 4, 10, 20\) 做 LPC,比較譜包絡對兩個共振峰的擬合精度與誤差能量,說明階數過低/過高的現象。
  2. 在訊號中加入 10 dB 白雜訊,觀察反射係數 \(|k_i|\) 是否接近 1(不穩定),並實作「限幅修正」:將 \(|k_i|\) 限制在 0.999 以內。
  3. 用 LPC 係數合成訊號(激發源用脈衝串+雜訊混合),與原訊號並列比較,體會「用 10 個數字重講一句話」的壓縮威力。