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)——动态的时频图

频谱只是一张快照。语音是随时间变化的,我们需要看“连续的快照”——这就是语谱图。

生成语谱图的流程:

  1. 分帧:把语音切成短帧,通常 20-40ms
  2. 加窗:每帧乘以汉明窗或汉宁窗,减少频谱泄漏
  3. FFT:对每帧做 FFT,得到频谱
  4. 拼接:把各帧的频谱按时间排列,形成二维图像

语谱图的三个维度:

  • 横轴:时间
  • 纵轴:频率
  • 颜色/灰度:能量强度(越亮能量越大)

下面这张 SVG 图展示了从语音波形到语谱图的完整流程:

语谱图生成流程 语音信号 分帧 加窗 FFT 对每一帧重复分帧→加窗→FFT 按时间拼接各帧频谱 语谱图(Spectrogram) 关键参数: • 帧长:20-40ms(决定频率分辨率) • 帧移:10-15ms(决定时间分辨率) • 窗函数:汉明窗最常用(减少频谱泄漏)

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 到频谱,从频谱到语谱图,从语谱图到梅尔频谱——每一步都在做一件事:把语音信号里最有用的信息提炼出来,扔掉冗余和噪声。

我个人觉得,理解语谱图是理解语音的“分水岭”。能看懂语谱图的人,看语音就像看乐谱;看不懂的人,看语音就像看天书。希望这一章能帮你跨过这道坎。