4. 频域特征提取:从傅里叶变换到语谱图
说实话,语音信号在时域里看起来就是一串乱糟糟的波形。你盯着它看半天,也看不出什么门道。但一旦把它扔到频域里,一切就清晰了——哪个频率能量强、哪个时刻有声音、是男声还是女声,一目了然。
这一章,我们就来聊聊怎么从频域里“榨”出语音的特征。我会从最基础的傅里叶变换讲起,一直讲到语谱图的生成和解读。嗯,这里面的坑不少,我当年踩过好几个。
4.1 为什么非要去频域?
先问个问题:时域信号不好吗?
好,但不够好。时域里你能看到音量大小、有无声音,但你看不到音色、看不到基频、看不到共振峰。而这些,恰恰是语音识别和说话人识别最需要的信息。
我个人习惯把频域分析比作“拆解乐器”——时域是听整首曲子,频域是把每个乐器的声音单独拎出来。你想想看,如果我想知道一段录音里有没有钢琴声,在时域里找几乎不可能,但在频域里,钢琴的泛音结构一目了然。
核心思想:任何时域信号都可以分解为不同频率的正弦波之和。傅里叶变换就是干这个活的。
4.2 傅里叶变换(FFT)——频域分析的基石
傅里叶变换的数学公式长这样:
X(f) = ∫ x(t) · e^(-j2πft) dt
别被公式吓到。说白了,它就是在问:“我的信号里,频率 f 的成分有多少?”
但在计算机里,我们处理的是离散的采样点,所以用的是离散傅里叶变换(DFT):
X[k] = Σ x[n] · e^(-j2πkn/N), n=0 to N-1
直接算 DFT 太慢了——复杂度 O(N²)。所以工程上全用快速傅里叶变换(FFT),复杂度降到 O(N log N)。
实用技巧:FFT 要求输入长度是 2 的幂次。如果不够,就补零(zero-padding)。我刚开始做语音处理时,经常忘记检查帧长是不是 2 的幂,结果程序跑得奇慢无比……后来养成了习惯,每次分帧后先算一下 next power of 2。
4.3 频谱图(Spectrum)——静态的频域快照
对一帧语音做 FFT,得到的就是频谱。它展示的是“这一刻”各个频率的能量分布。
频谱有两个关键部分:
- 幅度谱:|X[k]|,表示每个频率分量的强度
- 相位谱:∠X[k],表示每个频率分量的相位偏移
在语音算法里,我们通常只关心幅度谱。相位谱虽然也包含信息,但人耳对相位不敏感,很多任务直接把它扔了。
举个例子,一段 8kHz 采样率的语音,取 512 点做 FFT,得到 256 个频点(因为对称性,只用前一半)。每个频点对应的实际频率是:
f[k] = k * fs / N
比如 k=10,fs=8000,N=512,那么 f[10] = 10 * 8000 / 512 ≈ 156.25 Hz。
| FFT 点数 N | 采样率 fs | 频率分辨率 Δf | 有效频点数 |
|---|---|---|---|
| 256 | 8000 Hz | 31.25 Hz | 128 |
| 512 | 8000 Hz | 15.625 Hz | 256 |
| 1024 | 16000 Hz | 15.625 Hz | 512 |
注意:频率分辨率 Δf = fs / N。N 越大,分辨率越高,但时间分辨率会下降。这是时频分析里永恒的矛盾——你不可能同时把时间和频率都看得很清楚。
4.4 语谱图(Spectrogram)——动态的时频图
频谱只是一张快照。语音是随时间变化的,我们需要看“连续的快照”——这就是语谱图。
生成语谱图的流程:
- 分帧:把语音切成短帧,通常 20-40ms
- 加窗:每帧乘以汉明窗或汉宁窗,减少频谱泄漏
- FFT:对每帧做 FFT,得到频谱
- 拼接:把各帧的频谱按时间排列,形成二维图像
语谱图的三个维度:
- 横轴:时间
- 纵轴:频率
- 颜色/灰度:能量强度(越亮能量越大)
下面这张 SVG 图展示了从语音波形到语谱图的完整流程:
4.5 如何解读语谱图?
语谱图读起来其实挺直观的。我总结了几种常见的模式:
- 横条纹(谐波结构):对应浊音(元音),能量集中在基频及其整数倍处。比如男声基频约 100Hz,你会看到 100、200、300Hz 等位置有亮条
- 竖条纹(冲激):对应爆破音(如 /p/、/t/、/k/),能量在短时间内爆发
- 均匀噪声区:对应清音(如 /s/、/f/),能量分布均匀,没有明显条纹
- 暗区:静音或低能量段
我记得有一次做说话人识别,发现两个人在某个元音上的共振峰位置几乎一样,但谐波结构明显不同——一个基频 120Hz,另一个 200Hz。这就是语谱图的价值:它把声源和声道的信息分开了。
避坑指南:我曾经在调参数时,把帧长设成了 5ms,结果语谱图糊成一团,频率分辨率太差,根本看不清共振峰。后来改回 25ms,瞬间清晰了。记住:帧长短看时间细节,帧长长看频率细节,鱼和熊掌不可兼得。
4.6 语谱图的变体:窄带 vs 宽带
根据帧长的不同,语谱图分为两种:
| 类型 | 帧长 | 频率分辨率 | 时间分辨率 | 能看到什么 |
|---|---|---|---|---|
| 窄带语谱图 | 40-60ms | 高(~20Hz) | 低 | 谐波结构、基频轨迹 |
| 宽带语谱图 | 3-10ms | 低(~100Hz) | 高 | 共振峰、声门脉冲 |
窄带语谱图适合看音高变化,宽带语谱图适合看发音器官的共振特性。实际项目中,我一般先用宽带语谱图看共振峰,再用窄带语谱图确认基频——两个结合起来用。
4.7 从语谱图到特征:梅尔频谱
语谱图虽然信息丰富,但维度太高(通常几百维),直接扔给模型会炸。而且人耳对频率的感知是非线性的——低频敏感,高频迟钝。
所以工程上通常把语谱图映射到梅尔尺度(Mel Scale)上:
Mel(f) = 2595 · log10(1 + f/700)
然后通过梅尔滤波器组,把线性频率映射到梅尔频率,得到梅尔频谱。这一步有两个好处:
- 降维(从几百维降到 40-80 维)
- 更符合人耳听觉特性
梅尔频谱再取对数、做 DCT,就是大名鼎鼎的 MFCC(梅尔频率倒谱系数)。不过那是下一章的内容了。
一句话总结:语谱图是语音的“指纹”,梅尔频谱是“压缩后的指纹”,MFCC 是“去冗余后的指纹”。每一步都在保留关键信息的同时,让数据更干净、更紧凑。
4.8 实践:用 Python 生成语谱图
理论说完了,来点实际的。下面这段代码用 librosa 生成语谱图:
import librosa
import numpy as np
import matplotlib.pyplot as plt
# 加载语音
y, sr = librosa.load('speech.wav', sr=16000)
# 生成语谱图(默认参数:帧长2048,帧移512,窗函数hann)
D = librosa.stft(y, n_fft=2048, hop_length=512, win_length=2048)
S_db = librosa.amplitude_to_db(np.abs(D), ref=np.max)
# 显示
plt.figure(figsize=(10, 4))
librosa.display.specshow(S_db, sr=sr, hop_length=512, x_axis='time', y_axis='hz')
plt.colorbar(format='%+2.0f dB')
plt.title('Spectrogram')
plt.tight_layout()
plt.show()
这段代码我用了无数次。唯一要注意的是 amplitude_to_db 这一步——如果不转成 dB,语谱图的动态范围太大,暗的地方全黑,亮的地方过曝,什么都看不清。
常见错误:直接用幅度值显示语谱图。幅度范围从 0 到几千,显示出来就是一片漆黑。一定要转成 dB 尺度(对数压缩),才能看到细节。
4.9 本章小结
频域特征提取是语音算法的基本功。从 FFT 到频谱,从频谱到语谱图,从语谱图到梅尔频谱——每一步都在做一件事:把语音信号里最有用的信息提炼出来,扔掉冗余和噪声。
我个人觉得,理解语谱图是理解语音的“分水岭”。能看懂语谱图的人,看语音就像看乐谱;看不懂的人,看语音就像看天书。希望这一章能帮你跨过这道坎。