1. 基于谱减法的基本降噪实现

任务目标

实现经典谱减法(Spectral Subtraction),对一段含噪语音信号进行降噪处理,并计算降噪前后的信噪比(SNR)以评估效果。本案例使用合成含噪语音作为输入,确保可独立复现。

实现原理

谱减法的核心假设是噪声为加性平稳噪声,且语音与噪声不相关。算法流程为:对带噪语音分帧加窗→FFT→在噪声段估计噪声幅度谱→将带噪幅度谱减去过估计的噪声谱→保留带噪相位→IFFT重建时域信号。噪声估计取前若干帧(假设为纯噪声段)幅度谱的平均值。

完整代码

import numpy as np
import soundfile as sf

def add_noise(clean, noise, snr_db):
    """按给定SNR(dB)混合纯净语音与噪声"""
    clean = clean / np.max(np.abs(clean))
    noise = noise / np.max(np.abs(noise))
    clean_power = np.mean(clean**2)
    noise_power = np.mean(noise**2)
    scale = np.sqrt(clean_power / (noise_power * 10**(snr_db/10)))
    return clean + scale * noise

def spectral_subtraction(x, fs, n_fft=512, hop=128, alpha=2.0, beta=0.01, noise_frames=10):
    """
    经典谱减法降噪
    x: 含噪语音信号
    fs: 采样率
    n_fft: FFT长度
    hop: 帧移
    alpha: 过减因子
    beta: 谱下限系数
    noise_frames: 用于估计噪声的起始帧数
    """
    win = np.hanning(n_fft)
    n_frames = 1 + (len(x) - n_fft) // hop
    # 预分配
    X = np.zeros((n_frames, n_fft//2+1), dtype=complex)
    # 分帧加窗FFT
    for i in range(n_frames):
        start = i * hop
        frame = x[start:start+n_fft] * win
        X[i] = np.fft.rfft(frame)
    # 噪声估计:取前noise_frames帧幅度谱平均
    noise_mag = np.mean(np.abs(X[:noise_frames]), axis=0)
    # 谱减
    mag = np.abs(X)
    phase = np.angle(X)
    # 减去过减噪声谱
    mag_sub = mag**alpha - noise_frames * noise_mag**alpha  # 修正:乘系数而非帧数
    # 修正:使用过减因子alpha的噪声谱估计
    mag_sub = mag**alpha - 2.0 * noise_mag**alpha
    # 下限保护
    mag_sub = np.maximum(mag_sub, beta * noise_mag**alpha)
    mag_sub = mag_sub**(1/alpha)
    # 重建
    y = np.zeros(len(x))
    for i in range(n_frames):
        start = i * hop
        frame_spec = mag_sub[i] * np.exp(1j*phase[i])
        frame_t = np.fft.irfft(frame_spec, n=n_fft)
        y[start:start+n_fft] += frame_t * win
    return y

def compute_snr(clean, noisy):
    """计算信噪比(dB)"""
    noise = clean - noisy
    snr = 10*np.log10(np.sum(clean**2)/np.sum(noise**2))
    return snr

if __name__ == "__main__":
    # 生成测试信号:1s正弦语音 + 白噪声
    fs = 8000
    t = np.arange(fs)/fs
    clean = 0.5*np.sin(2*np.pi*440*t) + 0.3*np.sin(2*np.pi*880*t)
    # 生成白噪声
    np.random.seed(42)
    noise = np.random.randn(fs)
    # 混合为0dB SNR
    noisy = add_noise(clean, noise, 0)
    # 降噪
    enhanced = spectral_subtraction(noisy, fs)
    # 评估
    snr_in = compute_snr(clean, noisy)
    snr_out = compute_snr(clean, enhanced)
    print(f"输入SNR: {snr_in:.2f} dB")
    print(f"输出SNR: {snr_out:.2f} dB")
    print(f"SNR提升: {snr_out-snr_in:.2f} dB")
    # 保存结果
    sf.write("clean.wav", clean, fs)
    sf.write("noisy.wav", noisy, fs)
    sf.write("enhanced.wav", enhanced, fs)

运行与验证

环境依赖:numpy、soundfile(用于保存wav,若不需要可注释)。

运行方式:直接执行脚本:python spectral_subtraction.py

预期输出:输入SNR约为0dB,输出SNR应明显高于输入(通常提升5~15dB,具体取决于参数)。程序会生成三个wav文件,可听音对比。

验证标准:

  1. 输出SNR > 输入SNR,即程序打印的SNR提升为正数;
  2. enhanced.wav中语音可懂,背景噪声明显低于noisy.wav;
  3. 若将noise_frames设为0或极大值,效果应退化,验证噪声估计的作用。

关键参数说明

  • alpha(过减因子):越大降噪越强,但可能损伤语音,通常取1~2;
  • beta(谱下限):防止减出负值导致音乐噪声,通常取0.01~0.1;
  • noise_frames:假设前10帧为纯噪声段,实际应用中需通过VAD判断。

常见问题

  • 音乐噪声:谱减法的固有问题,可通过增大beta或使用平滑技术缓解;
  • 语音失真:alpha过大或噪声估计不准导致,可减小alpha或增加噪声帧数;
  • 信号长度不足:若信号短于n_fft,需补零或减小帧长。
谱减法降噪流程 含噪语音 x(n) 分帧 + 加窗 FFT → X(k) |X(k)|, ∠X(k) 噪声估计 |N(k)| 谱减: |Ŝ| = |X| - α|N| 下限保护: max(·, β|N|) |Ŝ|·e^(j∠X) IFFT 重叠相加 增强语音 ŷ(n) 前10帧