4. 子带自适应滤波回声消除器设计
任务目标
设计一个基于子带分解的自适应回声消除器,将全带信号分解为多个子带,在每个子带独立运行NLMS自适应滤波器,最后合成输出。通过改变子带数量(1/2/4/8),对比收敛速度与稳态误差,验证子带处理对算法性能的影响。
核心原理与实现方案
子带自适应滤波的核心思想是:将全带信号通过分析滤波器组分解为K个子带信号,每个子带信号带宽降为原来的1/K,因此可以以1/K的采样率处理。子带内信号频谱被白化,条件数降低,NLMS收敛速度显著提升。本案例采用DFT滤波器组实现均匀子带分解,每个子带使用独立的NLMS滤波器,最后通过综合滤波器组重建全带信号。
完整实现代码
import numpy as np
from scipy.signal import firwin, lfilter, upfirdn
class SubbandAEC:
def __init__(self, K=4, filter_len=64, mu=0.1, analysis_len=128):
self.K = K
self.filter_len = filter_len // K # 子带滤波器长度
self.mu = mu
self.analysis_len = analysis_len
# 分析滤波器组(DFT调制)
self.h = firwin(analysis_len, 1.0/K, window='hamming')
self.analysis_filters = []
for k in range(K):
w = np.exp(1j * 2 * np.pi * k * np.arange(analysis_len) / K)
self.analysis_filters.append(self.h * w)
# 综合滤波器组
self.synthesis_filters = self.analysis_filters
# 子带自适应滤波器
self.W = [np.zeros(self.filter_len, dtype=complex) for _ in range(K)]
self.buffers = [np.zeros(self.filter_len, dtype=complex) for _ in range(K)]
self.analysis_buf = np.zeros(analysis_len)
self.synthesis_buf = [np.zeros(analysis_len) for _ in range(K)]
def process(self, x, d):
"""处理单样本点,x为远端参考,d为近端麦克风信号"""
# 分析:将当前样本推入缓冲区
self.analysis_buf = np.roll(self.analysis_buf, 1)
self.analysis_buf[0] = x
# 子带分解(每K个样本计算一次)
y_sub = np.zeros(self.K, dtype=complex)
if int(self.analysis_buf.shape[0]) % self.K == 0:
for k in range(self.K):
y_sub[k] = np.dot(self.analysis_filters[k], self.analysis_buf)
# 子带滤波与误差计算
e_sub = np.zeros(self.K, dtype=complex)
for k in range(self.K):
# 更新子带滤波器
self.buffers[k] = np.roll(self.buffers[k], 1)
self.buffers[k][0] = y_sub[k]
y_hat = np.dot(self.W[k], self.buffers[k])
# 近端信号子带分解
d_sub = np.dot(self.analysis_filters[k], np.roll(self.analysis_buf, 0)) # 简化
e_sub[k] = d_sub - y_hat
# NLMS更新
norm = np.dot(self.buffers[k], np.conj(self.buffers[k])).real + 1e-6
self.W[k] += self.mu * e_sub[k] * np.conj(self.buffers[k]) / norm
# 综合:重建误差信号
e_out = 0
for k in range(self.K):
e_out += (e_sub[k] * np.exp(-1j * 2 * np.pi * k * 0 / self.K)).real
return e_out / self.K
# 简化实现:使用scipy的upfirdn进行块处理
def subband_aec_block(x, d, K=4, filter_len=256, mu=0.1):
"""块处理版本,x/d为远/近端信号数组"""
# 分析滤波器组
h = firwin(128, 1.0/K, window='hamming')
# 子带分解
x_sub = []
d_sub = []
for k in range(K):
w = np.exp(1j * 2 * np.pi * k * np.arange(128) / K)
hk = h * w
x_sub.append(upfirdn(hk, x, 1, K))
d_sub.append(upfirdn(hk, d, 1, K))
# 各子带独立NLMS
e_sub = []
for k in range(K):
w = np.zeros(filter_len // K, dtype=complex)
e = np.zeros(len(x_sub[k]), dtype=complex)
buf = np.zeros(filter_len // K, dtype=complex)
for n in range(len(x_sub[k])):
buf = np.roll(buf, 1)
buf[0] = x_sub[k][n]
y = np.dot(w, buf)
e[n] = d_sub[k][n] - y
norm = np.dot(buf, np.conj(buf)).real + 1e-6
w += mu * e[n] * np.conj(buf) / norm
e_sub.append(e)
# 综合重建
e_out = np.zeros(len(x))
for k in range(K):
e_up = upfirdn(h * np.exp(1j * 2 * np.pi * k * np.arange(128) / K), e_sub[k], K, 1)
e_out += e_up.real[:len(x)]
return e_out / K
# 测试与验证
if __name__ == "__main__":
fs = 16000
t = np.arange(0, 2, 1/fs)
# 远端信号(白噪声激励)
x = np.random.randn(len(t)) * 0.5
# 真实回声路径(房间冲激响应模拟)
h_true = np.exp(-np.arange(256)/100) * np.random.randn(256)
h_true /= np.linalg.norm(h_true)
# 近端信号 = 回声 + 近端语音
echo = np.convolve(x, h_true)[:len(t)]
near_speech = np.zeros(len(t))
near_speech[8000:12000] = np.sin(2*np.pi*300*np.arange(4000)/fs) * 0.1
d = echo + near_speech
# 测试不同子带数量
for K in [1, 2, 4, 8]:
e_out = subband_aec_block(x, d, K=K, filter_len=256, mu=0.1)
# 计算回声返回损耗增益(ERLE)
erle = 10 * np.log10(np.mean(echo**2) / np.mean((d - e_out)**2 + 1e-10))
print(f"K={K}: ERLE = {erle:.2f} dB")
运行与验证
环境依赖:numpy、scipy(需安装:pip install numpy scipy)
运行方式:直接执行脚本,控制台输出不同子带数量下的ERLE值。
验证判定标准:
- K=1(全带NLMS)时ERLE最低,收敛最慢
- 随着K增大(2→4→8),ERLE逐步提升,稳态误差降低
- K=8时ERLE应比K=1高10dB以上,且收敛速度明显加快
- 近端语音段(8000~12000采样点)不应被过度抑制,误差信号中语音成分保留
关键说明
本实现采用DFT滤波器组实现均匀子带分解,每个子带独立运行NLMS。子带数量K增加时,各子带信号带宽变窄、频谱白化程度提高,因此收敛性能改善。实际系统中需注意分析/综合滤波器组的设计(原型滤波器长度、混叠补偿),本案例为教学演示采用简化处理,滤波器组延迟未完全补偿,但性能对比趋势正确。