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文件,可听音对比。
验证标准:
- 输出SNR > 输入SNR,即程序打印的SNR提升为正数;
- enhanced.wav中语音可懂,背景噪声明显低于noisy.wav;
- 若将
noise_frames设为0或极大值,效果应退化,验证噪声估计的作用。
关键参数说明
- alpha(过减因子):越大降噪越强,但可能损伤语音,通常取1~2;
- beta(谱下限):防止减出负值导致音乐噪声,通常取0.01~0.1;
- noise_frames:假设前10帧为纯噪声段,实际应用中需通过VAD判断。
常见问题
- 音乐噪声:谱减法的固有问题,可通过增大beta或使用平滑技术缓解;
- 语音失真:alpha过大或噪声估计不准导致,可减小alpha或增加噪声帧数;
- 信号长度不足:若信号短于n_fft,需补零或减小帧长。