1. 基于谱减法的语音降噪

基于谱减法的语音降噪 学习卡片

任务目标

实现经典谱减法(Spectral Subtraction),对叠加了加性噪声的语音信号进行降噪处理,并通过信噪比(SNR)提升与波形对比评估降噪效果。本案例聚焦于谱减法的核心算法实现与效果验证,不涉及端点检测、噪声估计自适应等扩展内容。

实现原理

谱减法的基本假设是:带噪语音的短时幅度谱 ≈ 干净语音幅度谱 + 噪声幅度谱(相位近似不变)。因此,用带噪语音的幅度谱减去估计得到的噪声幅度谱,再结合原始相位进行逆变换,即可恢复增强后的语音。噪声谱通过带噪语音的前导静音段(无语音段)进行统计平均估计。

完整实现代码

import numpy as np
import soundfile as sf
import matplotlib.pyplot as plt

def spectral_subtraction(noisy, sr, n_fft=512, hop=128, alpha=1.0, beta=0.01, noise_frames=10):
    """
    经典谱减法降噪
    Args:
        noisy: 含噪语音信号 (float)
        sr: 采样率
        n_fft: FFT窗口长度
        hop: 帧移
        alpha: 过减因子 (通常1~2)
        beta: 频谱下限因子 (0~0.1)
        noise_frames: 用于估计噪声的起始帧数
    Returns:
        enhanced: 降噪后的语音信号
    """
    # 分帧加窗
    win = np.hanning(n_fft)
    n_frames = 1 + (len(noisy) - n_fft) // hop
    frames = np.zeros((n_frames, n_fft))
    for i in range(n_frames):
        start = i * hop
        frames[i] = noisy[start:start + n_fft] * win

    # STFT
    spec = np.fft.rfft(frames, axis=1)
    mag = np.abs(spec)
    phase = np.angle(spec)

    # 噪声估计:取前noise_frames帧的平均幅度谱
    noise_mag = np.mean(mag[:noise_frames], axis=0)

    # 谱减
    mag_enhanced = np.maximum(mag**alpha - noise_mag**alpha, beta * noise_mag**alpha) ** (1/alpha)

    # ISTFT
    spec_enhanced = mag_enhanced * np.exp(1j * phase)
    frames_enhanced = np.fft.irfft(spec_enhanced, n=n_fft, axis=1)

    # 重叠相加
    enhanced = np.zeros(len(noisy))
    window_sum = np.zeros(len(noisy))
    for i in range(n_frames):
        start = i * hop
        enhanced[start:start + n_fft] += frames_enhanced[i] * win
        window_sum[start:start + n_fft] += win**2
    # 归一化(避免除以零)
    enhanced = enhanced / (window_sum + 1e-10)
    return enhanced

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

# ========== 主程序 ==========
if __name__ == "__main__":
    # 1. 读取干净语音(请替换为实际文件路径)
    clean, sr = sf.read("clean_speech.wav")
    
    # 2. 生成含噪语音(添加高斯白噪声,目标SNR=5dB)
    np.random.seed(42)
    noise = np.random.randn(len(clean))
    noise = noise / np.sqrt(np.sum(noise**2)) * np.sqrt(np.sum(clean**2) / 10**(5/10))
    noisy = clean + noise
    
    # 3. 谱减法降噪
    enhanced = spectral_subtraction(noisy, sr, alpha=1.5, beta=0.01)
    
    # 4. 评估
    snr_noisy = compute_snr(clean, noisy)
    snr_enhanced = compute_snr(clean, enhanced)
    print(f"含噪语音 SNR: {snr_noisy:.2f} dB")
    print(f"降噪后 SNR: {snr_enhanced:.2f} dB")
    
    # 5. 保存结果
    sf.write("enhanced.wav", enhanced, sr)
    
    # 6. 绘制波形对比
    t = np.arange(len(clean)) / sr
    plt.figure(figsize=(12, 8))
    plt.subplot(3, 1, 1)
    plt.plot(t, clean)
    plt.title("Clean Speech")
    plt.subplot(3, 1, 2)
    plt.plot(t, noisy)
    plt.title("Noisy Speech (SNR=5dB)")
    plt.subplot(3, 1, 3)
    plt.plot(t, enhanced)
    plt.title("Enhanced Speech")
    plt.tight_layout()
    plt.savefig("comparison.png", dpi=150)
    plt.show()

运行与验证

环境依赖:numpy、soundfile、matplotlib(用于绘图)。安装命令:pip install numpy soundfile matplotlib

运行步骤:

  1. 准备一段干净语音文件,命名为 clean_speech.wav,放置于脚本同目录(采样率建议16kHz,时长2~3秒即可);
  2. 直接运行脚本:python spectral_subtraction.py;
  3. 程序将生成 enhanced.wav 和 comparison.png。

通过条件:

  • 控制台输出的降噪后SNR高于含噪SNR(例如从5dB提升至10dB以上);
  • 波形对比图中,增强语音的波形背景噪声明显减少,语音段轮廓清晰;
  • 试听 enhanced.wav,背景噪声显著降低,语音可懂度保持良好。

参数说明与常见问题

  • alpha(过减因子):增大可更强抑制噪声,但过大会导致语音失真(产生“音乐噪声”);建议范围1~2。
  • beta(频谱下限):防止减后幅度为负值,beta越大残留噪声越多但音乐噪声越少。
  • 噪声帧数:取前10帧(约0.1秒)估计噪声,需确保该段无语音;若文件开头有语音,需调整起始位置。
  • 音乐噪声:谱减法的固有问题,表现为残留的断续 tonal 噪声。可通过增大beta或后续引入平滑处理缓解,但本案例不展开。
谱减法降噪流程 含噪语音 x(n) = s(n) + d(n) 分帧 + 加窗 汉宁窗, 帧长512 FFT 幅度谱 |X| + 相位 噪声幅度谱估计 前10帧平均 |N| 谱减 |Ŝ| = max(|X|ᵅ - |N|ᵅ, β|N|ᵅ)^(1/α) ISTFT 增强幅度谱 + 原始相位 重叠相加 OLA + 归一化 增强语音 ŝ(n) SNR评估 对比降噪前后SNR