嵌入式开发 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 |
| SRAM | 512KB 内部 + 最大 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_ESP32S3或ESP32S3 Dev Module)。 -
工具 > PSRAM:选择
OPI PSRAM或QSPI 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
-
$1
-
$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 的第一步。如果有问题,欢迎在评论区留言交流!