3. 数字信号处理基础(下):傅里叶变换(FFT)原理、短时傅里叶变换(STFT)、语谱图生成与解读

上一节我们把时域信号聊透了。但说实话,做语音算法光看波形图,你很难看出门道。比如一段语音里有没有噪声、是男声还是女声、频率成分怎么分布的——这些信息都藏在频域里。

傅里叶变换,就是打开频域大门的钥匙。我当年刚接触时也觉得公式吓人,后来发现,你只要抓住核心思想:任何信号都可以拆成不同频率的正弦波叠加。就这么简单。

核心思想:傅里叶变换将时域信号映射到频域,揭示信号中包含哪些频率成分及其强度。

3.1 从离散傅里叶变换到快速傅里叶变换

计算机只能处理离散数据。所以我们需要离散傅里叶变换(DFT)。公式长这样:

X[k] = Σ_{n=0}^{N-1} x[n] · e^{-j(2π/N)kn}

嗯,看着有点头疼。但说白了,就是拿一组不同频率的“探针”去和原始信号做内积。哪个频率匹配上了,那个点的值就大。

DFT 的计算量是 O(N²)。如果 N=1024,要算一百万次。这在实时语音系统里根本跑不动。所以有了快速傅里叶变换(FFT)。

我的习惯:项目中我几乎从不手写 FFT,直接用成熟的库(如 numpy.fft、librosa)。但你必须理解它的原理——分治、蝶形运算、位逆序。否则遇到频谱泄漏、栅栏效应时,你连排查方向都没有。

FFT 的核心是 Cooley-Tukey 算法。它把 DFT 拆成小 DFT,利用旋转因子的对称性和周期性,把复杂度降到 O(N log₂N)。N=1024 时,只需要约一万次运算,快了两个数量级。

对比项 DFT FFT
计算复杂度 O(N²) O(N log₂N)
N=1024 运算量 ~1,048,576 ~10,240
适用场景 理论推导 工程实现

3.2 短时傅里叶变换(STFT)——给信号加窗

直接对整个语音做 FFT 有个大问题:语音是非平稳信号。一句话里,元音、辅音、静音段的频率特性完全不同。你一次算完,得到的是整段信号的平均频谱,细节全丢了。

怎么办?分段处理。这就是短时傅里叶变换(STFT)的思路。

STFT{x[n]}(m, ω) = Σ_{n=-∞}^{∞} x[n] · w[n-m] · e^{-jωn}

公式里多了一个 w[n-m],这是窗函数。它把信号切成一小段一小段,假设每段内信号是平稳的,然后对每段做 FFT。

我曾经踩过的坑:窗函数的选择直接影响频谱质量。矩形窗旁瓣高,容易产生频谱泄漏;汉明窗主瓣宽但旁瓣抑制好。做语音识别时我习惯用汉明窗,做音乐分析时用汉宁窗更多。没有绝对的好坏,看你的任务需求。

STFT 有三个关键参数:

  • 窗长(window length):通常 20-40ms。太短频率分辨率差,太长时间分辨率差。
  • 帧移(hop length):相邻两帧的起始点间隔。一般取窗长的 1/4 到 1/2。
  • FFT 点数(n_fft):通常取 2 的幂次,且大于等于窗长。多出来的部分补零。

你想想看,这三个参数是互相牵制的。窗长决定了你能分辨多近的两个频率;帧移决定了你能捕捉多快的变化。这就是经典的“时间-频率分辨率折中”。

3.3 语谱图——语音的“指纹”

把 STFT 的结果用图像展示出来,就是语谱图。横轴是时间,纵轴是频率,颜色深浅代表能量强度。

一句话总结:语谱图 = 时间 × 频率 × 能量的三维可视化。

我刚开始做语音增强时,调试算法的第一件事就是看语谱图。它能直观告诉你:噪声在哪个频段、语音的共振峰在哪、有没有混响拖尾。比听一千遍录音都管用。

语谱图有两种常见形式:

  • 线性语谱图:纵轴频率线性刻度。适合观察谐波结构。
  • 梅尔语谱图:纵轴按梅尔刻度变换。更符合人耳听觉特性,语音识别里用得最多。

生成语谱图的代码很简单(以 Python 为例):

import numpy as np
import librosa
import matplotlib.pyplot as plt

# 加载音频
y, sr = librosa.load('speech.wav', sr=16000)

# 计算 STFT
D = librosa.stft(y, n_fft=512, hop_length=160, win_length=400)
magnitude = np.abs(D)

# 转换为 dB 刻度
log_spectrogram = librosa.amplitude_to_db(magnitude, ref=np.max)

# 显示语谱图
plt.figure(figsize=(10, 4))
librosa.display.specshow(log_spectrogram, sr=sr, hop_length=160, x_axis='time', y_axis='hz')
plt.colorbar(format='%+2.0f dB')
plt.title('语谱图示例')
plt.tight_layout()
plt.show()

我建议:刚开始学的时候,多拿自己的录音试试。录一句“今天天气不错”,看看元音部分是不是有明显的横条纹(那是基频的谐波)。再录一段有背景噪声的,看看噪声在语谱图上长什么样。这种直观感受,比背一百个公式都有用。

3.4 语谱图解读——从图像反推信号

会看语谱图,是语音算法工程师的基本功。我面试新人时,经常拿一张语谱图问他:这段语音里有什么问题?

常见的语谱图特征:

  • 横条纹(谐波):对应浊音段,如元音。条纹间距就是基频 F0。
  • 竖条纹(冲击):对应爆破音,如 /p/、/t/、/k/。
  • 均匀分布的噪点:背景噪声。白噪声在语谱图上像均匀的雪花。
  • 低频能量集中:常见于风声、空调声等低频噪声。
  • 水平亮线:50Hz 或 60Hz 的工频干扰。

举个例子。你看到语谱图上有一段频率在 300-3400Hz 之间、有清晰谐波条纹的区域,那大概率是语音。如果全频段都有均匀能量,且没有谐波结构,那就是噪声。这就是语音增强里做 VAD(语音活动检测)的底层逻辑。

注意:语谱图的颜色映射会影响你的判断。默认的 'viridis' 或 'magma' 色系对细节展示较好。别用彩虹色,它容易产生视觉假象。我习惯用 'gray' 或 'inferno',看久了眼睛不累。

3.5 本章知识体系

下面这张图梳理了本章的核心逻辑链:

数字信号处理基础(下):知识体系 时域信号 x[n] DFT / FFT 非平稳信号 → 需要分段处理 STFT(加窗 + 分段 FFT) 语谱图(时间-频率-能量) 关键参数 • 窗长:20-40ms • 帧移:1/4 ~ 1/2 窗长 • n_fft:2 的幂次 • 窗函数:汉明/汉宁 语谱图特征 • 横条纹 → 浊音/谐波 • 竖条纹 → 爆破音 • 均匀噪点 → 背景噪声 • 水平亮线 → 工频干扰

从时域信号出发,经过 FFT 得到频域信息,再通过 STFT 解决非平稳问题,最终生成语谱图。每一步都有对应的工程参数需要调优。你掌握了这条链路,就掌握了语音算法最核心的信号处理工具。