3. ESP32-CAM图像分类部署
任务目标
在ESP32-CAM开发板上部署MobileNetV1图像分类模型,实现至少10类物体的实时识别。ESP32-CAM以ESP32-S芯片为核心,配备OV2640摄像头和MicroSD卡槽,算力有限(240MHz单核、520KB SRAM),因此采用TFLite Micro推理引擎加载量化后的MobileNetV1模型。
环境与依赖
- 硬件:ESP32-CAM开发板(AI-Thinker)、FTDI串口模块(用于烧录)、5V电源
- 软件:Arduino IDE 2.x、ESP32开发板包(v2.0.14+)
- 库:
EloquentTinyML(v1.0.0+)、EloquentTinyML::Tflite(自动拉取TFLite Micro)、esp32-camera(Arduino ESP32包内置) - 模型:MobileNetV1量化模型(输入96×96×3,10类输出),文件
model.h(C数组格式)
模型准备
使用TensorFlow训练或转换一个10类分类模型(如CIFAR-10子集或自采集数据集),经量化感知训练后转换为TFLite格式,再通过xxd -i model.tflite > model.h生成C头文件。模型输入需为96×96 RGB图像(ESP32-CAM可承受的最大推理尺寸),输出为10个类别的置信度分数。本案例假设已生成model.h,其中包含unsigned char model_tflite[]数组和长度变量。
完整实现代码
#include <esp_camera.h>
#include <EloquentTinyML.h>
#include "model.h"
#define CAMERA_MODEL_AI_THINKER
#include "camera_pins.h"
// 输入/输出维度需与模型匹配
#define NUM_INPUTS 96 * 96 * 3
#define NUM_OUTPUTS 10
#define NUM_CLASSES 10
Eloquent::TinyML::Tflite<NUM_INPUTS, NUM_OUTPUTS> ml;
// 10个类别名称(按训练顺序)
const char* class_labels[NUM_CLASSES] = {
"airplane", "automobile", "bird", "cat", "deer",
"dog", "frog", "horse", "ship", "truck"
};
// 摄像头初始化(AI-Thinker引脚配置)
void setupCamera() {
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sccb_sda = SIOD_GPIO_NUM;
config.pin_sccb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_RGB888; // 必须RGB888
config.frame_size = FRAMESIZE_96X96; // 与模型输入一致
config.jpeg_quality = 12;
config.fb_count = 1;
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("摄像头初始化失败: 0x%x\n", err);
while (1) delay(1000);
}
}
void setup() {
Serial.begin(115200);
delay(1000);
setupCamera();
// 初始化TFLite模型
ml.begin(model_tflite);
Serial.println("模型加载完成");
}
void loop() {
camera_fb_t* fb = esp_camera_fb_get();
if (!fb) {
Serial.println("图像采集失败");
delay(1000);
return;
}
// 将RGB888像素转为float数组(0~1归一化)
float input[NUM_INPUTS];
for (int i = 0; i < NUM_INPUTS; i++) {
input[i] = fb->buf[i] / 255.0f;
}
// 推理
uint32_t start = millis();
float* output = ml.predict(input);
uint32_t elapsed = millis() - start;
// 找最高置信度类别
int max_idx = 0;
float max_score = output[0];
for (int i = 1; i < NUM_OUTPUTS; i++) {
if (output[i] > max_score) {
max_score = output[i];
max_idx = i;
}
}
// 串口输出结果
Serial.printf("类别: %s, 置信度: %.2f%%, 推理耗时: %d ms\n",
class_labels[max_idx], max_score * 100, elapsed);
esp_camera_fb_return(fb);
delay(2000); // 每2秒识别一次
}
运行与烧录
- 将
model.h放在与.ino文件相同目录下 - Arduino IDE中选择开发板:
AI Thinker ESP32-CAM - 烧录设置:Flash Mode
QIO,Flash Frequency80MHz,Partition SchemeHuge APP (3MB No OTA) - 连接FTDI模块(TX→U0R,RX→U0T,GND→GND,5V→5V),将GPIO0拉低进入下载模式
- 点击上传,烧录完成后断开GPIO0并复位
验证方法
打开串口监视器(115200波特率),将摄像头对准待识别物体。若模型加载成功且推理正常,应看到类似输出:
模型加载完成
类别: cat, 置信度: 87.50%, 推理耗时: 850 ms
类别: dog, 置信度: 92.10%, 推理耗时: 832 ms
通过条件:连续识别10次不同物体,至少8次分类正确;推理耗时在1秒以内(量化模型+96×96输入下典型值)。若输出NaN或置信度始终接近10%(随机猜测),说明输入预处理或模型张量维度不匹配,需检查NUM_INPUTS是否与模型实际输入一致。
常见问题
- 编译错误“模型太大”:确认使用
Huge APP分区,且模型文件小于3MB - 摄像头无法初始化:检查PSRAM是否启用(Tools→PSRAM→
Enabled),OV2640需要PSRAM存储帧缓冲 - 推理结果全为同一类:检查RGB通道顺序(ESP32-CAM输出为RGB888,若模型训练时使用BGR需交换通道)
- 帧率过低:将
FRAMESIZE_96X96改为FRAMESIZE_QVGA后在代码中裁剪中心96×96区域,可提升采集速度