4. 使用Edge Impulse进行关键词唤醒
任务目标
在ESP32-S3开发板上,使用Edge Impulse完成关键词唤醒(KWS)模型的训练与部署,实现“Hey, ESP”短语的本地离线唤醒。当麦克风捕捉到该关键词时,开发板通过串口输出WAKE_WORD_DETECTED,同时板载LED点亮作为视觉反馈。
环境与依赖
- 硬件:ESP32-S3-DevKitC-1(或兼容板),INMP441 I2S数字麦克风
- 软件:Arduino IDE 2.x,ESP32 Arduino Core 3.x,Edge Impulse CLI(
edge-impulse-cli) - 接线:INMP441的SCK→GPIO4,WS→GPIO5,SD→GPIO6,L/R→GND,VDD→3.3V,GND→GND
实现方案
整体流程分为三个环节:数据采集(在Edge Impulse Studio中录制或上传“Hey, ESP”及噪声样本)→ 模型训练(使用Edge Impulse内置的KWS神经网络,MFCC特征)→ 部署推理(导出为Arduino库,在ESP32上运行实时推理)。本案例聚焦于部署端代码,训练部分在Edge Impulse云端完成,训练配置如下表所示。
| 配置项 | 参数 |
|---|---|
| 输入特征 | MFCC(40维,窗口30ms,步长20ms) |
| 模型架构 | 2层1D卷积(8/16通道)+ 全连接(16单元) |
| 训练周期 | 60 epochs,学习率0.001 |
| 目标类别 | “Hey, ESP” / 噪声(2类) |
训练完成后,在Edge Impulse Studio的Deployment页面选择Arduino Library,下载包含模型的ZIP包,并在Arduino IDE中通过Sketch → Include Library → Add .ZIP Library导入。
关键完整代码
以下代码为ESP32-S3上的完整推理程序,实现音频采集、特征提取(由Edge Impulse库自动完成)和关键词检测。将代码保存为kws_esp32.ino并上传至开发板。
#include <esp32-hal-ledc.h>
#include <driver/i2s.h>
#include <EI_hey_esp_inferencing.h> // Edge Impulse生成的模型头文件
#define I2S_WS 5
#define I2S_SCK 4
#define I2S_SD 6
#define LED_PIN 2
// I2S配置:16kHz采样率,32bit数据(实际有效16bit)
void i2s_init() {
i2s_config_t cfg = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = 16000,
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = 1024,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0
};
i2s_pin_config_t pins = {
.bck_io_num = I2S_SCK,
.ws_io_num = I2S_WS,
.data_out_num = -1,
.data_in_num = I2S_SD
};
i2s_driver_install(I2S_NUM_0, &cfg, 0, NULL);
i2s_set_pin(I2S_NUM_0, &pins);
}
// 从I2S读取音频并转换为int16_t(EI模型输入格式)
void read_audio(int16_t* buffer, size_t len) {
int32_t raw[1024];
size_t bytes_read = 0;
size_t samples_needed = len;
size_t offset = 0;
while (samples_needed > 0) {
size_t to_read = (samples_needed > 1024) ? 1024 : samples_needed;
i2s_read(I2S_NUM_0, raw, to_read * sizeof(int32_t), &bytes_read, portMAX_DELAY);
size_t samples_got = bytes_read / sizeof(int32_t);
for (size_t i = 0; i < samples_got; i++) {
buffer[offset + i] = (int16_t)(raw[i] >> 14); // 取高16位有效数据
}
offset += samples_got;
samples_needed -= samples_got;
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
i2s_init();
Serial.println("KWS Ready - 等待关键词...");
}
void loop() {
// EI模型要求输入16000Hz * 1s = 16000个样本
const int sample_count = EI_CLASSIFIER_RAW_SAMPLE_COUNT;
int16_t* audio_buffer = (int16_t*)malloc(sample_count * sizeof(int16_t));
if (!audio_buffer) return;
read_audio(audio_buffer, sample_count);
// 运行推理
signal_t signal;
int16_t* buf_ptr = audio_buffer;
signal.total_length = sample_count;
signal.get_data = [](size_t offset, size_t length, float* out_ptr) -> int {
int16_t* src = (int16_t*)buf_ptr + offset;
for (size_t i = 0; i < length; i++) {
out_ptr[i] = (float)src[i] / 32768.0f;
}
return 0;
};
ei_impulse_result_t result = {0};
EI_IMPULSE_ERROR res = run_classifier(&signal, &result, false);
if (res != EI_IMPULSE_OK) {
Serial.println("推理错误");
free(audio_buffer);
return;
}
// 判断“Hey, ESP”类别得分是否超过阈值
float kws_score = result.classification[0].value; // 类别顺序与训练时一致
if (strcmp(result.classification[0].label, "Hey, ESP") != 0) {
kws_score = result.classification[1].value;
}
if (kws_score > 0.80f) {
Serial.println("WAKE_WORD_DETECTED");
digitalWrite(LED_PIN, HIGH);
delay(2000);
digitalWrite(LED_PIN, LOW);
}
free(audio_buffer);
delay(50); // 避免连续触发
}
运行与验证
- 在Arduino IDE中选择开发板
ESP32S3 Dev Module,USB CDC启用(USB CDC On Boot: Enabled),上传上述代码。 - 打开串口监视器(115200波特率),等待约2秒初始化完成,看到
KWS Ready提示。 - 距离麦克风20~50cm处清晰说出“Hey, ESP”,观察串口是否打印
WAKE_WORD_DETECTED且LED点亮2秒。 - 说其他词语(如“Hello”或“Hey, Google”)或保持安静,确认不会触发唤醒。
通过标准:连续测试10次,至少8次能正确唤醒;干扰词测试10次,误触发不超过1次。若灵敏度不足,可调整代码中阈值0.80f(降低至0.70提高召回,升高至0.90降低误报),或返回Edge Impulse增加训练数据量。
常见问题
- 编译错误“EI_hey_esp_inferencing.h not found”:确认已通过Arduino IDE导入Edge Impulse生成的ZIP库,且库文件夹名与头文件名一致。
- 无音频输入:检查I2S引脚定义是否与接线一致,INMP441的L/R引脚必须接GND(左声道模式)。
- 持续误触发:增加训练集中的噪声样本数量,或提高检测阈值。