4. 基于子空间的语音增强

任务目标

实现基于信号子空间的语音增强算法,对叠加有色噪声(如低通滤波后的白噪声)的带噪语音进行降噪处理。算法核心思想:将带噪信号的协方差矩阵进行特征分解,将特征空间划分为信号子空间与噪声子空间,仅保留信号子空间分量并做谱修正,从而抑制噪声。

环境依赖

Python 3.8+,仅需 numpy 与 scipy(用于读取/生成测试音频)。若需听感验证,可选用 soundfile 读写 wav 文件。

核心实现

以下代码实现完整的子空间增强流程:分帧加窗 → 协方差估计 → 特征分解 → 信号子空间维数估计 → 子空间加权 → 重叠相加重建。噪声方差通过前几帧(纯噪声段)估计。

import numpy as np
from scipy.linalg import eigh

def subspace_enhance(x, fs, noise_frames=10, win_len=512, hop=256, alpha=0.98):
    """
    信号子空间语音增强(适用于有色噪声)
    x: 带噪语音信号 (一维数组)
    fs: 采样率
    noise_frames: 用于估计噪声方差的前若干帧数
    win_len: 帧长(样本数)
    hop: 帧移
    alpha: 谱修正系数 (0~1)
    返回: 增强后信号 (与x等长)
    """
    # 分帧加汉宁窗
    n_frames = 1 + (len(x) - win_len) // hop
    frames = np.zeros((n_frames, win_len))
    win = np.hanning(win_len)
    for i in range(n_frames):
        start = i * hop
        frames[i] = x[start:start+win_len] * win

    # 用前几帧估计噪声方差(假设起始段为纯噪声)
    noise_var = np.mean(frames[:noise_frames] ** 2)

    # 逐帧处理
    enh_frames = np.zeros_like(frames)
    for i in range(n_frames):
        # 帧内去均值(避免直流偏置影响协方差估计)
        frame = frames[i] - np.mean(frames[i])
        # 协方差矩阵(使用空间相关,此处用帧内延迟嵌入构造托普利兹矩阵)
        # 简化:直接使用帧的自相关构造协方差(频域子空间等价做法)
        # 此处采用时域延迟嵌入:构造 m 维子空间
        m = 64  # 子空间维度(嵌入维数)
        if win_len < m:
            m = win_len // 2
        # 构造 Hankel 矩阵(延迟嵌入)
        H = np.zeros((win_len - m + 1, m))
        for j in range(m):
            H[:, j] = frame[j : j + win_len - m + 1]
        # 协方差矩阵 R = H^T H / (行数)
        R = (H.T @ H) / (win_len - m + 1)

        # 特征分解(升序)
        eigvals, eigvecs = eigh(R)
        # 信号子空间维数估计:特征值 > 噪声方差 即视为信号
        noise_floor = noise_var * 1.5  # 有色噪声放宽阈值
        sig_dim = np.sum(eigvals > noise_floor)
        sig_dim = max(1, min(sig_dim, m - 1))  # 至少1维,至多m-1

        # 构造加权矩阵:信号子空间保留,噪声子空间置零,并做谱修正
        G = np.zeros((m, m))
        for j in range(sig_dim):
            # 维纳型修正:lambda_j / (lambda_j + alpha * noise_var)
            gain = eigvals[j] / (eigvals[j] + alpha * noise_var)
            G[j, j] = gain

        # 重建增强帧(在嵌入空间)
        enh_embedded = eigvecs @ G @ eigvecs.T @ H.T
        # 平均重叠部分还原为帧长
        enh_frame = np.zeros(win_len)
        count = np.zeros(win_len)
        for j in range(win_len - m + 1):
            enh_frame[j : j+m] += enh_embedded[:, j]
            count[j : j+m] += 1
        enh_frame = enh_frame / np.maximum(count, 1)
        enh_frames[i] = enh_frame * win  # 重新加窗

    # 重叠相加重建
    out = np.zeros(len(x))
    overlap_sum = np.zeros(len(x))
    for i in range(n_frames):
        start = i * hop
        out[start:start+win_len] += enh_frames[i]
        overlap_sum[start:start+win_len] += win ** 2  # 加窗平方归一化
    # 归一化避免重叠区能量不均
    out = out / np.maximum(overlap_sum, 1e-8)
    return out

# 使用示例(生成测试信号并调用)
if __name__ == "__main__":
    fs = 16000
    t = np.arange(fs * 2) / fs
    # 干净语音:1kHz 正弦 + 3kHz 正弦(模拟语音共振峰)
    clean = 0.5 * np.sin(2*np.pi*1000*t) + 0.3 * np.sin(2*np.pi*3000*t)
    # 有色噪声:白噪声经低通滤波(模拟车内/室内噪声)
    rng = np.random.default_rng(0)
    white = rng.standard_normal(len(t))
    # 简单低通:移动平均
    b = np.ones(50) / 50
    colored_noise = np.convolve(white, b, mode='same')
    colored_noise = colored_noise / np.max(np.abs(colored_noise)) * 0.3
    noisy = clean + colored_noise

    # 前0.5秒为纯噪声段(用于噪声估计)
    noisy[:int(0.5*fs)] = colored_noise[:int(0.5*fs)]

    enhanced = subspace_enhance(noisy, fs)

    # 输出信噪比对比(仅作数值验证)
    def snr(clean, sig):
        noise = sig - clean
        return 10*np.log10(np.sum(clean**2) / np.sum(noise**2))

    snr_in = snr(clean, noisy)
    snr_out = snr(clean, enhanced)
    print(f"输入SNR: {snr_in:.2f} dB")
    print(f"输出SNR: {snr_out:.2f} dB")
    print(f"提升: {snr_out - snr_in:.2f} dB")

运行与验证

  1. 直接运行脚本:python subspace_enhance.py
  2. 观察终端输出的 SNR 数值。若输出 SNR 高于输入 SNR(通常提升 3~10 dB),且无报错,则算法流程正确。
  3. 如需听感验证,可将 noisy 与 enhanced 用 soundfile.write() 导出为 wav 文件,主观对比背景噪声是否明显降低、语音是否保留清晰。

通过条件:程序正常结束,输出 SNR 提升为正数,且增强后信号无断裂或爆音(可观察波形连续性)。

关键说明

  • 噪声方差估计依赖前 noise_frames 帧为纯噪声段,实际应用中需通过语音活动检测(VAD)获取噪声段。
  • 嵌入维数 m 影响子空间分辨率:m 越大频率分辨率越高,但计算量增大且需要更多帧样本。本实现取 64,对 16kHz 信号在 32ms 帧长下效果良好。
  • 有色噪声的协方差矩阵特征值分布较平坦,阈值 noise_floor = 1.5 * noise_var 为经验值,可根据噪声颜色适当调整。
子空间语音增强流程 带噪语音 x(t) 分帧 + 加窗 协方差矩阵 R (延迟嵌入) 特征分解 R = UΛUᵀ 信号子空间提取 特征值 > 噪声阈值 噪声方差 前几帧估计 谱修正 + 重叠相加 增强语音 ŝ(t)