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 总结:预处理流程的“黄金组合”

好了,咱们把整个流程串起来:

  1. 预加重:用一阶高通滤波器提升高频能量,α取0.97。
  2. 分帧:帧长25ms,帧移10ms,50%重叠。
  3. 加窗:用汉明窗,减少频谱泄漏。
  4. STFT:FFT点数1024,得到幅度谱。

这套组合拳,我在多个语音克隆项目中都用过,效果稳定。当然,具体参数可以根据你的采样率和任务微调,但大方向不会错。

下一节,咱们会深入梅尔频谱和倒谱分析,那才是语音特征提取的重头戏。但今天讲的这些预处理步骤,是地基。地基不牢,地动山摇。嗯,记住这句话。