3. 音频信号基础:采样率、量化、傅里叶变换、梅尔频谱图、MFCC特征提取原理
各位同学,欢迎来到第三章。这一章咱们要啃一块硬骨头——音频信号的基础知识。说实话,很多做语音识别的同学,模型调得飞起,但一问到音频底层原理就懵了。我个人觉得,这恰恰是区分「调参侠」和「真专家」的分水岭。
好,咱们不整虚的,直接开干。
3.1 采样率:把连续的声音掰成离散的点
声音本质上是连续的空气振动。但计算机只认0和1。怎么办?
答案就是采样。每隔一小段时间,记录一次声音的「快照」。这个快照的频率,就是采样率。
核心公式:采样率 ≥ 2 × 信号最高频率(奈奎斯特定理)
举个例子。人耳能听到的最高频率大约是20kHz。所以CD音质的采样率是44.1kHz。为什么是这个数?因为44.1kHz > 2 × 20kHz = 40kHz,留了点余量。
我在项目中遇到过一个问题:有个同学录了一段语音,采样率设成了8kHz。结果做出来的语音识别模型,对「s」和「sh」这种高频辅音完全分不清。为什么?因为8kHz只能还原4kHz以下的信号,而「s」音的能量集中在8kHz以上。嗯,这就是典型的「采样率不够,高频全丢」。
实战建议:语音识别一般用16kHz就够了。音乐处理建议用44.1kHz或48kHz。别盲目追求高采样率,文件大小和计算量都会翻倍。
3.2 量化:给每个采样点一个精确的数字
采样解决了「什么时候记录」的问题。量化解决的是「记录成什么数值」的问题。
你想想看,每个采样点的振幅是连续的。但计算机只能用有限个数字来表示。比如16位量化,就是把振幅范围分成65536个等级。每个采样点取最接近的那个等级。
量化位数越高,精度越高,动态范围越大。但文件也越大。
| 量化位数 | 动态范围 | 常见用途 |
|---|---|---|
| 8位 | 48 dB | 电话语音(早期) |
| 16位 | 96 dB | CD音质、语音识别 |
| 24位 | 144 dB | 专业录音、音乐制作 |
| 32位浮点 | 理论上无限 | 音频处理中间格式 |
避坑指南:我曾经在做一个语音合成项目时,把16位音频直接转成8位来减小文件。结果合成出来的声音全是底噪,像收音机没调好频道。后来才发现,8位量化的量化噪声太大了,根本不适合做高质量合成。从此以后,我坚持用16位以上。
3.3 傅里叶变换:从时域到频域的魔法
原始音频信号是时域的——横轴是时间,纵轴是振幅。但人耳对频率更敏感。我们需要知道「某个时刻有哪些频率成分」。
傅里叶变换就是干这个的。它把时域信号分解成不同频率的正弦波叠加。
说白了,就是一句话:任何连续信号,都可以用不同频率、不同振幅的正弦波组合出来。
但在实际语音处理中,我们用的是短时傅里叶变换(STFT)。为什么?因为语音信号是非平稳的——它的频率成分随时间变化。你不可能用一个全局的傅里叶变换来描述一整段语音。
STFT的做法是:把信号切成一小段一小段(通常20-40ms),假设每小段内信号是平稳的,然后对每一小段做傅里叶变换。
# 伪代码示意
def stft(audio, frame_size=1024, hop_length=512):
frames = []
for start in range(0, len(audio) - frame_size, hop_length):
frame = audio[start:start+frame_size]
# 加窗(汉宁窗/汉明窗)
windowed = frame * np.hanning(frame_size)
# 傅里叶变换
spectrum = np.fft.rfft(windowed)
frames.append(np.abs(spectrum))
return np.array(frames) # 形状: [帧数, 频率点数]
这里有个细节要注意:加窗。直接切帧会在边界处产生不连续,导致频谱泄露。加窗就是让帧的两端平滑过渡到0。我个人习惯用汉宁窗,效果比较均衡。
3.4 梅尔频谱图:模拟人耳的听觉特性
好,现在我们有频谱了。但人耳对频率的感知不是线性的。人耳对低频更敏感,对高频分辨率较低。
梅尔刻度就是模拟这种非线性感知的。公式如下:
Mel(f) = 2595 × log10(1 + f/700)
你看,低频段(比如0-1000Hz)的梅尔刻度变化很快,分辨率高。高频段(比如8000Hz以上)变化很慢,分辨率低。这和人耳的听觉特性完全吻合。
梅尔频谱图的生成步骤:
- 对音频做STFT,得到幅度谱
- 将频率轴映射到梅尔刻度上
- 用一组梅尔滤波器(三角滤波器)对频谱进行滤波
- 取对数(模拟人耳对音量的对数感知)
关键点:梅尔频谱图是语音识别中最常用的特征之一。它保留了时频信息,又模拟了人耳特性。很多端到端模型(如DeepSpeech、Wav2Vec)直接用它作为输入。
3.5 MFCC:梅尔频谱的进一步压缩
MFCC(梅尔频率倒谱系数)是梅尔频谱图的「升级版」。它做了两件事:
- 取对数:已经做了
- 离散余弦变换(DCT):对梅尔频谱做DCT,得到倒谱系数
为什么要做DCT?因为梅尔频谱中相邻频带之间有相关性。DCT可以去除这种相关性,把能量集中到前几个系数上。
通常我们取前13个MFCC系数。为什么是13?经验值。我试过8、13、20、40,13在大多数任务上效果最好。当然,具体任务要具体调。
# TensorFlow中提取MFCC
import tensorflow as tf
def extract_mfcc(waveform, sample_rate=16000):
# 确保波形是float32
waveform = tf.cast(waveform, tf.float32)
# 计算梅尔频谱
stfts = tf.signal.stft(waveform,
frame_length=400, # 25ms @ 16kHz
frame_step=160, # 10ms @ 16kHz
fft_length=512)
spectrograms = tf.abs(stfts)
# 梅尔滤波器组
num_spectrogram_bins = stfts.shape[-1]
linear_to_mel_weight_matrix = tf.signal.linear_to_mel_weight_matrix(
num_mel_bins=40, # 40个梅尔频带
num_spectrogram_bins=num_spectrogram_bins,
sample_rate=sample_rate,
lower_edge_hertz=80.0,
upper_edge_hertz=7600.0)
mel_spectrograms = tf.tensordot(spectrograms,
linear_to_mel_weight_matrix, 1)
mel_spectrograms.set_shape(spectrograms.shape[:-1].concatenate(
linear_to_mel_weight_matrix.shape[-1:]))
# 取对数
log_mel_spectrograms = tf.math.log(mel_spectrograms + 1e-6)
# DCT得到MFCC
mfccs = tf.signal.mfccs_from_log_mel_spectrograms(
log_mel_spectrograms)[..., :13] # 取前13个系数
return mfccs
个人经验:MFCC在传统语音识别(GMM-HMM时代)是绝对的主角。但在深度学习时代,很多模型直接用梅尔频谱图甚至原始波形。不过MFCC仍然是一个很好的特征,尤其在数据量不大的时候,它能提供更紧凑的表示。
3.6 总结:这些特征怎么选?
好,咱们捋一捋:
- 原始波形:信息最完整,但维度高、冗余大。适合数据量极大的端到端模型。
- 频谱图:直观,但频率分辨率线性,不符合人耳特性。
- 梅尔频谱图:模拟人耳,信息丰富。目前最常用的特征之一。
- MFCC:更紧凑,去相关。适合传统方法和中小规模数据。
我个人建议:如果你刚开始做语音识别,先用梅尔频谱图。它足够好,而且可视化起来很直观。等你对任务有了感觉,再尝试MFCC或其他特征。
嗯,这一章的内容就到这儿。下一章咱们会动手用TensorFlow实现一个完整的语音识别流水线。到时候这些特征都会用上。
记住:理解原理比调参更重要。你把这些基础打牢了,后面遇到什么问题都能自己分析解决。