|
ESP32 + TinyML 离线 AI 语音识别实战:从零构建唤醒词检测系统

ESP32 + TinyML 离线 AI 语音识别实战:从零构建唤醒词检测系统

前言

「语音助手」已经成为智能家居的标配,但大多数方案都依赖云端服务——小爱同学、天猫精灵、Alexa,都需要联网才能工作。这不仅带来隐私担忧,还意味着网络延迟和服务器依赖。一旦断网,整个系统就变成了摆设。

如果能在本地完成语音识别呢?不需要联网、不需要云服务器、不需要等待云端响应,所有计算都在设备端完成。

TinyML(微型机器学习)让这成为可能。通过在 ESP32-S3 这样的微控制器上运行轻量级神经网络,我们可以实现完全离线的语音唤醒词检测,响应延迟低于 200ms,功耗低至毫瓦级别。这意味着你的设备可以在没有网络的环境下独立工作,同时保护用户隐私。

今天这篇文章,我会带你从零开始:用 Edge Impulse 训练一个语音识别模型,部署到 ESP32-S3,实现「你好,小智」这样的自定义唤醒词检测。整个过程不需要深度学习背景,也不需要昂贵的硬件。总成本不到 80 元,你就能搭建一个属于自己的离线语音助手。

TinyML 是什么?为什么选择 ESP32?

TinyML 的核心概念

TinyML 泛指在资源受限的微控制器(MCU)上运行机器学习模型的技术。它的核心挑战来自三个方面:

  • 内存限制:ESP32-S3 只有 512KB SRAM,而一个图像识别模型动辄几 MB。语音识别模型相对更小,但也要经过精心压缩才能放得下。
  • 算力限制:240MHz 的双核 CPU,没有 GPU 加速。一次推理需要在几十毫秒内完成数百万次乘加运算,这对算法优化提出了很高要求。
  • 功耗限制:电池供电设备需要毫瓦级功耗。传统的语音识别方案动辄几瓦,根本不适合边缘场景。

解决方案是模型压缩技术:量化(Quantization)将浮点权重压缩为 8 位整数,模型体积缩小 4 倍;剪枝(Pruning)移除不重要的连接,减少计算量;知识蒸馏(Knowledge Distillation)用大模型指导小模型训练,在精度损失最小化的前提下大幅减小模型体积。通过这些技术,一个原本需要 100MB 的语音模型可以压缩到 200KB 以内,精度损失控制在 5% 以内。

为什么选 ESP32-S3?

ESP32-S3 是乐鑫 2021 年推出的芯片,专为 AIoT 场景设计。相比老款 ESP32,它有三个关键升级:

  1. 向量指令加速:Xtensa LX7 处理器支持 AI Vector Instructions,专门优化了矩阵乘法和卷积运算,推理速度比老款 ESP32 快 2-3 倍
  2. 更大的内存空间:支持最大 8MB PSRAM,可以容纳更大的模型和更多的音频缓冲区
  3. 丰富的外设接口:I2S 接口直连数字麦克风,USB OTG 支持 UVC 摄像头,DVP 并行接口兼容主流图像传感器

对于语音识别任务,ESP32-S3 的 I2S 接口可以直连 MEMS 麦克风,采样率支持 16kHz(语音识别标准采样率),配合向量指令加速,推理延迟可以控制在 100-200ms。

与其他方案的对比

方案成本延迟隐私离线能力
ESP32-S3 + TinyML~50 元<200ms完全本地✅ 完全离线
树莓派 + Python~300 元500ms-2s本地/云端⚠️ 需 Linux 系统
云端 API(讯飞/百度)按量付费500ms-3s❌ 数据上传❌ 必须联网
专用语音模块(SU-03T)~30 元<100ms完全本地✅ 但命令固化

ESP32-S3 的优势在于:成本低、可自定义唤醒词、支持 WiFi/蓝牙联网扩展、社区生态丰富。专用模块虽然更便宜更快,但命令是固化的,无法自定义。树莓派方案虽然灵活,但功耗和成本都高出很多。

硬件清单

开始之前,准备以下硬件:

  • ESP32-S3 开发板:推荐 Seeed Studio XIAO ESP32S3 Sense 或 ESP32-S3-DevKitC-1。XIAO 系列体积仅 21x51mm、自带摄像头和麦克风接口,适合穿戴设备;DevKitC 引脚更多,适合原型扩展和多传感器接入。
  • INMP441 I2S MEMS 麦克风模块:数字输出、信噪比 61dB、支持 16kHz/48kHz 采样率、灵敏度 -26dBFS。如果用 XIAO ESP32S3 Sense,麦克风已经集成在扩展板上,无需额外购买。
  • USB-C 数据线:用于烧录固件和串口调试。注意要买数据线而不是纯充电线,很多便宜的线只能充电不能传数据。
  • 面包板和杜邦线:用于连接麦克风和开发板。推荐用短杜邦线(10cm 以内),减少信号干扰和噪声。
  • LED 或蜂鸣器(可选):用于指示唤醒成功,方便调试时直观确认检测结果。
  • 3.7V 锂电池(可选):用于功耗测试和移动部署场景,推荐 1000mAh 以上容量。

总成本约 50-80 元人民币,比买一个商业语音模块便宜得多,而且功能完全可定制。

硬件接线:I2S 麦克风连接

INMP441 是一款数字 MEMS 麦克风,通过 I2S(Inter-IC Sound)协议输出音频数据。I2S 是一种专为音频设计的串行总线,包含三条信号线:

  • BCLK(Bit Clock):位时钟,每个时钟周期传输一位数据
  • LRCLK(Left-Right Clock):左右声道选择时钟,低电平为左声道,高电平为右声道
  • DOUT(Data Out):数据输出线,16 位音频数据通过此线串行传输

接线图

INMP441 麦克风        ESP32-S3 开发板
─────────────────────────────────────
VDD                 3.3V
GND                 GND
BCLK                GPIO 26
LRCLK               GPIO 25
DOUT                GPIO 22
SD                  GND(芯片使能,接地为开启)
L/R                 GND(左声道模式)

注意事项

  • INMP441 工作电压 1.7-3.3V,绝对不要接 5V,否则可能烧毁模块
  • L/R 引脚接 GND 表示左声道,接 VDD 表示右声道,需要和代码中的声道设置一致
  • 如果用的是 XIAO ESP32S3 Sense,麦克风已经集成,跳过此步骤
  • 接线完成后,用万用表检查 VDD 和 GND 之间没有短路

Edge Impulse:训练语音识别模型

Edge Impulse 是一个专为嵌入式设备设计的机器学习平台,提供从数据采集、信号处理、模型训练到固件导出的全流程工具。它对个人开发者免费,支持 ESP32、Arduino、STM32 等主流平台,并且可以直接导出 Arduino 库,极大简化了部署流程。

步骤 1:创建项目

  1. 访问 edgeimpulse.com 并注册免费账号
  2. 点击 “Create new project”,命名为 esp32-voice-wakeup
  3. 选择 “Audio” 作为数据类型
  4. 采样率设置为 16000Hz(语音识别的标准采样率)
  5. 窗口长度(Sample length)设置为 1000ms(1 秒的音频片段)

步骤 2:采集音频数据

语音识别模型需要两类数据:

  • 唤醒词(Wake Word):你想识别的关键词,比如「你好小智」「开始工作」
  • 背景噪声(Background Noise):环境音、日常对话声、音乐、电视声等

每类数据至少采集 30 分钟的样本,覆盖不同场景。数据量直接决定模型质量——这是最重要的一步。

采集方法

  1. 在 Edge Impulse 控制台选择 “Data acquisition”
  2. 连接 ESP32-S3 作为采集设备(选择 “From microphone” 模式)
  3. 点击 “Start sampling”,说出唤醒词或录制环境音
  4. 每次采样 1 秒,重复 50 次以上

数据采集技巧

  • 在不同距离采集(10cm、50cm、1m),模拟真实使用场景
  • 在不同环境采集(安静房间、有背景音乐、多人交谈的办公室)
  • 让不同的人说唤醒词,增加模型的泛化能力
  • 背景噪声要尽量多样化,包含键盘声、脚步声、电视声等
  • 唤醒词和背景噪声的数据量比例建议 1:2,防止过拟合

步骤 3:设计特征提取

语音识别的关键是提取 MFCC(Mel-frequency cepstral coefficients,梅尔频率倒谱系数)特征。MFCC 模拟人耳的频率感知特性——人耳对低频声音的分辨能力高于高频,梅尔刻度正是基于这一特性设计的。MFCC 是语音识别领域最经典、最成熟的特征提取方法。

在 Edge Impulse 中配置:

  1. 进入 “Create impulse” 页面
  2. 添加 “Audio” → “MFE”(Mel-frequency energies)信号处理块
  3. 参数设置:Frame size 32ms,Frame stride 16ms,Number of coefficients 13
  4. 添加 “Classification” 学习块

MFE 处理块会自动将 1 秒的音频切分为多个帧,计算每帧的梅尔频率能量谱,生成一个二维特征矩阵。这个矩阵就是神经网络的输入。

步骤 4:训练模型

  1. 进入 “NN Classifier” 页面
  2. 设置网络结构:
    • Dense layer 1: 64 neurons, ReLU activation
    • Dropout 1: 0.25(防止过拟合)
    • Dense layer 2: 32 neurons, ReLU activation
    • Dropout 2: 0.25
    • Dense layer 3: number of classes, Softmax activation
  3. 设置训练参数:
    • Epochs: 50
    • Learning rate: 0.001
    • Batch size: 32
  4. 点击 “Train”,等待 5-10 分钟

模型评估: 训练完成后,查看 “Model testing” 页面的混淆矩阵。好的模型应该达到 90% 以上的准确率。如果低于 85%,需要增加训练数据、调整网络结构,或者检查数据质量(是否有标注错误、噪声过多等)。

步骤 5:导出模型

  1. 进入 “Deployment” 页面
  2. 选择 “Arduino library”
  3. 勾选 “Quantize model (INT8)“——将模型量化为 8 位整数,模型体积缩小 4 倍,推理速度提升 2-3 倍
  4. 点击 “Build”,下载 .zip 文件

导出的库包含训练好的模型权重和推理代码,可以直接在 Arduino IDE 中作为库导入使用。

Arduino 代码:部署到 ESP32-S3

步骤 1:安装开发环境

在 Arduino IDE 中完成以下配置:

  1. 安装 ESP32 开发板支持

    • 打开 “File” → “Preferences” → “Additional Board Manager URLs”
    • 添加 https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
    • 在 “Tools” → “Board” → “Board Manager” 中搜索 “esp32” 并安装最新版
  2. 导入 Edge Impulse 库

    • “Sketch” → “Include Library” → “Add .ZIP Library”
    • 选择刚才下载的 .zip 文件
  3. 配置开发板参数

    • Board: XIAO ESP32S3 Sense(或 ESP32S3 Dev Module)
    • PSRAM: “OPI PSRAM”(如果使用 XIAO Sense)
    • Flash Size: 8MB(或你的开发板实际大小)
    • Upload Speed: 921600

步骤 2:完整代码

// ESP32-S3 TinyML 离线语音唤醒词检测
// 需要安装 Edge Impulse Arduino 库

#include <esp32s3_ai_inference.h>
#include "esp32_voice_wakeup_inference.h"  // Edge Impulse 导出的库

// I2S 麦克风引脚定义(根据实际接线修改)
#define I2S_BCLK    26
#define I2S_LRCLK   25
#define I2S_DOUT    22

// LED 指示引脚
#define LED_PIN     2

// 置信度阈值(0.0-1.0),高于此值才触发唤醒
#define CONFIDENCE_THRESHOLD  0.80

// 音频缓冲区
static int16_t audio_buffer[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE];

// 初始化 I2S 麦克风
void init_microphone() {
    i2s_config_t i2s_config = {
        .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
        .sample_rate = 16000,
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
        .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
        .communication_format = I2S_COMM_FORMAT_STAND_I2S,
        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
        .dma_buf_count = 4,
        .dma_buf_len = 512,
    };

    i2s_pin_config_t pin_config = {
        .bck_io_num   = I2S_BCLK,
        .ws_io_num    = I2S_LRCLK,
        .data_out_num = I2S_PIN_NO_CHANGE,
        .data_in_num  = I2S_DOUT,
    };

    i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
    i2s_set_pin(I2S_NUM_0, &pin_config);
    i2s_zero_dma_buffer(I2S_NUM_0);
    Serial.println("✓ I2S 麦克风初始化完成");
}

// 从麦克风读取一帧音频数据
void read_audio() {
    size_t bytes_read = 0;
    i2s_read(I2S_NUM_0, audio_buffer, sizeof(audio_buffer),
             &bytes_read, portMAX_DELAY);
}

void setup() {
    Serial.begin(115200);
    while (!Serial) { delay(10); }
    Serial.println("═══════════════════════════════════");
    Serial.println("  ESP32-S3 TinyML 语音唤醒系统");
    Serial.println("═══════════════════════════════════");

    // 初始化 LED 指示灯
    pinMode(LED_PIN, OUTPUT);
    digitalWrite(LED_PIN, LOW);

    // 初始化麦克风
    init_microphone();

    // 打印模型信息
    Serial.printf("模型输入大小: %d 采样点\n", EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);
    Serial.printf("采样率: %d Hz\n", EI_CLASSIFIER_FREQUENCY);
    Serial.printf("类别数: %d\n", EI_CLASSIFIER_LABEL_COUNT);
    for (int i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) {
        Serial.printf("  类别 %d: %s\n", i, ei_classifier_get_label(i));
    }
    Serial.println("═══════════════════════════════════");
    Serial.println("开始监听唤醒词...");
}

void loop() {
    // 1. 从麦克风读取音频数据
    read_audio();

    // 2. 运行推理(特征提取 + 分类)
    ei_impulse_result_t result = {0};
    EI_IMPULSE_ERROR_t res = run_classifier(audio_buffer, &result, false);

    if (res != EI_IMPULSE_OK) {
        Serial.printf("推理失败,错误码: %d\n", res);
        return;
    }

    // 3. 查找置信度最高的类别
    int max_index = 0;
    float max_value = 0;
    for (int i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) {
        if (result.classification[i].value > max_value) {
            max_value = result.classification[i].value;
            max_index = i;
        }
    }

    // 4. 如果唤醒词置信度超过阈值,触发动作
    if (max_value > CONFIDENCE_THRESHOLD
        && strcmp(result.classification[max_index].label, "wakeup_word") == 0) {

        uint32_t total_ms = result.timing.dsp_us / 1000
                          + result.timing.classification_us / 1000;

        Serial.printf("\n🎤 检测到唤醒词!\n");
        Serial.printf("   置信度: %.1f%%\n", max_value * 100);
        Serial.printf("   推理延迟: %d ms\n", total_ms);
        Serial.printf("   特征提取: %d ms\n", result.timing.dsp_us / 1000);
        Serial.printf("   模型推理: %d ms\n\n", result.timing.classification_us / 1000);

        // LED 闪烁 1 秒作为视觉反馈
        digitalWrite(LED_PIN, HIGH);
        delay(500);
        digitalWrite(LED_PIN, LOW);

        // 在这里添加后续动作,例如:
        // - 通过 MQTT 发送指令到 Home Assistant
        // - 启动后续的语音命令识别
        // - 控制继电器打开灯光
        // - 播放提示音(通过 I2S 接口的 DAC)
    }

    delay(50);  // 50ms 间隔,平衡响应速度和 CPU 占用
}

步骤 3:烧录和测试

  1. 用 USB-C 线连接 ESP32-S3 到电脑
  2. 在 Arduino IDE 中选择正确的开发板型号和串口
  3. 点击 “Upload” 烧录固件(首次烧录可能需要按住 BOOT 键)
  4. 打开串口监视器(波特率 115200)
  5. 对着麦克风说出唤醒词,观察串口输出

调试技巧

  • 如果检测不到唤醒词,先检查麦克风接线是否正确,用示波器或逻辑分析仪检查 I2S 信号
  • 如果误触发率高(没说话也触发),提高置信度阈值(从 0.80 调到 0.90)
  • 如果漏检率高(说了检测不到),降低阈值(从 0.80 调到 0.70),同时检查训练数据是否足够
  • 查看推理延迟,正常应该在 100-200ms 范围内

功耗优化与性能基准测试

推理性能基准

在 ESP32-S3 @ 240MHz、开启 PSRAM 的条件下测试:

指标数值
模型大小(量化后)180 KB
总推理延迟120-180 ms
特征提取耗时30-50 ms
分类推理耗时90-130 ms
SRAM 占用45 KB
测试准确率92-95%

功耗优化策略

对于电池供电的 IoT 设备,功耗是核心指标。以下是几种实用的优化方法:

1. 降低 CPU 频率

推理不需要 240MHz,160MHz 就足够:

// 降低 CPU 频率到 160MHz,功耗降低约 30%
setCpuFrequencyMhz(160);

2. 间歇监听模式

不需要持续监听时,让 MCU 在两次检测之间进入轻量睡眠:

// 每 5 秒监听一次,其余时间进入 light sleep
esp_sleep_enable_timer_wakeup(5 * 1000000);  // 5 秒
esp_light_sleep_start();

3. 关闭不必要的无线模块

如果不需要联网,彻底关闭 WiFi 和蓝牙:

WiFi.mode(WIFI_OFF);
btStop();

功耗测试结果

工作模式平均功耗2000mAh 电池续航
持续监听(240MHz)120 mA~16 小时
持续监听(160MHz)85 mA~23 小时
间歇监听(5 秒间隔)15 mA~130 小时
深度睡眠 + 定时唤醒0.5 mA~4000 小时

使用间歇监听模式,一块 2000mAh 的锂电池可以支撑 5 天以上的续航。如果进一步结合深度睡眠,续航可以延长到数月。

扩展应用

这个离线语音唤醒系统可以扩展到很多实际场景:

智能家居控制:唤醒后通过 WiFi/MQTT 发送指令到 Home Assistant,控制灯光、空调、窗帘等设备。也可以扩展为多级命令识别,比如「开灯」「关灯」「调高温度」。

安防监控:检测异常声音(玻璃破碎声、警报声、婴儿哭声),结合 ESP32 的摄像头做声源定位和录像触发。

工业预测性维护:采集设备运行声音,训练异常检测模型。当机器发出异常声响时自动报警,比传统的振动传感器方案更直观。

无障碍辅助:为行动不便的人士提供语音控制接口,离线工作确保隐私安全,不受网络状况影响。

常见问题

Q: 模型准确率不够高怎么办? A: 增加训练数据是最有效的方法。尝试在不同环境、不同距离、不同说话人采集数据。也可以使用 Edge Impulse 的数据增强功能(添加噪声、时间偏移、速度变化)来扩充数据集。

Q: 可以识别多个唤醒词或命令词吗? A: 可以。在 Edge Impulse 中为每个关键词创建一个类别,训练多分类模型。但建议命令词不超过 5 个,否则准确率会下降。如果需要更多命令,建议采用两级架构:唤醒词 + 命令词。

Q: 如何提高推理速度? A: 启用 PSRAM(外部内存)释放 SRAM、降低模型复杂度(减少神经元数量)、使用 INT8 量化。ESP32-S3 的向量指令在 ESP-IDF 5.x 中已自动启用。

Q: 能用中文唤醒词吗? A: 完全可以。Edge Impulse 支持任何语言的语音数据,只要采集足够的中文训练样本即可。中文唤醒词建议选择 3-4 个音节的短语,比如「你好小智」「开始工作」。

总结

通过这篇文章,我们实现了一个完全离线的 ESP32-S3 语音唤醒系统。核心要点回顾:

  1. TinyML 让边缘 AI 成为可能:在几美元的微控制器上运行神经网络,不需要云端依赖
  2. Edge Impulse 简化了开发流程:从数据采集到模型部署,全程可视化操作,不需要手写训练代码
  3. ESP32-S3 是理想的 TinyML 平台:向量指令加速、低功耗、丰富的外设接口、活跃的社区生态
  4. 离线语音识别有广泛应用:智能家居、安防监控、工业维护、无障碍辅助

下一步,你可以尝试:

  • 集成更多传感器(温湿度、加速度计、气压计),打造多模态感知节点
  • 添加语音命令识别(不只是唤醒词,还能识别「开灯」「关灯」等指令)
  • 用 ESP-NOW 组建多节点语音网络,实现全屋覆盖
  • 结合我们之前的 ESP32-S3 图像识别文章 实现音视频双模态 AI

有问题欢迎在评论区讨论,或者加入我们的 Telegram 群组交流硬件开发经验。下次见!