ESP32-S3 边缘 AI 入门:TinyML 图像识别实战

ESP32-S3 边缘 AI 入门:TinyML 图像识别实战

前言

这两年「边缘 AI」这个词在硬件圈子里越来越火。很多人一听到 AI 就觉得需要 GPU、需要云端算力、需要一堆昂贵的服务器。但实际上,像 ESP32-S3 这种几美元的开发板,已经能跑简单的图像分类模型了。

今天这篇文章就来带大家从零开始,用 ESP32-S3 + OV2640 摄像头搭建一个离线图像识别系统。不用联网、不用云服务器,所有推理都在本地完成。做完这个项目,你就能理解 TinyML 的基本工作流程,也能把它扩展到自己的智能家居、工业检测或者安防监控项目里。

为什么选 ESP32-S3?

ESP32-S3 是乐鑫 2021 年推出的双核 Xtensa LX7 处理器芯片,主频 240MHz,内置 512KB SRAM 和可选的外部 PSRAM。相比老款 ESP32,S3 最大的升级是加入了向量指令加速(Vector Instructions),专门用来加速神经网络推理。

官方数据表明,ESP32-S3 运行 MobileNetV1 这样的轻量级 CNN 模型时,推理速度比 ESP32 快 2-3 倍。再加上它自带 WiFi 和蓝牙,非常适合做低功耗的边缘 AI 节点。

核心参数速览

参数规格
CPU双核 Xtensa LX7 @ 240MHz
SRAM512KB 内部 + 最大 8MB 外部 PSRAM
Flash最大 16MB
无线WiFi 802.11 b/g/n + Bluetooth 5 LE
加速指令AI Vector Instructions(矩阵乘法加速)
摄像头接口支持 DVP 并行接口(OV2640/OV5640)

硬件清单

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

  • ESP32-S3 开发板:推荐 Seeed Studio XIAO ESP32S3 Sense 或者 ESP32-S3-CAM 模组。XIAO 系列体积小、引脚少,适合原型验证;CAM 模组自带摄像头排线插座,接线更方便。

  • OV2640 摄像头模块:200 万像素,支持 JPEG 输出。如果买的是 XIAO ESP32S3 Sense 扩展板,摄像头已经集成好了。

  • MicroSD 卡(可选):用于存储采集的图像数据集,建议 4GB 以上 Class10 卡。

  • USB-C 数据线:用于烧录固件和串口调试。

  • 面包板和杜邦线:如果使用独立摄像头模块,需要手动接线。

接线示意(独立摄像头方案)

如果你用的是 ESP32-S3 DevKit + 独立 OV2640 模块,接线如下:

OV2640        ESP32-S3
--------      --------
VCC           3.3V
GND           GND
SIOC          GPIO 23
SIOD          GPIO 18
VSYNC         GPIO 38
HREF          GPIO 47
PCLK          GPIO 12
D0-D7         GPIO 11, 9, 8, 10, 7, 6, 5, 4
RESET         GPIO 15 (或接 3.3V)
PWDN          GPIO 48 (或接 GND)

**

注意**:不同开发板的摄像头引脚定义可能不同,务必查阅你手头开发板的原理图。XIAO ESP32S3 Sense 的摄像头引脚已经在板级配置文件中预设好,无需手动接线。

软件环境准备

1. 安装 Arduino IDE

arduino.cc 下载最新版 Arduino IDE(2.x 或 1.8.x 均可)。

2. 添加 ESP32 板级支持

打开 Arduino IDE,进入 文件 > 首选项,在「附加开发板管理器网址」中添加:

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

然后进入 工具 > 开发板 > 开发板管理器,搜索 esp32,安装 Espressif Systems 提供的 esp32 包(建议版本 2.0.8 或以上)。

3. 选择开发板和启用 PSRAM

  • 工具 > 开发板:选择你的 ESP32-S3 型号(如 XIAO_ESP32S3ESP32S3 Dev Module)。

  • 工具 > PSRAM:选择 OPI PSRAMQSPI PSRAM(根据你板子的实际配置)。这一步非常关键,摄像头帧缓冲需要大量内存,不开启 PSRAM 会导致编译通过但运行时崩溃。

4. 安装 Edge Impulse CLI(可选)

如果你想用 Edge Impulse 平台训练模型,需要安装 Node.js 和 Edge Impulse CLI:

npm install -g edge-impulse-cli

数据采集:收集猫狗图像

机器学习的第一步是收集数据集。我们这里做一个经典的「猫 vs 狗」二分类任务。

方法一:直接用 ESP32-S3 拍照采集

Seeed Studio 提供了一个现成的拍照例程,可以把摄像头画面保存到 SD 卡。步骤如下:

  1. $1

  2. $1

  3. $1

  4. $1

方法二:从公开数据集下载

如果懒得自己拍,可以直接用 Kaggle 上的 Cat and Dog 数据集,里面已经有上万张标注好的图片。下载后挑选其中一部分(每类 200-500 张足够)用于训练。

用 Edge Impulse 训练图像分类模型

Edge Impulse 是一个面向嵌入式设备的 ML 开发平台,提供从数据采集、模型训练到部署的一站式服务。注册免费账号后,按以下步骤操作:

1. 创建新项目

登录 edgeimpulse.com,点击 Create new project,命名为 cat-dog-classifier

2. 上传数据

进入 Data acquisition 页面,点击 Upload data,选择「Select a folder」,上传你收集的猫和狗的图片文件夹。Edge Impulse 会自动从文件名推断标签(比如 cat_001.jpg 的标签就是 cat)。

上传完成后,确保训练集和测试集的比例约为 80:20。

3. 设计 Impulse

进入 Impulse design 页面,点击 Create impulse。设置如下:

  • Input block:Image(96x96 像素,RGB)

  • Processing block:Image(preprocessing)

  • Learning block:Transfer Learning(Images)

点击 Save impulse

4. 生成特征

进入 Image 处理块,保持默认参数(Resize mode: Fit shortest axis),点击 Generate features。这一步会把原始图片转换成模型可以理解的数值特征。

5. 训练模型

进入 Transfer Learning 学习块,设置如下:

  • Architecture:MobileNetV2 0.1(最轻量,适合 ESP32-S3)

  • Training cycles:30

  • Learning rate:0.0005

点击 Start training。训练过程大约需要 5-10 分钟,取决于数据量。

6. 测试模型精度

训练完成后,进入 Model testing 页面,点击 Classify all。理想情况下,测试集准确率应该在 85%-95% 之间。如果太低,可能需要增加数据量或调整训练参数。

7. 部署为 Arduino 库

进入 Deployment 页面,选择 Arduino Library,点击 Build。下载生成的 .zip 文件。

在 ESP32-S3 上部署模型

1. 安装模型库

在 Arduino IDE 中,点击 项目 > 加载库 > 添加 .ZIP 库,选择刚才下载的 Edge Impulse 库文件。

2. 编写推理代码

下面是一个完整的推理示例,摄像头实时捕捉画面,每帧都经过模型分类,结果通过串口输出:

#include 
#include "edge-impulse-sdk/classifier/ei_run_classifier.h"
#include "model-parameters/model_metadata.h"

// 摄像头配置(根据实际硬件修改引脚)
#define PWDN_GPIO_NUM     48
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM     12
#define SIOD_GPIO_NUM     18
#define SIOC_GPIO_NUM     23
#define Y9_GPIO_NUM       11
#define Y8_GPIO_NUM       9
#define Y7_GPIO_NUM       8
#define Y6_GPIO_NUM       10
#define Y5_GPIO_NUM       7
#define Y4_GPIO_NUM       6
#define Y3_GPIO_NUM       5
#define Y2_GPIO_NUM       4
#define VSYNC_GPIO_NUM    38
#define HREF_GPIO_NUM     47
#define PCLK_GPIO_NUM     12

static camera_config_t camera_config = {
    .pin_pwdn  = PWDN_GPIO_NUM,
    .pin_reset = RESET_GPIO_NUM,
    .pin_xclk = XCLK_GPIO_NUM,
    .pin_sscb_sda = SIOD_GPIO_NUM,
    .pin_sscb_scl = SIOC_GPIO_NUM,
    .pin_d7 = Y9_GPIO_NUM,
    .pin_d6 = Y8_GPIO_NUM,
    .pin_d5 = Y7_GPIO_NUM,
    .pin_d4 = Y6_GPIO_NUM,
    .pin_d3 = Y5_GPIO_NUM,
    .pin_d2 = Y4_GPIO_NUM,
    .pin_d1 = Y3_GPIO_NUM,
    .pin_d0 = Y2_GPIO_NUM,
    .pin_vsync = VSYNC_GPIO_NUM,
    .pin_href = HREF_GPIO_NUM,
    .pin_pclk = PCLK_GPIO_NUM,
    .xclk_freq_hz = 20000000,
    .ledc_timer = LEDC_TIMER_0,
    .ledc_channel = LEDC_CHANNEL_0,
    .pixel_format = PIXFORMAT_JPEG,
    .frame_size = FRAMESIZE_QVGA,
    .jpeg_quality = 12,
    .fb_count = 1,
    .fb_location = CAMERA_FB_IN_PSRAM,
    .grab_mode = CAMERA_GRAB_WHEN_EMPTY,
};

void setup() {
    Serial.begin(115200);

    // 初始化摄像头
    esp_err_t err = esp_camera_init(&camera_config);
    if (err != ESP_OK) {
        Serial.printf("Camera init failed with error 0x%x\n", err);
        return;
    }

    Serial.println("Camera ready");
}

void loop() {
    // 捕获一帧
    camera_fb_t *fb = esp_camera_fb_get();
    if (!fb) {
        Serial.println("Camera capture failed");
        return;
    }

    // 将 JPEG 解码为 RGB,并缩放到 96x96
    signal_t signal;
    float features;

    // 这里简化处理:实际项目中需要用 libjpeg 解码
    // 并使用 bilinear interpolation 缩放到模型输入尺寸

    // 运行推理
    ei_impulse_result_t result = {0};
    EI_IMPULSE_ERROR res = run_classifier(&signal, &result, false);

    if (res == EI_IMPULSE_OK) {
        for (size_t ix = 0; ix 注意**:上面的代码是简化版,实际部署时 Edge Impulse 会生成完整的预处理代码(包括 JPEG 解码和图像缩放)。你只需要把生成的库导入 Arduino IDE,调用 `run_classifier()` 即可。

### 3. 查看推理结果

上传代码后,打开串口监视器,你会看到类似这样的输出:

cat: 0.9234 dog: 0.0766


这表示当前帧被分类为「猫」,置信度 92.34%。

## 性能优化技巧

ESP32-S3 的资源有限,要让模型跑得顺畅,需要注意以下几点:

### 1. 降低输入分辨率

MobileNetV2 默认输入是 96x96 或 160x160。分辨率越低,推理越快,但精度也会下降。对于简单分类任务,96x96 通常够用。

### 2. 使用量化模型

Edge Impulse 支持 INT8 量化,可以把模型体积缩小 4 倍,推理速度提升 2-3 倍。在 **Deployment** 页面选择 **Quantized (INT8)** 即可。

### 3. 减少推理频率

不需要每帧都推理。可以每隔 500ms-1s 推理一次,或者只在检测到运动时才触发推理(配合 PIR 传感器或帧差法)。

### 4. 启用 PSRAM

前面提到过,摄像头帧缓冲和模型权重都需要大量内存。务必在 Arduino IDE 中启用 PSRAM,否则会出现 `malloc failed` 错误。

## 常见问题排查

### Q1:上传代码后串口没有任何输出

- 检查 USB 驱动是否安装正确(Windows 需要安装 CP210x 或 CH340 驱动)。

- 确认波特率设置为 115200。

- 按住 BOOT 键的同时按一下 RESET 键,让开发板进入下载模式。

### Q2:摄像头初始化失败(error 0x105)

- 检查摄像头接线是否正确,特别是 I2C 引脚(SIOD/SIOC)。

- 确认 PSRAM 已启用。

- 尝试降低 `xclk_freq_hz` 到 10MHz 或更低。

### Q3:推理结果为 NaN 或全零

- 检查图像预处理是否正确(RGB 通道顺序、归一化范围)。

- 确认模型输入尺寸与实际传入的数据一致。

- 查看 Edge Impulse 生成的示例代码,对比预处理逻辑。

### Q4:内存不足导致重启

- 减少同时分配的帧缓冲数量(`fb_count` 设为 1)。

- 使用更小的模型(MobileNetV2 0.1 而非 0.35)。

- 开启 INT8 量化。

## 进阶方向

完成这个基础项目后,你可以继续探索:

- **多分类任务**:扩展到更多类别,比如识别水果、手势或工业零件。

- **目标检测**:使用 YOLOv5-Tiny 或 SSD-MobileNet 做物体定位(需要更强的硬件如 ESP32-S3 + 外部 NPU)。

- **人脸检测**:结合 FaceNet 或 MTCNN 做人脸识别门禁系统。

- **低功耗优化**:利用 ESP32-S3 的深度睡眠模式,只在检测到运动时唤醒摄像头和模型。

## 结语

ESP32-S3 虽然不能跑大模型,但对于简单的图像分类、关键词检测、异常振动识别等任务,已经完全够用。关键是理解 TinyML 的工作流程:**数据采集 → 模型训练 → 量化压缩 → 边缘部署**。掌握这套方法论后,你可以把它应用到任何资源受限的嵌入式场景中。

希望这篇文章能帮你迈出边缘 AI 的第一步。如果有问题,欢迎在评论区留言交流!