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);  // 避免连续触发
}

运行与验证

  1. 在Arduino IDE中选择开发板ESP32S3 Dev Module,USB CDC启用(USB CDC On Boot: Enabled),上传上述代码。
  2. 打开串口监视器(115200波特率),等待约2秒初始化完成,看到KWS Ready提示。
  3. 距离麦克风20~50cm处清晰说出“Hey, ESP”,观察串口是否打印WAKE_WORD_DETECTED且LED点亮2秒。
  4. 说其他词语(如“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(左声道模式)。
  • 持续误触发:增加训练集中的噪声样本数量,或提高检测阈值。