3. 维纳滤波降噪

任务目标

实现维纳滤波降噪,针对平稳加性噪声场景,通过估计噪声功率谱与带噪信号功率谱,计算维纳增益函数并对带噪语音进行频域滤波,最终输出降噪后的语音信号。

核心原理

维纳滤波在频域的最优解为:

H(k) = Ps(k) / (Ps(k) + Pn(k))

其中 Ps(k) 为纯净信号功率谱,Pn(k) 为噪声功率谱。实际中 Ps(k) 不可直接获得,用 Px(k) - Pn(k) 近似(Px(k) 为带噪信号功率谱),并做半波整流防止负值。

实现方案

采用分帧加窗、逐帧FFT处理的方式。噪声功率谱通过前导无语音段(前10帧)估计并保持固定,适用于平稳噪声场景。增益下限设为0.01以避免完全消除语音。

完整代码

import numpy as np
import soundfile as sf

def wiener_filter(input_file, output_file, noise_frames=10, alpha=0.98):
    """
    维纳滤波降噪(平稳噪声场景)
    参数:
        input_file: 输入带噪语音路径
        output_file: 输出降噪语音路径
        noise_frames: 用于估计噪声的起始帧数
        alpha: 平滑系数(0~1),越大平滑越强
    """
    # 读取音频
    x, sr = sf.read(input_file)
    if x.ndim > 1:
        x = x.mean(axis=1)  # 转单声道
    
    # 分帧参数
    win_len = 512
    hop = 128
    win = np.hanning(win_len)
    
    # 计算帧数并填充
    n_frames = 1 + (len(x) - win_len) // hop
    x_pad = np.pad(x, (0, n_frames * hop + win_len - len(x)), mode='reflect')
    
    # 分帧
    frames = np.stack([x_pad[i*hop:i*hop+win_len] * win 
                       for i in range(n_frames)])
    
    # 逐帧FFT
    X = np.fft.rfft(frames, axis=1)
    mag = np.abs(X)
    phase = np.angle(X)
    
    # 估计噪声功率谱(前noise_frames帧)
    noise_power = np.mean(mag[:noise_frames]**2, axis=0)
    
    # 逐帧维纳滤波
    est = np.zeros_like(mag)
    smooth_power = mag[0]**2  # 初始功率谱
    
    for i in range(n_frames):
        # 功率谱平滑
        smooth_power = alpha * smooth_power + (1 - alpha) * mag[i]**2
        
        # 估计纯净信号功率谱(半波整流)
        speech_power = np.maximum(smooth_power - noise_power, 0)
        
        # 维纳增益
        gain = speech_power / (speech_power + noise_power)
        gain = np.clip(gain, 0.01, 1.0)
        
        est[i] = mag[i] * gain
    
    # 重建信号
    Y = est * np.exp(1j * phase)
    frames_out = np.fft.irfft(Y, axis=1)
    
    # 重叠相加
    out = np.zeros(len(x_pad))
    win_sum = np.zeros(len(x_pad))
    for i in range(n_frames):
        out[i*hop:i*hop+win_len] += frames_out[i] * win
        win_sum[i*hop:i*hop+win_len] += win**2
    
    # 归一化并去除填充
    out = out / (win_sum + 1e-8)
    out = out[:len(x)]
    
    # 写入文件
    sf.write(output_file, out, sr)
    return out

if __name__ == "__main__":
    # 使用示例
    wiener_filter("noisy.wav", "enhanced.wav")

运行与验证

依赖安装:

pip install numpy soundfile

运行方式:将带噪语音文件命名为 noisy.wav 置于脚本同目录,执行脚本即可生成 enhanced.wav。

验证方法:

  1. 准备一段干净语音,叠加高斯白噪声(信噪比约5dB)生成测试文件;
  2. 运行脚本后,播放 enhanced.wav,主观听感噪声应明显降低,语音可懂度保持;
  3. 客观验证:分别计算输入/输出信噪比,输出信噪比应高于输入(例如输入5dB,输出可达10dB以上);
  4. 观察波形:降噪后波形中噪声底电平明显降低,语音段幅值基本保留。

关键参数说明

  • 噪声估计帧数(noise_frames=10):假设前10帧(约0.16秒@8kHz)为纯噪声段,适用于平稳噪声;若噪声非平稳需改为噪声跟踪算法。
  • 平滑系数(alpha=0.98):控制功率谱估计的方差,过大则跟踪慢,过小则残留音乐噪声。
  • 增益下限(0.01):防止深衰减导致语音失真,同时保留残余噪声作为掩蔽。

常见问题

  • 输出有“音乐噪声”:增大alpha值(如0.99),或对增益做时域平滑。
  • 语音段被过度衰减:检查噪声估计是否偏高,可减少noise_frames或改用前导静音检测。
  • 采样率不匹配:代码自动读取文件采样率,无需手动设置;但窗长512在低采样率(8kHz)下对应64ms,在高采样率(44.1kHz)下仅12ms,建议按采样率调整窗长(如 win_len = int(0.032 * sr))。