先说结论:能带动,但别指望它像手机喊"小爱同学"那样灵敏。我手上这块是 imx6ull 的开发板,512MB 内存,跑 Linux,之前一直拿来做点串口收发的小活。前阵子突发奇想,能不能让它听懂一句"你好板子",然后点亮一个 LED。折腾了差不多两个周末,中间翻车好几次,今天把过程完整写下来,包括我踩的坑。
一、我到底想干什么
场景很简单:板子上接了个麦克风模块(INMP441,I2S 接口),我想让它一直监听,听到特定唤醒词就执行一个动作,比如点亮 GPIO 上的 LED,或者打印一行日志。不联网,不上云,纯本地。
为什么不用云端方案?一是这块板子经常放在没网的环境里跑,二是语音数据传出去我也不太放心。说白了就是想要一个"断网也能用"的土办法。
二、涉及的知识点,先捋一遍
1、语音唤醒和语音识别不是一回事。
语音识别是把整句话转成文字,计算量大。语音唤醒只判断"是不是那个词",输出是一个是/否,模型可以做得非常小。这一点很关键,很多人一上来就想跑 Whisper,那玩意在这块板子上根本带不动。
2、小模型的选择。
我试过两个方向:一个是 Google 的 speech commands 系列小模型(就是那个识别 up/down/yes/no 的),另一个是开源的 Porcupine。前者完全离线、模型几百 KB,后者效果更好但要注册 access key。我最后用的是 speech commands 那套思路,自己训了一个两分类的小模型:唤醒词 / 非唤醒词。
3、特征提取用 MFCC。
音频不能直接喂给模型,要先转成梅尔频谱或者 MFCC 系数。Python 里用 librosa 一行就能算,但板子上跑 librosa 太重,我是在电脑上把特征算好、训好模型,板子上只做推理。
三、具体怎么操作
第一步,收集数据。我自己录了"你好板子"大概 200 遍,语速、距离、音量都故意弄得不一致。负样本用了 speech commands 数据集里的其他词,外加环境噪声。这里要注意,负样本一定要包含"听起来像但不是"的词,比如"你好朋友""你好板砖",不然模型很容易误触发。
第二步,训模型。我在电脑上用的是下面这段,可以直接抄:
python
import tensorflow as tf
from tensorflow.keras import layers
model = tf.keras.Sequential([
layers.Input(shape=(49, 13)), # MFCC 帧数 x 系数
layers.Conv1D(32, 3, activation='relu'),
layers.MaxPooling1D(2),
layers.Conv1D(64, 3, activation='relu'),
layers.GlobalAveragePooling1D(),
layers.Dense(32, activation='relu'),
layers.Dense(2, activation='softmax')
])
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
model.fit(X_train, y_train, epochs=30, validation_split=0.2)
model.save('wakeword.h5')
训完转成 tflite,模型大小 180KB 左右,这个体积板子完全吃得下。
第三步,板子上推理。这里我让 AI 帮我写了推理部分的 C 代码框架,提示词是这么给的:
markdown
我在 imx6ull(ARM Cortex-A7,512MB 内存)上跑 tflite 推理,
输入是 49x13 的 MFCC 特征,模型是两分类。
请给我一份 C 语言推理代码,要求:
1. 用 TensorFlow Lite C API
2. 输入数据从 float 数组读入
3. 输出两个概率,打印较大的那个类别
4. 代码要能直接编译,注释写清楚每步干什么
AI 给的代码基本能用,我改了两处:一是它默认用了动态库链接,我改成静态链接省得板子上找不到 so;二是它没处理内存对齐,我加了个 aligned_alloc。改完编译通过。
第四步,音频采集。I2S 麦克风采到的原始 PCM,要先在板子上算 MFCC。这一步是最大的坑,我后面细说。
四、实测效果,包括翻车的地方
先说好的:安静环境下,距离 1 米以内,唤醒成功率大概 85%,误触发大概每小时 2 到 3 次。LED 点亮延迟在 400ms 左右,能接受。
再说翻车的地方,这部分才是重点。
翻车一:板子上算 MFCC 太慢。我一开始想直接在板子上用 C 实现 MFCC,结果一帧要算 30 多毫秒,实时性根本不够。后来改成用定点运算加查表,勉强压到 8ms 一帧,才勉强跟上。这一步我折腾了整整一个下午,最后是参考了一个开源项目的实现才搞定。
翻车二:噪声环境下基本废掉。开着风扇或者旁边有人说话,误触发率飙升。我试过加一个能量阈值做前置过滤,有点用,但治标不治本。这是小模型的通病,不是调参能解决的。
翻车三:AI 给的代码有个隐藏 bug。它算 MFCC 的时候默认采样率是 16000,但我的麦克风实际是 44100,导致特征全错,模型输出一直是 50% 对 50%。我排查了大半天,最后是打印中间结果才发现的。所以 AI 给的代码,参数一定要自己核一遍。
五、适用边界,什么情况下别用它
第一,如果你的场景要求高唤醒率、低误触发,比如做产品,别用这套。老老实实上专门的唤醒芯片或者成熟的唤醒引擎,省心得多。
第二,如果板子没有硬件浮点或者主频低于 500MHz,算 MFCC 会很吃力,建议直接放弃,或者换带 DSP 的芯片。
第三,如果唤醒词要经常换,这套方案也不合适,每换一次就得重新收数据、重新训模型,成本不低。
第四,如果你只是想玩玩、学习一下端侧推理的流程,那这套方案挺合适的,成本低,链路完整,从收数据到板上跑通全走一遍,学到的东西比看十篇文章都多。
最后说句实话,我目前这套也就是个能跑的 demo,离"好用"还有距离。但整个过程走下来,我对端侧 AI 的边界心里有数了,知道什么能干什么不能干,这比结果本身值钱。有在类似板子上折腾过的朋友,欢迎留言交流,我这边还有几个坑没填完。