用 ESP32-CAM 做本地人形识别?不联网也能玩转边缘 AI
你有没有遇到过这样的场景:家里装了摄像头,想看看门口有没有人,结果点开App要等十几秒加载画面——延迟高不说,还总担心视频被上传到云端,隐私“裸奔”。
其实,这些问题的根源在于把所有数据都往云端送。但如果我们换个思路:让设备自己“看懂”画面,只在发现异常时告诉你一声——是不是既省带宽、又低延迟、还更安全?
今天我们就来干一件“硬核小事”:用一块不到60块钱的 ESP32-CAM 模块,跑一个轻量级AI模型,实现本地人形识别。整个过程不需要联网推理,不依赖服务器,真正把智能做到“端侧”。
为什么选 ESP32-CAM?
先别急着写代码,咱们得搞清楚——这块小板子凭什么能跑AI?
ESP32-CAM 是乐鑫推出的一款“麻雀虽小五脏俱全”的嵌入式视觉模组。它集成了:
- 双核处理器(LX6),主频最高240MHz
- Wi-Fi + 蓝牙双模通信
- OV2640 摄像头传感器,支持最大1600×1200分辨率
- DVP 接口和外挂 PSRAM(通常8MB),专门用来扛图像缓存
最关键的是:它的价格便宜到离谱——批量采购只要六七美元。
当然,它也不是万能的。比如没有浮点运算加速单元,内存也有限。但正因如此,它逼着我们去思考:如何在资源极度受限的情况下,让AI真正落地?
这就引出了我们的核心技术路径:
👉摄像头采集 → 图像预处理 → 本地AI推理 → 事件触发上报
全程在模块上完成,连原始图像都不出设备。
硬件准备与相机初始化
如果你手上有 AI-Thinker 出品的 ESP32-CAM 模块,那可以直接开干。接线很简单,重点是记住几个关键引脚:
#define PWDN_GPIO_NUM 32 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 0 #define SIOD_GPIO_NUM 26 #define SIOC_GPIO_NUM 27 // ... Y0~Y7, PCLK, VSYNC, HREF 等 DVP 数据和同步信号这些引脚对应 OV2640 的控制和数据通道。其中XCLK提供系统时钟(一般设为20MHz),而PCLK是像素时钟,每一拍传输一个像素数据。
初始化的时候最怕什么?内存溢出。
因为一张 QVGA(320x240)JPEG 图像大概有20~40KB,连续缓存几帧就得靠 PSRAM。所以一定要先检测是否存在外部 RAM:
if (psramFound()) { config.frame_size = FRAMESIZE_QVGA; config.fb_count = 2; // 可以双缓冲 } else { config.frame_size = FRAMESIZE_LOW; config.fb_count = 1; }否则很容易出现Camera init failed with error 0x20004这类错误——说白了就是“内存不够,别勉强”。
✅经验之谈:开发阶段建议始终启用 PSRAM,并在 Arduino IDE 或 ESP-IDF 中开启
CONFIG_ESP32_SPIRAM_SUPPORT编译选项。
让 AI 在指尖跳舞:TensorFlow Lite Micro 上场
现在轮到主角登场了:怎么让这个只有几百KB RAM 的芯片跑神经网络?
答案是——TensorFlow Lite Micro(TFLite Micro)。
它是 Google 专为微控制器设计的极简推理引擎,整个核心库编译后不到 20KB,完全用 C++ 实现,能在 FreeRTOS 上稳定运行。
我们选用的是 Google 官方提供的 person_detection_int8.tflite 模型。特点很鲜明:
| 特性 | 参数 |
|---|---|
| 输入尺寸 | 96×96 灰度图 |
| 输出 | 单一概率值(是否含有人) |
| 模型大小 | ~18KB(INT8量化后) |
| 推理时间 | ESP32 上约 400ms/帧 |
别小看这18KB的模型,它是在 COCO 数据集上训练过的 MobileNetV1 小变体,经过剪枝+量化压缩而来,准确率对日常使用足够了。
如何把模型塞进固件?
我们需要将.tflite文件转换成 C 数组:
xxd -i person_detection_int8.tflite > model_person_detect_quantized.h生成的结果类似这样:
const unsigned char g_person_detect_model_data[] = { 0x1c, 0x00, 0x00, 0x00, 'T', 'f', 'l', '3', ... };然后在代码中加载模型:
#include "tensorflow/lite/micro/micro_interpreter.h" #include "model_person_detect_quantized.h" // 分配张量内存区域(至少10KB) uint8_t tensor_arena[10 * 1024] __attribute__((aligned(16))); const tflite::Model* model = tflite::GetModel(g_person_detect_model_data); tflite::MicroInterpreter interpreter(model, tensor_arena, kTensorArenaSize, &error_reporter); // 获取输入输出张量 TfLiteTensor* input = interpreter.input(0); TfLiteTensor* output = interpreter.output(0);⚠️ 注意:
tensor_arena必须对齐16字节,否则可能崩溃。
从图像到推理:全流程打通
接下来是最关键的一环:怎么把摄像头拍的图喂给AI?
流程如下:
JPEG帧 → 解码为RGB/灰度 → 缩放至96x96 → 归一化 → 填入输入张量 → 推理由于 ESP32-CAM 默认输出 JPEG 格式(节省带宽),我们需要先解码。可以用开源库如JPEGDecoder或 ESP-IDF 内建函数。
简化版处理逻辑如下:
void detect_person(camera_fb_t * fb) { // 步骤1:JPEG → 灰度图 uint8_t *raw_gray = (uint8_t*)malloc(320 * 240); int ret = fmt2gray(fb->buf, fb->len, raw_gray, PIXFORMAT_JPEG); if (ret != 0) return; // 步骤2:缩放至96x96(最近邻插值即可) for (int y = 0; y < 96; y++) { for (int x = 0; x < 96; x++) { int src_x = (x * 320) / 96; int src_y = (y * 240) / 96; input->data.uint8[y * 96 + x] = raw_gray[src_y * 320 + src_x]; } } // 步骤3:执行推理 TfLiteStatus invoke_status = interpreter.Invoke(); if (invoke_status != kTfLiteOk) { Serial.println("推理失败"); free(raw_gray); return; } // 步骤4:读取结果 float confidence = output->data.f[0]; // 注意:虽然是INT8模型,输出可能是float if (confidence > 0.7) { Serial.println("✅ 检测到人!"); digitalWrite(LED_PIN, HIGH); // 触发报警灯 } else { Serial.print("❌ 无人 "); Serial.println(confidence, 3); } free(raw_gray); }🔍调试提示:初期可以串口打印
confidence值,观察模型对不同场景的响应(比如背影、侧身、远距离)。
你会发现,哪怕只是一个人影出现在角落,模型也能捕捉到特征。虽然不能定位具体位置(无边界框输出),但对于“有没有人”这种二分类任务来说,已经绰绰有余。
实际部署中的坑点与秘籍
理论讲完,实战才是检验真理的标准。我在实际项目中踩过不少坑,总结出以下几点“血泪经验”:
❌ 坑1:频繁重启?电源没跟上!
ESP32-CAM 拍照瞬间电流可达 200mA 以上,USB 供电或劣质电源容易导致电压跌落复位。
✅解决方案:
- 使用 5V/2A 开关电源
- 并联一个 100μF 电解电容在 VCC-GND 之间做储能
❌ 坑2:模型跑不动?Flash 速度太慢!
默认 SPI Flash 频率是 40MHz,如果模型放在 Flash 里直接运行,每次读权重都会卡顿。
✅解决方案:
- 启用 Flash Cache,在 menuconfig 中设置SPI_FLASH_FREQ_80M
- 或者把模型复制到 RAM 中再加载(牺牲一点内存换速度)
❌ 坑3:误报太多?环境干扰大!
光线变化、窗帘飘动、宠物走动都可能触发误检。
✅解决方案:
- 加入“连续多帧确认”机制:连续2~3帧 >0.7才判定为人
- 结合 PIR 人体红外传感器做双重验证(软硬结合更稳)
✅ 秘籍:降低功耗的终极姿势 —— 动态唤醒
如果你要做电池供电的野外监控设备,不可能让它一直拍照跑AI。
聪明的做法是:
1. 平时进入深度睡眠(<10μA)
2. 外部中断(如 PIR 检测到运动)唤醒
3. 唤醒后拍几张照片做AI判断
4. 判断为人则发送警报,否则继续睡
这样续航可以从几天拉长到几个月。
能做什么?不止是“有人没人”
你以为这只是个玩具级别的“滴滴响”装置?错。它可以衍生出很多实用功能:
🛠 应用场景1:家庭安防报警器
- 检测到人自动点亮LED灯
- 通过 MQTT 发送到 Home Assistant
- 手机弹窗提醒:“阳台检测到移动目标”
🧓 应用场景2:独居老人跌倒辅助监测
- 安装在客厅角落,非侵入式
- 发现长时间静止+突然倒地动作 → 触发紧急通知子女
🏭 应用场景3:无人仓库巡检节点
- 多个 ESP32-CAM 分布式部署
- 检测到人员进入禁区 → 自动记录时间并拍照上传
所有这些,都不需要持续录像上传,极大降低存储和隐私风险。
写在最后:边缘AI的起点,不是终点
当你第一次看到串口输出 “✅ Human detected!” 的那一刻,你会意识到:原来AI并不遥远,也不一定需要GPU集群。
ESP32-CAM + TFLite Micro 的组合,就像当年的Arduino一样,正在成为嵌入式AI的入门标准平台。
它或许算力孱弱,推理慢半拍,识别不够精细。但它证明了一件事:智能感知可以廉价、自主、安全地发生在每一个终端节点上。
未来我们可以做得更多:
- 用 ESP32-S3 替代(支持向量指令,提速3倍)
- 引入 TinyML 技术做关键词+视觉融合
- 构建自组织的无线传感网,实现协同感知
但一切的起点,往往就是这么一块小小的摄像头模块。
如果你也在尝试类似的边缘AI项目,欢迎留言交流。尤其是你遇到了哪些奇葩bug,又是怎么解决的?我们一起把这条路走得更宽一点。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考