2. 维纳滤波语音降噪
任务目标
实现维纳滤波语音降噪算法,对加性高斯白噪声和粉红噪声污染的语音进行降噪处理,并与谱减法在输出信噪比和主观听感两方面进行对比。
环境依赖
Python 3.8+,需要 numpy、scipy、soundfile 库。若需绘制波形图,额外安装 matplotlib(本案例不强制)。
核心原理与实现
维纳滤波在频域的最小均方误差估计形式为:
H(f) = P_s(f) / (P_s(f) + P_n(f))
其中 P_s 为纯净语音功率谱,P_n 为噪声功率谱。实际中二者未知,采用判决引导法(Decision-Directed)从带噪语音估计先验信噪比 ξ(k),进而得到增益函数:
H(k) = ξ(k) / (1 + ξ(k))
谱减法增益为 G(k) = max(1 - α/SNR_post(k), β),其中 α 为过减因子,β 为谱下限。
完整代码
import numpy as np
import soundfile as sf
from scipy.signal import stft, istft
def add_noise(clean, noise, snr_db):
"""按给定SNR混合语音与噪声"""
clean = clean / np.max(np.abs(clean))
noise = noise / np.max(np.abs(noise))
if len(noise) < len(clean):
noise = np.tile(noise, int(np.ceil(len(clean)/len(noise))))[:len(clean)]
else:
noise = noise[:len(clean)]
p_clean = np.mean(clean**2)
p_noise = np.mean(noise**2)
target_p_noise = p_clean / (10**(snr_db/10))
noise_scaled = noise * np.sqrt(target_p_noise / p_noise)
return clean + noise_scaled
def wiener_filter(x, fs, n_fft=512, hop=128, alpha=0.98, noise_frames=10):
"""维纳滤波降噪(判决引导先验SNR估计)"""
f, t, X = stft(x, fs, nperseg=n_fft, noverlap=n_fft-hop)
X_mag = np.abs(X)
# 用前几帧估计噪声功率谱
noise_pow = np.mean(X_mag[:, :noise_frames]**2, axis=1, keepdims=True)
# 初始化后验SNR
post_snr = np.maximum((X_mag**2 / noise_pow) - 1, 0)
xi = np.maximum(post_snr, 0) # 先验SNR初始值
H = np.ones_like(X_mag)
for i in range(X.shape[1]):
if i > 0:
xi = alpha * (H[:, i-1]**2 * post_snr[:, i-1]) + (1-alpha) * post_snr[:, i]
H[:, i] = xi / (1 + xi)
Y = X * H
_, y = istft(Y, fs, nperseg=n_fft, noverlap=n_fft-hop)
return y[:len(x)]
def spectral_subtraction(x, fs, n_fft=512, hop=128, alpha=2.0, beta=0.01, noise_frames=10):
"""谱减法降噪"""
f, t, X = stft(x, fs, nperseg=n_fft, noverlap=n_fft-hop)
X_mag = np.abs(X)
phase = np.angle(X)
noise_pow = np.mean(X_mag[:, :noise_frames]**2, axis=1, keepdims=True)
post_snr = X_mag**2 / noise_pow
gain = np.maximum(1 - alpha / np.maximum(post_snr, 1e-10), beta)
Y = gain * X_mag * np.exp(1j*phase)
_, y = istft(Y, fs, nperseg=n_fft, noverlap=n_fft-hop)
return y[:len(x)]
def compute_snr(clean, est):
"""计算输出信噪比(dB)"""
noise = clean - est
return 10*np.log10(np.mean(clean**2) / np.mean(noise**2))
if __name__ == "__main__":
# 加载纯净语音(自行准备wav文件)
clean, fs = sf.read("clean.wav")
if len(clean.shape) > 1:
clean = clean[:, 0]
# 生成两种噪声
rng = np.random.default_rng(42)
white = rng.standard_normal(len(clean))
# 粉红噪声(简化生成:对白噪声做低通滤波)
from scipy.signal import butter, lfilter
b, a = butter(1, 0.2)
pink = lfilter(b, a, white)
pink = pink / np.max(np.abs(pink))
for noise_type, noise in [("white", white), ("pink", pink)]:
noisy = add_noise(clean, noise, snr_db=5)
y_wiener = wiener_filter(noisy, fs)
y_sub = spectral_subtraction(noisy, fs)
snr_in = compute_snr(clean, noisy)
snr_w = compute_snr(clean, y_wiener)
snr_s = compute_snr(clean, y_sub)
print(f"{noise_type}噪声: 输入SNR={snr_in:.2f}dB, 维纳输出={snr_w:.2f}dB, 谱减输出={snr_s:.2f}dB")
sf.write(f"wiener_{noise_type}.wav", y_wiener, fs)
sf.write(f"sub_{noise_type}.wav", y_sub, fs)
运行与验证
- 准备一段干净语音文件命名为
clean.wav(建议2~3秒,采样率16kHz),置于脚本同目录。 - 运行脚本:
python wiener_demo.py。 - 观察终端输出的SNR数值,并试听生成的
wiener_white.wav、sub_white.wav等文件。
通过判定标准:
- 两种噪声下,维纳滤波和谱减法的输出SNR均高于输入SNR(约5dB),说明降噪有效。
- 维纳滤波输出SNR通常高于谱减法2~4dB,且残留音乐噪声明显更少(主观听感更自然)。
- 若输出SNR反而下降,检查
noise_frames是否取到静音段,或调整alpha(维纳)与alpha/beta(谱减)参数。
常见问题
- 噪声帧选取:代码默认取前10帧估计噪声,若语音开头无静音段,需手动指定含纯噪声的帧区间。
- 相位处理:谱减法保留带噪语音相位,维纳滤波直接对复数谱施加实增益,二者均不重建相位。
- 边界效应:ISTFT后输出长度与输入可能略有差异,代码已截断至原始长度
len(x)。