3、语音信号基础:声音的物理特性、采样定理、量化与编码、语音信号预处理概述
好,咱们开始第三讲。
说实话,做嵌入式语音识别,很多人一上来就调算法、跑模型。结果呢?麦克风一接,全是噪声,识别率惨不忍睹。为什么?因为最基础的东西没搞懂——你连声音是什么、怎么变成数字信号的都没弄清楚。
这一章,咱们就把地基打牢。我会结合我这些年踩过的坑,把语音信号的物理本质、数字化过程、以及预处理套路,给你讲透。
3.1 声音的物理特性:你听到的到底是什么?
声音,说白了就是空气的振动。这种振动以波的形式传播,所以我们叫它「声波」。
描述一个声波,主要看三个参数:
- 频率:每秒振动的次数,单位是赫兹(Hz)。人耳能听到的大概是20Hz到20kHz。我做过一个项目,客户非要用8kHz采样率去识别鸟叫声,结果高频信息全丢了——鸟叫的基频虽然低,但泛音都在10kHz以上。嗯,这就是典型的「想当然」错误。
- 幅度:振动的强弱,决定了声音的大小。在嵌入式里,我们通常用ADC采集到的电压值来表示。
- 相位:波形的起始位置。说实话,在语音识别里,相位信息通常用处不大,我们更关心幅度和频率的变化。
重点记住:语音信号是典型的非平稳信号。什么意思?就是它的频率和幅度一直在变。你想想看,你说「你好」这两个字,声带的振动频率、口腔的形状都在动态变化。所以,我们不能用处理正弦波的方法去处理语音。
3.2 采样定理:别让你的信号「混叠」了
要把模拟的声音变成数字信号,第一步就是采样。这里有个铁律——奈奎斯特采样定理。
定理很简单:采样频率必须大于信号最高频率的两倍。否则,就会发生「混叠」——高频信号会伪装成低频信号混进来,你根本分不清。
举个例子。我曾经帮一个团队调试语音唤醒模块。他们用了16kHz采样率,理论上能处理8kHz以内的信号。但麦克风本身能拾取到12kHz的噪声(比如空调压缩机的高频啸叫)。结果呢?这个12kHz的信号被采样后,变成了4kHz的假信号,混在语音里,导致误唤醒率飙升。
避坑指南:采样前一定要加抗混叠滤波器!我习惯在麦克风输出端接一个RC低通滤波器,截止频率设为采样率的一半左右。别省这个电容,省了就是给自己挖坑。
常见的语音采样率有:
| 应用场景 | 采样率 | 说明 |
|---|---|---|
| 电话语音 | 8 kHz | 带宽300Hz-3.4kHz,够用 |
| 普通语音识别 | 16 kHz | 覆盖大部分语音能量,STM32常用 |
| 高保真录音 | 44.1 kHz | CD音质,嵌入式很少用 |
我个人建议,做STM32上的语音识别,优先选16kHz。为什么?因为8kHz会丢失一些辅音信息(比如/s/、/f/这些清辅音的高频成分),而16kHz刚好够用,又不至于数据量太大把MCU撑爆。
3.3 量化与编码:精度和动态范围的博弈
采样是把时间离散化,量化则是把幅度离散化。
量化说白了,就是用有限个数字去表示无限连续的幅度值。比如你用16位ADC,那幅度就被分成65536个等级。每个等级之间,必然有误差——这就是量化噪声。
这里有个关键参数:量化位数。
- 8位:256个等级,动态范围约48dB。噪声大,只适合对音质要求极低的场合。我见过有人用8位做语音识别,结果背景噪声稍微大一点,识别率直接掉到30%。
- 16位:65536个等级,动态范围约96dB。这是语音识别的标配。STM32的ADC虽然是12位,但外挂一个16位音频ADC(比如WM8960)效果会好很多。
- 24位:动态范围144dB,专业录音级别。嵌入式基本用不上,数据量太大。
小技巧:量化位数每增加1位,信噪比提升约6dB。但代价是数据量翻倍。在STM32上,16位是性价比最高的选择。我曾经试过用12位ADC直接采,结果发现底噪太大,后来加了个前置放大器才勉强能用。
编码就简单了。最常用的是线性PCM编码——直接把量化后的数值存起来。比如16位PCM,每个采样点占2字节。16kHz采样率,每秒就是32KB数据。嗯,这个数据量对STM32来说,处理起来还行,但存储和传输就要掂量掂量了。
3.4 语音信号预处理概述:别让脏数据进模型
原始语音信号直接扔给算法?那基本是找死。预处理的目的,就是把信号「洗干净」,让后续的特征提取和识别更靠谱。
预处理通常包括这几步:
- 预加重:提升高频成分。因为语音信号的高频能量通常比低频弱,但高频部分(比如辅音)恰恰包含了很多辨识信息。我习惯用一个一阶高通滤波器,系数设0.97左右。效果立竿见影。
- 分帧:语音是非平稳的,但在极短时间(10-30ms)内可以认为是平稳的。所以我们把信号切成一段一段的「帧」。帧长一般取20-30ms,帧移取10-15ms(也就是相邻帧有重叠)。
- 加窗:直接分帧会在帧边界产生截断效应,导致频谱泄露。解决办法是加窗函数,比如汉明窗。汉明窗能让帧中间的数据权重更大,两端的权重变小,减少频谱泄露。
- 端点检测(VAD):判断哪些是有效语音,哪些是静音或噪声。这一步很重要——你想想看,如果背景噪声和语音一起送进模型,模型会学歪的。我常用的方法是基于短时能量和过零率,简单有效。
我的经验:预处理做得好,识别率能提升10-20%。别小看这一步。我曾经接手过一个项目,之前的工程师把预处理全跳过了,直接拿原始波形去训练模型。结果模型在实验室跑得挺好,一到实际场景就崩。后来我加了预加重和VAD,问题就解决了。
最后,给你一个简单的预处理流程伪代码,方便你理解:
// 伪代码:语音信号预处理
function preprocess(signal, fs):
// 1. 预加重
signal = filter([1, -0.97], 1, signal)
// 2. 分帧参数
frame_len = 0.025 * fs // 25ms
frame_shift = 0.010 * fs // 10ms
// 3. 加汉明窗
window = hamming(frame_len)
// 4. 逐帧处理
for each frame in signal:
frame = frame * window
// 后续提取MFCC等特征...
return frames
嗯,这一章的内容就到这里。下一章,我们会深入讲解特征提取——MFCC是怎么算出来的,以及为什么它这么适合语音识别。