首页
看点啥
插画图片
首页 看点啥 在 ESP32 上跑自定义唤醒词:从 TFLite 到离线语音唤醒:一个下午搞定

在 ESP32 上跑自定义唤醒词:从 TFLite 到离线语音唤醒:一个下午搞定

2026-07-14 0

在 ESP32 上跑自定义唤醒词:从 TFLite 到离线语音唤醒,一个下午搞定

为什么选 TFLite + ESP32?

如果你在做智能家居、机器人或者任何需要语音交互的硬件项目,大概率会遇到一个需求:设备能听懂一句特定的唤醒词。

市面上方案不少,但大部分要么贵(云端 ASR),要么绑定特定词("小爱同学"),要么需要联网。而 ESP32-S3 这颗芯片——双核 240MHz、自带 8MB PSRAM、价格不到 30 块——已经足够在本地跑一个完整的神经网络唤醒词检测。

关键组合是 TFLite Micro + ESP-IDF。TFLite 是 Google 的推理引擎,专门为嵌入式设备优化过,ESP-IDF 直接内置了对它的组件支持。模型用 INT8 量化后不到 80KB,推理一次 ~360ms,配合 5 层检测管线,安静环境误触率极低。

唤醒词模型到底在算什么?

很多同学听到"神经网络"就觉得门槛高。其实唤醒词模型做的事情非常直观:

音频 (16kHz) → Mel 频谱 [98帧 × 32维] → Backbone → [256维向量] → Head → 概率 [0~1]

Mel 频谱:把人耳听到的声音画成一张"图"。横轴是时间,纵轴是频率,颜色深浅代表能量大小。1 秒音频被切成 98 帧,每帧 32 个频率通道。

Backbone:模型的主体,负责从这张"图"里提取特征。常见的是 DS-TCN(深度可分离时序卷积网络)或 SRNN。它输出一个 256 维向量——可以理解为"这段音频的关键特征浓缩版"。

Head:把 256 维向量变成一个概率。常用的 MultiProto head 只有 325 个参数(5 个原型 × 256 维 + 一个线性分类器),体积几乎可以忽略。它做的事:L2 归一化 → 和原型算余弦相似度 → 线性加权 → sigmoid。

Mel 频谱Backbone Head 输出┌──────────┐ ┌────────┐┌─────────┐│········│ │ SRNN ││ prob=0.9 │ ← 说出唤醒词│████████│──────→│or│──────→ │ prob=0.0 │ ← 安静/噪音│████████│ │ DS-TCN ││ prob=0.1 │ ← 其他词│········│ └────────┘└─────────┘└──────────┘ ↑ 98×32 256维向量

模型到手后只需要两个文件:.tflite(backbone,INT8 量化,~76KB)和 head.h(MultiProto 权重,~17KB)。

集成 onnx-wakeword —— 3 步跑起来

voicute/onnx-wakeword 是一个完整的 ESP-IDF 开源项目,包含唤醒词推理引擎和完整 Demo。

项目结构

esp32/├── components/voicute/← 核心库(不依赖具体模型)├── main/← Demo 应用│ ├── main.cpp唤醒词 + 语音命令完整示例│ └── head.h模型 head(用户替换)├── spiffs_content/← 放 .tflite 模型├── build.bat / flash.bat← 一键编译烧录└── sdkconfig.defaults ← 预配好 PSRAM、Flash

第一步:Clone + 编译

git clone https://github.com/voicute/onnx-wakeword.gitcd onnx-wakeword/esp32build.bat # Windows

首次编译会下载 ESP-IDF 依赖(esp-tflite-micro、esp-dsp、esp-sr 等),之后双击 build.bat 就行。已经预配好了 ESP32-S3、16MB Flash、PSRAM,开箱即用。

第二步:替换模型

cp your_wakeword.tflite spiffs_content/cp your_head.h main/head.h

第三步:烧录

flash.batpython wake_monitor.py COM5 # 观察实时 prob/RMS

核心代码就这么几行

#include "recognizer.h"#include "head.h"#include "kws_postprocess.h"// 唤醒回调static void on_wake(voice_event_t ev, voice_evt_data_t d, void *u) {if (ev == VOICE_EVT_AWAKEN) ESP_LOGI("APP", ">>> 关键词!");}void app_main() {recognizer_config_t cfg = {.model_path = "/spiffs",.threshold= 0.70f,.l5_enabled = 1,// 能量跳变过滤(防音乐/视频误触).l5_delta = 200.0f,.postprocess= kws_postprocess,};recognizer_start(&cfg);recognizer_register_callback(0, on_wake, NULL);while (1) {// 从麦克风采集 16192 采样点int64_t now = esp_timer_get_time() / 1000;recognizer_run_frame(pcm, rms, now);}}

Demo 还包括 MultiNet 语音命令(唤醒后说"灯光变红/蓝/绿"),完整展示了唤醒→进入命令模式的流程。不需要的话删掉 sdkconfig.defaults 里相关配置即可。

5 层检测管线

拿概率只是一个基础。真正决定体验的是误触发控制。onnx-wakeword 内置了和 Android 端同款的 L1-L5 检测管线:

作用说明
L1连续帧确认N 帧连续超阈值才放行
L2峰/底比峰值必须远高于背景概率
L3冷却1.5s 内不重复触发
L4突发阻断短时间连触发 → 拉黑 5s
L5能量跳变当前音量必须明显高于历史窗口 → 排除平稳音乐/视频声

L5 是最核心的过滤器。它的逻辑是:如果 RMS 突然跳变(说明有人在说话),进入待确认;等 400ms 后检查尾音是否降回安静水平——是就确认唤醒,否则拦截。这个机制能挡住绝大多数 TV、视频、音乐误触。

调参实战

如果你是第一次跑,推荐配置:L5 开,其他层关。ESP32 推理一帧 ~360ms,唤醒词 ~500ms,最多被一帧覆盖,L1 连续帧确认不适用;L2/L3 按需开。

如果遇到误触发,两步排查:

  1. 先开 L5(l5_enabled=1),把 l5_delta 设到 200。这个值适合安静环境,意味着"当前音量比之前 0.52 秒的最低音量高 200 才放行"。安静时底噪 RMS 一般在 1050,说话时能到 300~1000,200 的差值足够区分。

  2. 如果还是容易误触发,把 l5_delta 调大:

.l5_enabled = 1,.l5_delta = 800.0f,// 从 200 加大到 800,嘈杂环境用

环境建议 delta安静 RMS说话 RMS
安静房间200~40010~50200~500
普通室内400~80050~150300~800
嘈杂/有音乐800~1200100~300500~1500

如果怎么调都唤不醒(l5_delta 太大导致说话也被拦截),就看串口日志里的 L5 steady: curRms=xxx preMin=xxx,把 delta 调到比你说话时的 RMS 差值略小就行。反之,如果没说话也莫名其妙触发,delta 往大了加。

获得自己的唤醒词模型

不用准备数据,不用 GPU,不用懂深度学习。

打开 voicute.com,输入你想用的关键词,点击生成,直接下载两个文件:

然后重新编译烧录就行了。从改词到跑通,十分钟。

总结

你需要的去哪搞
ESP-IDF 推理引擎 + Demogithub.com/voicute/onn…
自定义唤醒词模型voicute.com 输关键词直接下载

从零到在自己的 ESP32 设备上喊出专属唤醒词,最快一个下午。离线、免费、想换就换。

喜欢(0)

上一篇

我开源了一套 AI Agent 框架:有性格 有规则 还能自己造工具

我开源了一套 AI Agent 框架:有性格 有规则 还能自己造工具

下一篇

Callback 系统:Eino 的事件总线(第52篇-E38)

Callback 系统:Eino 的事件总线(第52篇-E38)
猜你喜欢