第三讲:数字信号处理基础——采样定理、傅里叶变换、短时傅里叶变换

各位同学,欢迎来到第三讲。

说实话,这一讲的内容,是后面所有降噪算法的地基。你想想看,麦克风采集到的语音信号,本质上就是一串随时间变化的电压值。我们要在这串数字里把噪声揪出来、把干净的人声保留住,靠的是什么?靠的就是我们今天要讲的这几个数学工具。

我个人习惯把这一讲叫做「三把刀」:采样定理、傅里叶变换、短时傅里叶变换。这三把刀你磨快了,后面切什么噪声都顺手。

3.1 采样定理:别让你的信号「假摔」

先问一个问题:麦克风采集到的声音,是连续的还是离散的?

答案是连续的模拟信号。但我们的芯片、DSP、MCU,只能处理离散的数字信号。所以第一步,就是把连续信号变成离散信号——这个过程叫采样。

采样定理(Nyquist-Shannon定理)告诉我们:

  • 采样频率必须大于信号最高频率的两倍。
  • 否则,就会发生「混叠」——高频信号会伪装成低频信号混进来。

我在项目中遇到过一件事。有一次做座舱内的语音唤醒,麦克风采样率设成了8kHz。结果发现,空调风机的高频噪声(大概4.5kHz)被采样后,变成了3.5kHz的低频噪声,直接触发了误唤醒。查了半天才发现是采样率不够,混叠了。

避坑指南: 我曾经在调试车载麦克风阵列时,忽略了抗混叠滤波器。结果高频噪声全部折叠到语音频段,降噪算法怎么调都调不好。后来加了一级模拟低通滤波器,问题才解决。记住:采样前一定要做抗混叠滤波!

座舱场景下,人声频率范围一般在300Hz~3.4kHz。为了安全,我建议采样率至少设到16kHz。如果你要处理更高的噪声成分(比如轮胎胎噪、风噪),建议用48kHz采样。

3.2 傅里叶变换:从时域到频域的「翻译官」

好,现在信号已经离散化了。但问题来了:我们拿到的是一串时间点上的幅度值。噪声和语音混在一起,怎么区分?

答案就是:把信号从时域变到频域。

傅里叶变换的核心思想很简单:任何一个信号,都可以看成是无数个不同频率、不同幅度、不同相位的正弦波叠加而成。

数学公式长这样:

X(f) = ∫ x(t) · e^(-j2πft) dt

但在嵌入式系统里,我们用的是离散版本——离散傅里叶变换(DFT)

X[k] = Σ x[n] · e^(-j2πkn/N)   (n=0 to N-1)

嗯,这里要注意:DFT的计算量是O(N²)。如果你处理的是1024点数据,就要算100多万次乘法。这在MCU上跑,CPU直接冒烟。

所以实际工程中,我们用的是快速傅里叶变换(FFT)。它把计算量降到了O(N log₂N)。比如1024点FFT,只需要约5000次乘法,快了200倍。

关键点: FFT不是一种新的变换,只是DFT的一种高效算法实现。你写代码时直接调FFT库就行,不用自己手写。

3.3 短时傅里叶变换:给信号加个「滑动窗口」

傅里叶变换有个大问题:它假设信号是平稳的。但语音信号是时变的——你说话时,每个音节的频率都在变化。一个全局的傅里叶变换,只能告诉你整个信号里有哪些频率成分,却不知道这些频率在什么时间出现。

这就好比一张照片只能看到整体,看不到时间轴上的变化。我们需要的是「视频」——既能看频率,又能看时间。

短时傅里叶变换(STFT)就是干这个的。它的做法是:

  1. 把信号切成一小段一小段(每段叫一个「帧」)。
  2. 对每一帧加窗(比如汉宁窗、海明窗)。
  3. 对每一帧做FFT。
  4. 把结果按时间顺序排列,得到一张「时频谱图」。

数学上:

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

其中w[n]是窗函数,m是帧的起始位置。

我在项目中常用的参数是:

参数 推荐值 说明
帧长 512点(32ms @16kHz) 太短频率分辨率差,太长时域分辨率差
帧移 256点(50%重叠) 保证帧间连续性,避免「咔咔」声
窗函数 汉宁窗 旁瓣衰减好,适合语音处理
个人经验: 我刚开始做降噪时,帧长选了1024点(64ms)。结果发现语音的辅音部分(比如「t」、「k」)被严重模糊,听起来像含了口水。后来改成512点,辅音清晰度明显提升。记住:语音的瞬态信息很宝贵,帧长别太长。

3.4 从STFT到降噪:我们拿到了什么?

做完STFT后,我们得到的是一个二维复数矩阵:

  • 横轴:时间帧(frame index)
  • 纵轴:频率点(frequency bin)
  • 每个元素:复数,包含幅度和相位

降噪算法通常只处理幅度谱,相位一般保持不变。为什么?因为人耳对相位不敏感,但对幅度变化非常敏感。你想想看,把相位改乱了,听起来就是「金属声」、「机器人声」。

所以典型的降噪流程是:

  1. 对带噪语音做STFT,得到幅度谱和相位谱。
  2. 用某种算法(比如谱减法、维纳滤波、深度学习)估计出干净的幅度谱。
  3. 用估计的幅度谱 + 原始相位谱,做逆STFT(ISTFT),恢复时域信号。
注意: ISTFT时,要保证「重叠相加」的正确性。如果帧移和窗函数不匹配,恢复出来的信号会有「咔咔」的拼接噪声。我建议用50%重叠+汉宁窗,这是最稳妥的组合。

3.5 嵌入式实现中的几个坑

好了,理论讲完了。但落地到座舱的嵌入式平台上,还有几个坑要避开:

  • 定点数运算: 很多MCU没有FPU,FFT要用定点数实现。注意防止溢出,Q15格式是常用选择。
  • 实时性要求: 帧长10ms,你必须在10ms内完成一帧的FFT+降噪+ISTFT。否则就会丢帧。
  • 内存管理: STFT需要缓存多帧数据。比如512点FFT,复数数组就要2KB。如果做多通道(比如4麦阵列),内存压力不小。

我记得有一次在ARM Cortex-M4上移植降噪算法,FFT库用的是CMSIS-DSP。一开始没注意内存对齐,结果FFT结果全是错的。后来加了__align(8)修饰,问题解决。嗯,嵌入式开发就是这样,细节决定成败。

3.6 小结

这一讲我们聊了三件事:

  1. 采样定理——决定了你的信号能不能被正确还原。
  2. 傅里叶变换/FFT——把时域信号变到频域,方便分离噪声和语音。
  3. 短时傅里叶变换——给信号加窗分帧,得到时频谱,这是降噪算法的输入。

说白了,这三把刀就是降噪算法的「眼睛」。你看不到噪声长什么样,就没办法把它去掉。下一讲,我们会用这些工具,开始真正动手做降噪。

课后你可以做个小实验:用Python的librosa库,读一段语音,做STFT,看看时频谱图长什么样。你会发现,噪声和语音在频域上其实是「分居」的——这就是我们能降噪的根本原因。

好,今天就到这里。有问题随时找我。