3、语音信号预处理:预加重、分帧、加窗、短时傅里叶变换(STFT)原理
好,咱们进入正题。语音信号预处理,说白了就是给原始音频“洗个澡”,让它更适合后续的模型去处理。你想想看,麦克风录进来的声音,带着环境噪声、嘴唇爆破音、还有各种不稳定的能量波动,直接扔给模型,它肯定懵。
我做了这么多年音频工程,可以负责任地告诉你:预处理做得好,模型效果差不了。这一节,咱们就把预加重、分帧、加窗、STFT这四个核心步骤彻底讲透。
3.1 预加重:为什么要“抬高高频”?
先问一个问题:人的发声器官,天生就是个“低通滤波器”。什么意思?就是声带振动产生的能量,大部分集中在低频段。高频部分,比如齿音、擦音,能量天然就弱。
但高频信息恰恰很重要。它包含了辅音的清晰度,决定了语音的可懂度。如果直接拿原始信号去分析,低频分量会“淹没”高频细节,模型根本学不到那些关键的区分特征。
预加重,就是干这个的。它用一个一阶高通滤波器,把高频部分“抬”起来,让整个频谱的能量分布更平坦。
核心公式:
y[n] = x[n] - α * x[n-1]
其中 α 通常取 0.95 到 0.97 之间。我个人习惯用 0.97,效果比较稳。
我在项目中遇到过一件事:有一次用低质量麦克风录制的数据训练TTS模型,合成出来的声音总是“闷闷的”,像隔着一层布。后来检查预处理流程,发现预加重系数设成了0.9,高频抬得不够。改成0.97之后,声音立刻“亮”了起来。嗯,细节决定成败。
避坑指南:
我曾经犯过一个低级错误——在预加重之前做了降噪。结果降噪算法把一些高频噪声滤掉了,预加重又把残留的高频噪声放大了,导致信噪比反而下降。正确的顺序应该是:先预加重,再做其他处理。
3.2 分帧:把连续信号切成“小段”
语音信号是非平稳的。它的统计特性随时间变化。但如果我们把时间窗口缩得足够短(比如20-30毫秒),在这个小片段内,信号可以近似认为是平稳的。这就是分帧的理论基础。
分帧有两个关键参数:
- 帧长:通常取20-30ms。太短了,频率分辨率不够;太长了,平稳性假设不成立。
- 帧移:相邻两帧之间的重叠部分,通常取帧长的1/3到1/2。我一般用10ms的帧移,也就是50%的重叠率。
为什么要重叠?你想想看,如果两帧之间没有重叠,那帧边界处的信息就丢失了。加窗之后,帧两端的权重会降低,重叠可以保证每个采样点都被多个帧“照顾”到,信息不丢失。
举个例子:
采样率16kHz,帧长25ms,那就是400个采样点。帧移10ms,就是160个采样点。那么第一帧是0-399,第二帧是160-559,以此类推。
3.3 加窗:为什么要“削峰填谷”?
分帧之后,直接做傅里叶变换行不行?理论上可以,但效果很差。因为分帧相当于对信号做了一个“矩形窗”——突然截断,突然开始。这种突变会在频谱上引入“频谱泄漏”,也就是能量从主瓣扩散到旁瓣,产生很多虚假的频率分量。
加窗的目的,就是让帧的两端平滑地衰减到0,减少这种截断效应。
常用的窗函数有两种:
| 窗函数 | 特点 | 我的使用场景 |
|---|---|---|
| 汉明窗 | 旁瓣衰减较大,主瓣宽度适中 | 语音识别、说话人识别,我90%的情况用这个 |
| 汉宁窗 | 主瓣更窄,但旁瓣衰减不如汉明窗 | 音乐信号处理、需要更高频率分辨率时 |
汉明窗公式:
w[n] = 0.54 - 0.46 * cos(2πn / (N-1))
汉宁窗公式:
w[n] = 0.5 * (1 - cos(2πn / (N-1)))
我个人习惯用汉明窗。为什么?因为它的旁瓣衰减更干净,能更好地抑制频谱泄漏。在语音克隆任务中,我们需要提取精细的频谱特征,汉明窗的表现更稳定。
注意:
加窗之后,帧的能量会降低(因为两端被衰减了)。如果你后续需要做能量归一化,记得在加窗之后再做。我曾经在加窗之前做了归一化,结果加窗又把能量压下去了,导致特征不一致。
3.4 短时傅里叶变换(STFT):从时域到频域的“桥梁”
好了,现在我们有了一帧一帧的、加窗后的信号。接下来要做什么?把它从时域变换到频域。因为频域特征(比如共振峰、谐波结构)对语音识别和克隆来说,比时域波形直观得多。
STFT的原理很简单:对每一帧信号做一次FFT(快速傅里叶变换)。
关键参数:
- FFT点数:通常取512、1024或2048。点数越多,频率分辨率越高,但计算量也越大。我一般取1024点,兼顾精度和速度。
- 频率分辨率:采样率 / FFT点数。比如16kHz采样率,1024点FFT,频率分辨率就是15.6Hz。
STFT的输出是一个复数矩阵,包含幅度谱和相位谱。在语音合成中,我们通常只取幅度谱(或者更精确地说,功率谱),因为相位信息对听觉感知不那么敏感。但在语音克隆中,相位信息其实也很重要,不过那是后话了。
代码示例(Python + librosa):
import librosa
import numpy as np
# 加载音频
y, sr = librosa.load('speech.wav', sr=16000)
# 预加重
pre_emphasis = 0.97
y_pre = np.append(y[0], y[1:] - pre_emphasis * y[:-1])
# 分帧加窗 + STFT
n_fft = 1024
hop_length = 160 # 10ms帧移
win_length = 400 # 25ms帧长
window = 'hamming'
D = librosa.stft(y_pre, n_fft=n_fft, hop_length=hop_length,
win_length=win_length, window=window)
# 取幅度谱
magnitude = np.abs(D)
你看,librosa把分帧、加窗、STFT都封装在stft函数里了。但理解背后的原理很重要——出了问题才知道怎么调参。
避坑指南:
我曾经在STFT之后直接取对数,结果发现低频部分全是噪声。后来检查发现,是预加重系数设得太高,导致低频能量被过度压制。记住:预加重只是“抬高高频”,不是“压制低频”。如果低频能量太低,可以适当降低α值。
3.5 总结:预处理流程的“黄金组合”
好了,咱们把整个流程串起来:
- 预加重:用一阶高通滤波器提升高频能量,α取0.97。
- 分帧:帧长25ms,帧移10ms,50%重叠。
- 加窗:用汉明窗,减少频谱泄漏。
- STFT:FFT点数1024,得到幅度谱。
这套组合拳,我在多个语音克隆项目中都用过,效果稳定。当然,具体参数可以根据你的采样率和任务微调,但大方向不会错。
下一节,咱们会深入梅尔频谱和倒谱分析,那才是语音特征提取的重头戏。但今天讲的这些预处理步骤,是地基。地基不牢,地动山摇。嗯,记住这句话。