第二章 唤醒词原理剖析:唤醒词检测的完整流程

好,咱们直接进入正题。上一章我们聊了唤醒词定制到底能做什么,这一章,我来拆解一下唤醒词检测的完整流程。说白了,就是你的车机从「沉睡」到「醒来」到底经历了什么。

我经常跟团队里的新人说,别把唤醒词想得太神秘。它本质上就是一个关键词检测任务,只不过对实时性、功耗和准确率的要求特别苛刻。整个流程,我习惯把它分成六个环节:

  1. 音频采集
  2. 预处理
  3. 特征提取
  4. 声学模型
  5. 解码
  6. 决策

嗯,咱们一个一个来看。

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(梅尔频率倒谱系数)。它的提取流程大致是:

  1. 对每一帧做FFT,得到频谱
  2. 通过梅尔滤波器组,模拟人耳的非线性感知
  3. 取对数,做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,同时加了一个短时确认窗口,问题就解决了。

总结一下:唤醒词检测的六个环节,环环相扣。音频采集是基础,预处理是保障,特征提取是桥梁,声学模型是核心,解码是路径搜索,决策是最终把关。任何一个环节出问题,唤醒体验都会大打折扣。

下一章,我会带大家手写一个简单的唤醒词检测器,从零开始搭建整个流程。到时候咱们用代码把今天讲的这些理论串起来,你会有更直观的感受。

嗯,今天就到这儿。有什么问题,欢迎在课程群里交流。