第二章 唤醒词原理剖析:唤醒词检测的完整流程
好,咱们直接进入正题。上一章我们聊了唤醒词定制到底能做什么,这一章,我来拆解一下唤醒词检测的完整流程。说白了,就是你的车机从「沉睡」到「醒来」到底经历了什么。
我经常跟团队里的新人说,别把唤醒词想得太神秘。它本质上就是一个关键词检测任务,只不过对实时性、功耗和准确率的要求特别苛刻。整个流程,我习惯把它分成六个环节:
- 音频采集
- 预处理
- 特征提取
- 声学模型
- 解码
- 决策
嗯,咱们一个一个来看。
2.1 音频采集:第一道门槛
唤醒词检测的第一步,就是把声音变成电信号。这步看似简单,其实坑最多。
车载环境有多嘈杂?风噪、胎噪、发动机轰鸣、空调风声、乘客聊天……麦克风如果选不好,后面算法再牛也白搭。我个人习惯,在项目初期就会跟硬件团队敲定麦克风的几个关键参数:
- 采样率:唤醒词一般用16kHz就够了,因为人声的主要能量集中在4kHz以下。用48kHz反而增加计算量,没必要。
- 位深:16bit是标配,24bit更好但成本高。
- 信噪比:至少65dB以上,否则底噪会淹没问题。
避坑指南:我曾经在一个项目中,麦克风选型时没注意指向性,结果副驾说话比主驾还灵敏。后来不得不加波束成形算法来补救,白白浪费了两周开发时间。所以,麦克风阵列的布局一定要在结构设计阶段就介入。
2.2 预处理:把脏活干好
原始音频信号进来,不能直接用。为什么?因为里面有太多「杂质」。预处理主要做三件事:
- 降噪:用谱减法或维纳滤波,把稳态噪声压下去。我试过用深度学习降噪,效果是好,但延迟太高,不适合唤醒场景。
- 端点检测:判断人什么时候开始说话。这个很关键,不然车机一直在做无用计算,功耗扛不住。
- 分帧加窗:把连续信号切成20-30ms的小段,每段之间重叠50%。为什么要重叠?你想想看,如果帧之间不重叠,刚好一个「嗨」字被切在两帧中间,那特征就丢了。
这里我贴一段简单的分帧代码,大家感受一下:
def framing(signal, fs=16000, frame_len=0.025, frame_shift=0.010):
"""
分帧函数
:param signal: 原始音频信号
:param fs: 采样率
:param frame_len: 帧长(秒)
:param frame_shift: 帧移(秒)
"""
frame_length = int(frame_len * fs)
frame_shift_len = int(frame_shift * fs)
num_frames = int(np.ceil((len(signal) - frame_length) / frame_shift_len)) + 1
# 补零处理
pad_len = (num_frames - 1) * frame_shift_len + frame_length - len(signal)
signal = np.pad(signal, (0, pad_len), 'constant')
frames = []
for i in range(num_frames):
start = i * frame_shift_len
end = start + frame_length
frames.append(signal[start:end] * np.hamming(frame_length))
return np.array(frames)
重点:加窗函数我推荐用汉明窗,它能有效减少频谱泄露。别问我为什么不用矩形窗,试过一次你就知道了——频谱旁瓣高得吓人。
2.3 特征提取:声音的「指纹」
分帧之后,我们得到的是时域信号,但人耳对频率更敏感。所以要把时域信号转换到频域,提取出有代表性的特征。
唤醒词领域,最经典的特征是MFCC(梅尔频率倒谱系数)。它的提取流程大致是:
- 对每一帧做FFT,得到频谱
- 通过梅尔滤波器组,模拟人耳的非线性感知
- 取对数,做DCT变换,得到倒谱系数
我一般取13维MFCC,再加上一阶差分和二阶差分,总共39维。为什么是39?不是越多越好。特征维数太高,模型参数爆炸,嵌入式设备扛不住。
| 特征类型 | 维度 | 说明 |
|---|---|---|
| 静态MFCC | 13 | 基础特征 |
| 一阶差分 | 13 | 反映变化趋势 |
| 二阶差分 | 13 | 反映加速度 |
个人经验:有些项目会用Fbank(滤波器组特征)替代MFCC,因为Fbank保留了更多信息,配合深度学习模型效果更好。但MFCC在传统GMM-HMM模型上更稳定。选哪个?看你的声学模型架构。
2.4 声学模型:核心引擎
特征提取完了,接下来就是声学模型登场。它的任务很简单:判断每一帧音频对应哪个音素。
举个例子,「你好」这个词,可以拆成「n」「i」「h」「ao」四个音素。声学模型要输出每一帧属于这些音素的概率。
目前主流方案有两种:
- DNN-HMM混合模型:DNN负责计算状态后验概率,HMM负责时序建模。我早期做唤醒词时用的就是这种,优点是成熟稳定,缺点是调参麻烦。
- 端到端模型:直接用CTC或Attention机制,输入特征直接输出音素序列。现在很多新项目都在往这个方向转,因为省去了强制对齐的步骤。
我记得有一次,用DNN-HMM模型做「小度小度」的唤醒,模型在安静环境下准确率99%,但一上路就掉到85%。后来发现是训练数据里缺少高速场景的噪声样本。所以,数据增强在车载场景下特别重要。
警告:千万不要在模型训练时只用干净数据。一定要混入各种车载噪声——空调声、雨刮声、转向灯声。否则你的唤醒词在实车上就是个摆设。
2.5 解码:把概率变成路径
声学模型输出的是每一帧的概率,但我们需要的是完整的词序列。解码器的作用,就是把这些概率「串」起来,找到最可能的音素路径。
解码过程用到了维特比算法。说白了,就是动态规划找最优路径。同时,解码器还要结合语言模型——对于唤醒词来说,语言模型很简单,就是一个有限状态机,定义了唤醒词中音素的合法连接方式。
举个例子,唤醒词是「嗨,小迪」,那语言模型就规定了:
- 必须先出现「h」「ai」
- 然后出现「x」「iao」
- 最后出现「d」「i」
- 中间可以有静音,但不能有其他音素
解码器会实时计算当前路径的累积得分,一旦得分超过阈值,就触发唤醒。
2.6 决策:最后一公里
解码器输出候选路径后,决策模块要做最终判断。这里有两个关键参数:
- 阈值:得分超过多少才算唤醒?阈值设低了,容易误唤醒;设高了,又可能喊不醒。我一般建议在开发阶段先设一个保守值,等实车测试后再微调。
- 确认机制:有些系统会要求连续两次检测到唤醒词才触发,或者要求唤醒词后紧跟一个指令。这能有效降低误唤醒率。
我曾经在一个项目中,用户总是抱怨「喊十次只能唤醒七八次」。后来发现是阈值设得太高,因为测试时用的是标准普通话,但用户带点方言口音。最后我们把阈值降低了0.2,同时加了一个短时确认窗口,问题就解决了。
总结一下:唤醒词检测的六个环节,环环相扣。音频采集是基础,预处理是保障,特征提取是桥梁,声学模型是核心,解码是路径搜索,决策是最终把关。任何一个环节出问题,唤醒体验都会大打折扣。
下一章,我会带大家手写一个简单的唤醒词检测器,从零开始搭建整个流程。到时候咱们用代码把今天讲的这些理论串起来,你会有更直观的感受。
嗯,今天就到这儿。有什么问题,欢迎在课程群里交流。