基于 ESP32 与机器学习的智能语音家居控制系统

一、系统概述

本系统以ESP32(或 ESP32-S3) 为核心,融合机器学习技术实现本地化语音识别,无需依赖云端即可完成语音唤醒、命令解析与家居设备控制。系统支持自定义唤醒词(如 "小 ESP")和常用控制命令(如 "打开灯光""关闭风扇"),通过继电器模块控制家电,同时提供语音反馈(如 "已打开空调")。相比传统云端语音控制,本系统具有响应速度快(<500ms)、隐私性强(本地处理)、无网络依赖的优势,适合家庭、办公室等场景的智能控制。

二、核心技术与方案设计

1. 技术架构

系统采用 "本地语音处理 + 机器学习推理 + 设备控制" 的架构,关键技术包括:

  • 语音采集与预处理:通过麦克风采集语音信号,进行降噪、分帧、特征提取(MFCC 梅尔频率倒谱系数);
  • 机器学习推理:基于 TensorFlow Lite for Microcontrollers(TFLite-Micro)部署轻量级语音识别模型,实现唤醒词检测和命令分类;
  • 设备控制:通过继电器 / 蓝牙模块控制灯光、插座、风扇等家居设备;
  • 语音反馈:通过扬声器播放预设提示音或简单 TTS(文本转语音)反馈。

2. 机器学习模型设计

  • 唤醒词模型:采用轻量级 CNN(卷积神经网络),基于 Google Speech Commands 数据集的自定义唤醒词(如 "小 ESP")训练,模型大小控制在 50KB 以内,适合 ESP32 内存;
  • 命令识别模型:采用 DNN(深度神经网络),识别 10-20 条常用命令(如 "打开灯光""调高温度"),输入为 MFCC 特征,输出为命令类别概率。

三、硬件选型

组件名称 型号规格 用途说明 数量
主控模块 ESP32-S3-WROOM-1 高性能版本,支持 I2S 接口、更大 RAM(320KB),适合运行 TFLite 模型 1
语音采集模块 INMP441(I2S 数字麦克风) 低噪声、数字输出,支持 I2S 协议,直接与 ESP32 的 I2S 接口通信 1
音频输出模块 8Ω 0.5W 扬声器 + LM386 功放 播放反馈提示音(如 "已执行"),LM386 用于驱动扬声器 1 套
家居控制模块 4 路继电器模块(5V) 控制灯光、插座等 220V 家电(需注意强电隔离) 1
辅助组件 按键(用于模型校准)、LED 指示灯、杜邦线、5V 电源 手动触发校准、状态指示、供电 若干

硬件选型说明:

  • ESP32-S3:相比基础款 ESP32,增加了硬件浮点运算单元和更大的 SRAM,能更高效运行机器学习模型,且支持 I2S 接口直接连接数字麦克风,减少信号干扰;
  • INMP441:数字麦克风避免了模拟信号的噪声干扰,I2S 接口可通过 DMA 方式高速采集语音数据,适合实时处理;
  • 继电器模块:需通过光耦隔离强电与 ESP32,避免干扰或损坏主控。

四、硬件接线图

核心接线表(ESP32-S3 引脚 → 模块引脚)

ESP32-S3 引脚 模块名称 模块引脚 备注说明
3.3V INMP441 VDD 麦克风供电(3.3V)
GND INMP441 GND 共地
GPIO18(I2S_BCK) INMP441 BCK I2S 时钟线
GPIO17(I2S_WS) INMP441 WS I2S 声道选择线
GPIO16(I2S_DO) INMP441 DO I2S 数据输出线
GPIO4 LM386 功放 IN 音频信号输入(PWM/DAC)
5V 继电器模块 VCC 继电器供电(需独立 5V,避免 ESP32 过载)
GND 继电器模块 GND 共地
GPIO21 继电器模块 IN1 控制灯光(高电平触发)
GPIO22 继电器模块 IN2 控制风扇
GPIO2 校准按键 一端(另一端接 GND) 长按进入模型校准模式
GPIO13 状态 LED(绿) 阳极(串 220Ω 电阻) 系统运行正常指示

接线示意图(简化)

plaintext

复制代码
ESP32-S3        INMP441(麦克风)      继电器模块          音频模块  
3.3V  ──────── VDD                    ────────            ────────  
GND   ──────── GND                    GND                 GND  
GPIO18──────── BCK  
GPIO17──────── WS  
GPIO16──────── DO  
GPIO21──────── IN1(灯光)  
GPIO22──────── IN2(风扇)  
GPIO4 ──────── LM386输入  
5V    ──────── 继电器VCC  

五、软件设计

1. 开发环境与依赖

  • 开发工具:Arduino IDE 2.0+(需安装 ESP32-S3 开发板支持)
  • 核心库:
    • TensorFlowLite_ESP32:TFLite-Micro 在 ESP32 上的移植库,用于模型推理;
    • ESP32_I2S:驱动 I2S 麦克风采集语音数据;
    • SimpleAudioPlayer:通过 PWM 输出音频反馈;
    • PubSubClient(可选):如需远程控制,可通过 MQTT 连接家居网关。

2. 系统流程图

3. 核心代码实现

(1)初始化与模型加载
复制代码
#include <Arduino.h>  
#include <TensorFlowLite.h>  
#include "tensorflow/lite/micro/all_ops_resolver.h"  
#include "tensorflow/lite/micro/micro_error_reporter.h"  
#include "tensorflow/lite/micro/micro_interpreter.h"  
#include "tensorflow/lite/schema/schema_generated.h"  
#include "tensorflow/lite/version.h"  

// 引入模型(唤醒词模型和命令识别模型,需提前转换为C数组)  
#include "wake_word_model.h"    // 唤醒词模型(自定义"小ESP")  
#include "command_model.h"      // 命令识别模型(10条命令)  

// I2S麦克风配置  
#include <driver/i2s.h>  
#define I2S_BCK_PIN 18  
#define I2S_WS_PIN 17  
#define I2S_DO_PIN 16  

// 设备控制引脚  
#define RELAY_LIGHT 21   // 灯光继电器  
#define RELAY_FAN 22     // 风扇继电器  
#define LED_STATUS 13    // 状态指示灯  

// TFLite相关变量  
tflite::MicroErrorReporter tflite_error_reporter;  
tflite::AllOpsResolver tflite_resolver;  
const tflite::Model* wake_word_model = nullptr;  
tflite::MicroInterpreter* wake_word_interpreter = nullptr;  
const tflite::Model* command_model = nullptr;  
tflite::MicroInterpreter* command_interpreter = nullptr;  

// 模型输入输出缓冲区(根据模型输入维度定义)  
const int WAKE_WORD_INPUT_SIZE = 490;  // 唤醒词模型输入长度(MFCC特征)  
float wake_word_input_buffer[WAKE_WORD_INPUT_SIZE];  
const int COMMAND_INPUT_SIZE = 980;    // 命令模型输入长度  
float command_input_buffer[COMMAND_INPUT_SIZE];  

void setup() {  
  Serial.begin(115200);  
  pinMode(RELAY_LIGHT, OUTPUT);  
  pinMode(RELAY_FAN, OUTPUT);  
  pinMode(LED_STATUS, OUTPUT);  
  digitalWrite(LED_STATUS, HIGH);  // 系统启动完成  

  // 初始化I2S麦克风  
  i2s_config_t i2s_config = {  
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),  
    .sample_rate = 16000,  // 语音识别常用16kHz采样率  
    .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_BCK_PIN,  
    .ws_io_num = I2S_WS_PIN,  
    .data_out_num = I2S_PIN_NO_CHANGE,  // 仅输入,无输出  
    .data_in_num = I2S_DO_PIN  
  };  
  i2s_driver_install(I2S_NUM_0, &i2s_config, 0, nullptr);  
  i2s_set_pin(I2S_NUM_0, &pin_config);  

  // 加载唤醒词模型  
  wake_word_model = tflite::GetModel(wake_word_model_data);  // 模型数据来自wake_word_model.h  
  if (wake_word_model->version() != TFLITE_SCHEMA_VERSION) {  
    Serial.println("唤醒词模型版本不匹配!");  
    while (1);  
  }  
  // 为解释器分配内存(根据模型大小调整)  
  static uint8_t wake_word_tensor_arena[8 * 1024];  // 8KB内存池  
  wake_word_interpreter = new tflite::MicroInterpreter(  
    wake_word_model, tflite_resolver, wake_word_tensor_arena,  
    sizeof(wake_word_tensor_arena), &tflite_error_reporter  
  );  
  wake_word_interpreter->AllocateTensors();  

  // 加载命令识别模型(流程同上)  
  command_model = tflite::GetModel(command_model_data);  
  static uint8_t command_tensor_arena[16 * 1024];  // 16KB内存池  
  command_interpreter = new tflite::MicroInterpreter(  
    command_model, tflite_resolver, command_tensor_arena,  
    sizeof(command_tensor_arena), &tflite_error_reporter  
  );  
  command_interpreter->AllocateTensors();  

  Serial.println("系统初始化完成,等待唤醒词...");  
}  
(2)语音采集与预处理(MFCC 提取)
复制代码
// 采样缓冲区(16位PCM数据)  
int16_t audio_buffer[1024];  
// MFCC特征提取(简化版,实际需实现梅尔滤波器组)  
void extractMFCC(int16_t* pcm_data, int pcm_len, float* mfcc_out, int mfcc_len) {  
  // 步骤:1. 预加重(高通滤波);2. 分帧(20ms一帧,16kHz下320点);3. 加窗(汉宁窗);  
  // 4. FFT变换;5. 梅尔滤波;6. 取对数;7. DCT变换得到MFCC特征  
  // 此处为简化代码,实际需根据语音处理库实现(如使用kissFFT)  
  for (int i = 0; i < mfcc_len; i++) {  
    mfcc_out[i] = (float)pcm_data[i % pcm_len] / 32768.0;  // 临时占位,需替换为真实MFCC  
  }  
}  

// 采集唤醒词语音(1秒)并提取MFCC  
bool collectWakeWordAudio() {  
  size_t bytes_read;  
  i2s_read(I2S_NUM_0, audio_buffer, sizeof(audio_buffer), &bytes_read, portMAX_DELAY);  
  int samples_read = bytes_read / 2;  // 16位数据,字节数/2=样本数  
  if (samples_read < 16000) {  // 16kHz下1秒=16000样本  
    Serial.println("唤醒词采集数据不足!");  
    return false;  
  }  
  extractMFCC(audio_buffer, samples_read, wake_word_input_buffer, WAKE_WORD_INPUT_SIZE);  
  return true;  
}  
(3)机器学习推理与命令执行
复制代码
// 唤醒词识别(返回是否检测到唤醒词)  
bool detectWakeWord() {  
  if (!collectWakeWordAudio()) return false;  
  // 将MFCC特征输入模型  
  float* input = wake_word_interpreter->input(0)->data.f;  
  memcpy(input, wake_word_input_buffer, WAKE_WORD_INPUT_SIZE * sizeof(float));  
  // 推理  
  TfLiteStatus status = wake_word_interpreter->Invoke();  
  if (status != kTfLiteOk) {  
    Serial.println("唤醒词推理失败!");  
    return false;  
  }  
  // 输出为"唤醒词"概率(假设输出索引0为唤醒词,1为非唤醒词)  
  float* output = wake_word_interpreter->output(0)->data.f;  
  return (output[0] > 0.8);  // 概率>80%认为检测到  
}  

// 命令识别(返回命令ID:0=打开灯光,1=关闭灯光,2=打开风扇...)  
int recognizeCommand() {  
  // 采集3秒命令语音(16kHz*3=48000样本)  
  size_t total_samples = 0;  
  while (total_samples < 48000) {  
    size_t bytes_read;  
    i2s_read(I2S_NUM_0, audio_buffer, sizeof(audio_buffer), &bytes_read, portMAX_DELAY);  
    total_samples += bytes_read / 2;  
  }  
  // 提取MFCC特征  
  extractMFCC(audio_buffer, total_samples, command_input_buffer, COMMAND_INPUT_SIZE);  
  // 输入模型推理  
  float* input = command_interpreter->input(0)->data.f;  
  memcpy(input, command_input_buffer, COMMAND_INPUT_SIZE * sizeof(float));  
  TfLiteStatus status = command_interpreter->Invoke();  
  if (status != kTfLiteOk) {  
    Serial.println("命令推理失败!");  
    return -1;  
  }  
  // 取概率最大的命令ID  
  float* output = command_interpreter->output(0)->data.f;  
  int max_idx = 0;  
  float max_prob = 0;  
  for (int i = 0; i < 10; i++) {  // 假设有10条命令  
    if (output[i] > max_prob) {  
      max_prob = output[i];  
      max_idx = i;  
    }  
  }  
  return (max_prob > 0.7) ? max_idx : -1;  // 概率>70%认为有效  
}  

// 执行命令  
void executeCommand(int cmd_id) {  
  switch(cmd_id) {  
    case 0:  // 打开灯光  
      digitalWrite(RELAY_LIGHT, HIGH);  
      playAudio("打开灯光");  // 播放反馈(需提前录制或TTS生成)  
      break;  
    case 1:  // 关闭灯光  
      digitalWrite(RELAY_LIGHT, LOW);  
      playAudio("关闭灯光");  
      break;  
    case 2:  // 打开风扇  
      digitalWrite(RELAY_FAN, HIGH);  
      playAudio("打开风扇");  
      break;  
    case 3:  // 关闭风扇  
      digitalWrite(RELAY_FAN, LOW);  
      playAudio("关闭风扇");  
      break;  
    default:  
      playAudio("未识别命令");  
  }  
}  
(4)主循环
复制代码
void loop() {  
  // 监听唤醒词  
  if (detectWakeWord()) {  
    Serial.println("检测到唤醒词!");  
    digitalWrite(LED_STATUS, LOW);  // 状态灯闪烁提示  
    delay(300);  
    digitalWrite(LED_STATUS, HIGH);  

    playAudio("请说命令");  // 提示用户输入命令  

    // 识别命令并执行  
    int cmd = recognizeCommand();  
    if (cmd != -1) {  
      executeCommand(cmd);  
    } else {  
      playAudio("未听清,请重试");  
    }  

    Serial.println("返回唤醒词监听...");  
  }  
  delay(100);  // 降低CPU占用  
}  

六、机器学习模型训练与部署

1. 数据准备

  • 唤醒词数据:录制 500 条 "小 ESP" 语音(不同人、不同距离、带环境噪声),以及 5000 条非唤醒词语音(日常对话、背景噪声);
  • 命令数据:每条命令(如 "打开灯光")录制 300 条,覆盖不同口音和语速。

2. 模型训练(基于 TensorFlow)

复制代码
# 简化的模型训练代码(Python)  
import tensorflow as tf  
from tensorflow.keras import layers  

# 唤醒词模型(轻量级CNN)  
model = tf.keras.Sequential([  
  layers.Reshape((49, 10), input_shape=(490,)),  # 490=49帧×10维MFCC  
  layers.Conv1D(16, 3, activation='relu'),  
  layers.GlobalMaxPooling1D(),  
  layers.Dense(32, activation='relu'),  
  layers.Dense(2, activation='softmax')  # 2类:唤醒词/非唤醒词  
])  
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])  

# 训练模型(使用准备好的MFCC特征数据)  
model.fit(train_x, train_y, epochs=20, validation_data=(val_x, val_y))  

# 转换为TFLite-Micro模型  
converter = tf.lite.TFLiteConverter.from_keras_model(model)  
converter.optimizations = [tf.lite.Optimize.DEFAULT]  # 量化压缩  
tflite_model = converter.convert()  
with open('wake_word_model.tflite', 'wb') as f:  
  f.write(tflite_model)  

3. 模型部署到 ESP32

  • 使用xxd工具将.tflite模型转换为 C 数组(可被 Arduino 代码引用):

    bash

    复制代码
    xxd -i wake_word_model.tflite > wake_word_model.h  
  • 生成的wake_word_model.h中包含wake_word_model_data数组,直接在代码中引用(见初始化部分)。

七、系统优化与扩展

1. 性能优化

  • 模型轻量化:通过量化(INT8)将模型体积压缩 50% 以上,减少内存占用;
  • 推理加速:利用 ESP32-S3 的硬件浮点单元(FPU),或使用 TFLite-Micro 的 CMSIS-NN 优化库;
  • 降噪处理:添加自适应滤波算法(如 NLMS)去除环境噪声,提高识别准确率。

2. 功能扩展

  • 多设备控制:增加红外发射模块(如 IRremote 库),控制空调、电视等红外设备;
  • 蓝牙联动:通过 ESP32 的 BLE 连接智能手环,实现 "靠近自动解锁" 等场景;
  • OTA 模型更新:支持通过 WiFi 远程更新语音模型,无需重新烧录固件;
  • 自定义命令:通过手机 APP 录制新命令,本地增量训练模型(需简化训练逻辑)。

八、注意事项

  1. 电源设计:继电器和功放模块需独立 5V 供电,避免 ESP32 电源波动导致语音采集噪声;
  2. 麦克风安装:远离继电器、电机等电磁干扰源,建议加装海绵防风罩;
  3. 模型校准:首次使用需在实际环境中校准(长按校准按键,采集背景噪声更新模型阈值);
  4. 唤醒词误触:通过增加 "双唤醒词"(如 "小 ESP + 确认")或设置灵敏度调节,减少误触发。

总结

本系统通过 ESP32-S3 的本地计算能力与机器学习结合,实现了脱离云端的智能语音控制,兼顾响应速度与隐私保护。核心难点在于语音预处理优化和轻量级模型设计,需通过大量实际数据训练和调试提升识别稳定性。扩展后可支持多设备联动,成为完整的智能家居控制中枢,适合个人 DIY 或小型场景应用。

相关推荐
koo3642 小时前
李宏毅机器学习笔记
人工智能·笔记·机器学习
nix.gnehc2 小时前
机器学习概念
人工智能·机器学习
长桥夜波2 小时前
机器学习日报17
人工智能·机器学习
Nina_7172 小时前
pytorch核心组件以及流程
人工智能·pytorch·python
Elastic 中国社区官方博客2 小时前
Elasticsearch 的结构化文档配置 - 递归分块实践
大数据·人工智能·elasticsearch·搜索引擎·ai·全文检索·jenkins
掘金一周2 小时前
重新思考 weapp-tailwindcss 的未来 | 掘金一周 11.13
前端·人工智能·后端
Clarence Liu2 小时前
机器学习(4) cost function(代价函数)
人工智能·机器学习
木头左2 小时前
技术指标时空编码构建LSTM兼容的量化交易特征工程体系
人工智能·rnn·lstm
得帆云2 小时前
低代码高频实践场景系列之一——EHS系统
大数据·人工智能·物联网