3、频域特征提取:傅里叶变换、短时傅里叶变换、频谱图
说实话,刚入行那会儿,我总觉得时域信号看着挺直观的——波形上下跳动,振幅清清楚楚。但做音频处理久了你会发现,耳朵其实是个频域分析仪。我们听一首歌,分辨的是音高、音色、和弦,这些全是频率信息。时域波形?那只是空气振动的原始记录。
所以这一章,咱们就聊聊怎么把时域信号搬到频域去看。核心工具就是傅里叶变换,以及它的实战变体——短时傅里叶变换。
3.1 傅里叶变换:从时域到频域的桥梁
傅里叶变换的数学公式长这样:
X(f) = ∫ x(t) · e^(-j2πft) dt
别被积分吓到。说白了,它就是在干一件事:把一段信号,拆解成不同频率的正弦波之和。每个频率分量有多强,就是频谱上的幅度值。
我个人的理解方式更简单——想象你在听交响乐。傅里叶变换就是帮你从混合的乐器声中,单独拎出小提琴、大提琴、长笛各自在拉什么音。每个乐器的音量,就是频谱上的峰值。
核心要点:
- 连续傅里叶变换(CFT):理论工具,实际用不了(信号是离散的)
- 离散傅里叶变换(DFT):计算机能算的版本,复杂度 O(N²)
- 快速傅里叶变换(FFT):DFT 的快速算法,复杂度 O(N log N),实际工程全用它
嗯,这里要注意:FFT 不是一种新的变换,只是 DFT 的加速实现。我在项目中见过有人把 FFT 和 DFT 当成两回事,结果代码里自己手写 DFT,处理一段 10 秒的音频等了半分钟……用 FFT 的话,毫秒级就出结果了。
3.2 短时傅里叶变换:给频谱加上时间轴
傅里叶变换有个硬伤——它假设信号是平稳的。但音频信号呢?说话、唱歌、音乐,全是随时间变化的。一段语音里,前半句是元音,后半句是辅音,频率成分完全不同。你用一整段的傅里叶变换,只能得到一个「平均频谱」,啥细节都丢了。
怎么办?加窗分段。这就是短时傅里叶变换(STFT)的思路:
- 把信号切成很多小段(帧),每帧 20-40 毫秒
- 对每一帧加窗(防止截断效应)
- 对每一帧做 FFT
- 把结果按时间排列起来
你想想看,这样我们就得到了一个时间-频率-幅度的三维表示。这就是频谱图(Spectrogram)的基础。
帧参数经验值:
| 参数 | 典型值 | 说明 |
|---|---|---|
| 帧长(window size) | 1024 点(≈23ms @44.1kHz) | 太短频率分辨率差,太长时间分辨率差 |
| 帧移(hop length) | 512 点(50% 重叠) | 常用 25%-75%,50% 是折中 |
| FFT 点数(n_fft) | 1024 或 2048 | 一般等于或大于帧长,不足补零 |
| 窗函数 | 汉宁窗(Hann) | 减少频谱泄漏,比矩形窗好很多 |
我曾经踩过一个坑:做语音端点检测时,用了 512 点的帧长,结果辅音段(比如 /s/、/ʃ/ 这种摩擦音)的时间分辨率不够,短促的爆破音直接被抹平了。后来改成 256 点帧长 + 75% 重叠,才把那些瞬态特征抓出来。所以参数不是死的,得看你的任务需求。
3.3 频谱图:音频工程师的「X 光片」
频谱图就是把 STFT 的结果用图像展示出来:
- 横轴:时间
- 纵轴:频率
- 颜色/亮度:幅度(能量)
我习惯把频谱图叫做音频的「X 光片」。你看一段语音的频谱图,能直接看出:
- 基频在哪里(最低的那条亮线)
- 谐波结构(基频的整数倍位置)
- 共振峰(能量集中的频带)
- 噪声段(均匀分布的模糊区域)
下面这张图展示了从原始音频到频谱图的完整流程:
3.4 实战:用 Python 提取频谱图
理论说完了,上代码。我个人最常用的是 librosa,接口设计得很顺手:
import librosa
import numpy as np
import matplotlib.pyplot as plt
# 加载音频
y, sr = librosa.load('speech.wav', sr=16000)
# STFT 计算
D = librosa.stft(y, n_fft=1024, hop_length=512, win_length=1024, window='hann')
# 幅度谱(dB 刻度)
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()
这段代码跑完,你就能看到一张漂亮的频谱图了。但我得提醒一句:别直接用默认参数就交差。我在做音乐音高估计时,默认的 2048 点 n_fft 频率分辨率够了,但时间分辨率太差,导致快速音阶的过渡段糊成一团。后来改成 512 点 + 75% 重叠,才把每个音符的起振点看清楚。
常见坑点:
- 频谱泄漏:不加窗直接 FFT,频谱上会出现「拖尾」伪影。解决办法:加汉宁窗或汉明窗。
- 直流分量:0 Hz 处的能量通常很大,会压扁其他频率的显示。用
librosa.stft默认会去掉直流,或者自己用高通滤波器预处理。 - 频率轴范围:FFT 的结果是对称的,实际只用 0 ~ sr/2 这一段。别把另一半也画出来,那是冗余信息。
- 幅度刻度:线性刻度下,弱信号根本看不见。转成 dB 刻度(对数)才能看清全貌。
3.5 频谱图的变体与选择
实际项目中,我们很少直接用原始频谱图。根据任务不同,有几个常用变体:
| 类型 | 计算方式 | 适用场景 |
|---|---|---|
| 线性频谱图 | |STFT| | 谐波分析、基频估计 |
| 对数频谱图 | 20·log₁₀(|STFT|) | 语音识别、通用可视化 |
| Mel 频谱图 | 线性频谱 → Mel 滤波器组 | 语音识别、说话人识别 |
| 功率谱密度 | |STFT|² / (n_fft · sr) | 噪声分析、声学测量 |
我个人建议:做可视化分析时用对数频谱图,因为人耳对声音的感知本身就是对数的。做特征输入时,Mel 频谱图往往是更好的起点——它模拟了人耳的频率分辨率,后续接神经网络也更容易收敛。
有一次我做环境声音分类,直接用线性频谱图训练,模型在低频段(比如引擎轰鸣)表现很好,但高频段(比如鸟叫、玻璃破碎)几乎不响应。换成 Mel 频谱图后,高频细节被放大,分类准确率直接提升了 12%。这就是特征选择的力量。
3.6 小结
这一章我们聊了:
- 傅里叶变换的本质——把时域信号拆成频率分量
- 短时傅里叶变换——加窗分段,给频谱加上时间轴
- 频谱图——时间-频率-幅度的三维可视化
- 实战代码和参数调优经验