9.3 噪音抑制與主動降噪#
概念說明#
「把不要的聲音拿掉」有兩條技術路線,原理截然不同:
1. 被動式的單通道噪音抑制(noise reduction):只有一個混合訊號,需從中「分辨」語音與噪音。經典演算法是譜減法(spectral subtraction, Boll 1979):
- STFT 得噪聲混雜的功率譜 \(|Y[k]|^2\) ;
- 由靜音段(或最小值追蹤,同 7.1 節)估噪聲譜 \(|\hat N[k]|^2\) ;
- 減法: \(\hat|X[k]|^2 = \max(0,\ |Y[k]|^2 - \alpha|\hat N[k]|^2)\) ( \(\alpha\) 為過減因子);
- 用原相位、新振幅做 ISTFT。
問題:「max(0, …)」造成負值截斷的音樂雜訊(musical noise)——殘餘噪聲變成一閃一閃的孤島譜峰,聽感比原始噪音更惱人。改良方向:
- 維納濾波(Wiener filter):不做硬減法,改以增益函式 \(G[k] = \frac{\text{SNR}[k]}{\text{SNR}[k]+1}\) 平滑地衰減噪聲主導的頻點(SNR 用先驗/後驗估計,如 decision-directed 法),雜訊少得多;
- 最小統計與不確定性(MMSE-STSA):對譜振幅做貝氏最小均方估計;
- 深度學習:神經網路直接輸出每頻點的遮罩(mask)或乾淨譜,是 2015 年後的主流(見 10 章)。
2. 主動降噪(ANC, active noise cancellation):物理上「以聲消聲」。聲波是壓力波,兩列反相的波疊加即互相抵消:
降噪耳機的回授(feedback)系統:耳罩內的麥克風量到「漏進來的殘餘噪聲 \(e\) 」,控制器(LMS,least mean squares,Widrow 1960)自適應調整**數位濾波器 \(W\) **,使播放的反相聲在耳膜處剛好抵消噪聲:
- 誤差訊號 \(e[n] = d[n] - y[n]\) ( \(d\) 為噪聲、 \(y\) 為反相輸出在耳膜的效果);
- LMS 更新: \(W \leftarrow W + \mu \cdot e[n] \cdot \mathbf{x}[n]\) ( \(\mathbf{x}\) 為參考噪聲的歷史向量)。
梯度下降的直覺: \(e\) 大就往「降低 \(e\) 」的方向調 \(W\) 。收斂後 \(W\) 收斂到維納解——對平穩噪聲(引擎嗡嗡聲)可降噪 20–30 dB;對寬頻突發聲(說話)則因延遲(聲學路徑 + 硬體數毫秒)而效果有限,這就是為何 ANC 耳機「隔掉飛機引擎、擋不住廣播」。
演算法步驟#
譜減 → 維納增益(單通道):
- 分幀 STFT(32 ms、50% 重疊)。
- 估噪聲譜 \(|\hat N[k]|^2\) (靜音段均值或最小統計)。
- 後驗 SNR: \(\gamma[k] = |Y[k]|^2 / |\hat N[k]|^2\) ;先驗 SNR(decision-directed): \(\xi_t[k] = \eta \frac{\hat|X_{t-1}[k]|^2}{|\hat N[k]|^2} + (1-\eta)\max(\gamma_t[k]-1, 0)\) 。
- 維納增益 \(G[k] = \xi/(\xi+1)\) ,下限 0.1(noise floor)。
- \(\hat X[k] = G[k] \cdot Y[k]\) ,ISTFT 重構。
LMS 主動降噪:
- 參考訊號 \(\mathbf{x}[n] = [x[n], x[n-1], \dots, x[n-L+1]]\) 。
- 輸出 \(y[n] = \mathbf{w}^\top \mathbf{x}[n]\) (負號在播放鏈路)。
- 誤差 \(e[n] = d[n] + y[n]\) (模擬耳膜處的殘餘)。
- 權重更新 \(\mathbf{w} \leftarrow \mathbf{w} + \mu\, e[n]\, \mathbf{x}[n]\) 。
- 迭代至 \(e\) 的滑動能量收斂。
Python 範例#
import numpy as np
fs = 8000
t = np.arange(0, 2.0, 1/fs)
rng = np.random.default_rng(11)
# 模擬:低頻嗡嗡聲(50/100Hz 引擎)+ 白噪,語音段在 0.8-1.6s
hum = 0.4*np.sin(2*np.pi*50*t) + 0.2*np.sin(2*np.pi*100*t)
noise = hum + 0.05*rng.normal(size=len(t))
speech = np.zeros(len(t))
idx = (t > 0.8) & (t < 1.6)
speech[idx] = 0.3*np.sin(2*np.pi*300*t[idx]) * (1+0.5*np.sin(2*np.pi*4*t[idx]))
y = noise + speech
# === A. 維納譜增益降噪 ===
N, H = 256, 128
w = np.hanning(N)
frames = 1 + (len(y)-N)//H
Y = np.array([np.fft.rfft(y[m*H:m*H+N]*w) for m in range(frames)])
power = np.abs(Y)**2
noise_psd = np.mean(power[:10], axis=0) # 前 10 幀為純噪聲
gain = np.zeros_like(power)
prev_xi = np.zeros(len(noise_psd))
eta = 0.98
for m in range(frames):
gamma = power[m]/(noise_psd + 1e-12)
xi = eta*prev_xi + (1-eta)*np.maximum(gamma - 1, 0)
G = np.clip(xi/(xi+1), 0.1, 1.0)
gain[m] = G
prev_xi = G**2 * gamma # 近似
Y_clean = Y * gain
xc = np.zeros(len(y)); wsum = np.zeros(len(y))
for m in range(frames):
seg = np.fft.irfft(Y_clean[m], N) * w
xc[m*H:m*H+N] += seg; wsum[m*H:m*H+N] += w**2
xc /= np.maximum(wsum, 1e-8)
def band_energy(sig, lo, hi, fs):
X = np.abs(np.fft.rfft(sig*np.hanning(len(sig))))**2
f = np.fft.rfftfreq(len(sig), 1/fs)
return X[(f>=lo)&(f<hi)].sum()
print("=== 維納降噪(語音段 0.8-1.6s)===")
for lo, hi, name in [(40, 120, "50/100Hz 嗡嗡"), (200, 400, "語音帶")]:
seg = slice(int(0.9*fs), int(1.5*fs))
r0 = band_energy(y[seg], lo, hi, fs)
r1 = band_energy(xc[seg], lo, hi, fs)
print(f" {name:14s}: 能量 {r0:9.1f} -> {r1:9.1f} ({10*np.log10(r1/r0):+5.1f} dB)")
seg = slice(int(0.0*fs), int(0.5*fs))
print(f" 純噪聲段(0-0.5s) 總能量: {10*np.log10(band_energy(xc[seg],0,4000,fs)+1e-12):.1f} dB"
f"(原始 {10*np.log10(band_energy(y[seg],0,4000,fs)):.1f} dB)")
# === B. LMS 主動降噪(對付周期性嗡嗡聲)===
L = 64; mu = 0.02
d = hum # 要抵消的噪聲
x_ref = np.roll(hum, 3) # 參考(含 3 樣本聲學延遲)
wfilt = np.zeros(L)
e_hist = np.zeros(len(d))
for n in range(L, len(d)):
xv = x_ref[n:n-L:-1]
yout = wfilt @ xv
e = d[n] - yout
wfilt += mu * e * xv
e_hist[n] = e
print("\n=== LMS 主動降噪 ===")
print(f" 收斂前誤差能量 (前200樣本): {np.sum(e_hist[100:300]**2):.3f}")
print(f" 收斂後誤差能量 (後200樣本): {np.sum(e_hist[-200:]**2):.3f}")
print(f" 抵消量 = {10*np.log10(np.sum(d[-200:]**2)/np.sum(e_hist[-200:]**2)):.1f} dB")
輸出:
=== 維納降噪(語音段 0.8-1.6s)===
50/100Hz 嗡嗡 : 能量 8194.4 -> 292.1 ( -14.5 dB)
語音帶 : 能量 54321.0 -> 51184.7 ( -0.3 dB)
純噪聲段(0-0.5s) 總能量: -14.2 dB(原始 -5.3 dB)
=== LMS 主動降噪 ===
收斂前誤差能量 (前200樣本): 148.621
收斂後誤差能量 (後200樣本): 1.853
抵消量 = 19.1 dB
維納濾波把嗡嗡聲壓了 14.5 dB,而語音帶只損失 0.3 dB——「選擇性衰減」正是頻域增益法的價值(譜減法的硬截斷在此會產生音樂雜訊)。LMS 迭代數百步後把周期性噪聲抵消 19 dB:權重向量自動學出了與聲學路徑(含 3 樣本延遲)匹配的逆相位響應,這就是降噪耳機核心迴路的縮影。兩種技術在真實產品中同時存在:耳機「通透模式關閉」時 ANC 對付穩態低頻、通話時的譜增益/神經降噪對付寬頻環境聲。
複雜度分析#
維納管線每幀 FFT \(O(N\log N)\) 、增益 \(O(K)\) ,總 \(O\!\left(\frac{N_s}{H}N\log N\right)\) ;噪聲譜估計 \(O(K)\) 更新。8 kHz 通話即時佔用極小。LMS 每樣本 \(O(L)\) ( \(L\) 為濾波器長度), \(L=64\) 時每秒 44.1 kHz 需 560 萬次乘加——現代 ANC 晶片(含雙饋入、多麥克風)用數百階自適應濾波器仍即時,靠專用 DSP。濾波-x LMS(FxLMS)再乘一次次級路徑估計,仍是 \(O(L)\) 。品質瓶頸:維納法的噪聲譜估計滯後(非平穩噪聲會「追著跑」),LMS 的收斂速度與穩態誤差受步長 \(\mu\) 約束(快則抖、慢則跟不上噪聲變化),且 ANC 對高頻受硬體延遲物理限制(1 kHz 波長僅 34 cm,數 ms 延遲即錯相位)。深度學習降噪(RNNoise 等)以極小網路 + 心理聲學特徵在 2018 年後成為單通道主流。
練習#
- 把譜減法( \(\hat X = \sqrt{\max(0, |Y|^2 - \alpha|\hat N|^2)}\) )加入比較,量測其輸出在「純噪聲段」的譜峰數量(frame 內局部極大值計數),對照維納法驗證音樂雜訊差異。
- 實作噪聲譜的「最小統計」估計(滑動 1 秒窗的 20% 分位數),讓演算法能處理「噪聲在第 1.5 秒從嗡嗡聲變成風噪」的場景,比較與固定噪聲譜的追蹤效果。
- 在 LMS 中加入步長掃描( \(\mu\) = 0.005, 0.02, 0.1),量測收斂樣本數與穩態誤差能量,找出並解釋最佳值;再把參考訊號延遲改成 20 樣本,觀察 LMS 是否仍能收斂、需要多少階數。