实现 ESP32-S3 上单摄像头人脸追踪的核心代码骨架,替代 Grove Vision AI V2
模块,通过 UART 发送人脸坐标驱动 RP2040 控制的眼球/YAW 舵机。
## 规划文档(docs/phase-01-face-tracking/)
- GOAL.md Phase 目标与 5 大成功标准
- RESEARCH.md esp-dl v3.2/3.3 + human_face_detect 0.4.1 技术调研
- PLAN.md 15 个原子任务的执行计划(T01-T15)
- PLAN_CHECK.md 计划审查报告(PASS_WITH_NOTES)
- PROGRESS.md 执行进度追踪(批次 1-3 已完成)
## 批次 1:依赖与开关(T01-T03)
- main/idf_component.yml
新增 esp-dl ~3.3.0 + human_face_detect 0.4.1(仅 S3/P4)
esp-sr 从 ~2.2.0 升级到 ~2.3.1,解决 esp-dsp 1.6/1.7 版本冲突
- main/Kconfig.projbuild
新增 CONFIG_XIAOZHI_ENABLE_FACE_TRACKING 开关(默认 y,depends on S3)
新增 CONFIG_XIAOZHI_FACE_TRACKING_FPS_CHOICE(5/10/15)
- main/boards/common/esp32_camera.{h,cc}
新增 ProbeFrameCapture() 最小 V4L2 DQBUF/QBUF 探针(T01)
- main/application.cc
Start() 末尾调用 probe 验证摄像头硬件链路
## 批次 2:人脸检测核心(T04-T06)
- main/boards/common/esp32_camera.{h,cc}
新增 FrameRef 结构体 + CaptureForDetection/ReleaseDetectionFrame
双超时 mutex 策略:face_tracker 10ms timeout 跳帧,Capture() RAII guard
- main/face_tracker.{h,cc}(新建)
Core 0 / 优先级 2 / 栈 8KB 独立任务
集成 esp-dl HumanFaceDetect 推理
坐标归一化 cx*224/W-112,匹配 RP2040 pixel_centre=112
多人脸遍历挑 score 最高,避免多脸时眼球摇摆
三重保护:Kconfig depends on S3 + 源文件 #if 守卫 + CMake 条件排除
- main/CMakeLists.txt
非 S3 目标从 SOURCES 移除 face_tracker.cc
## 批次 3:UART 协议扩展(T07)
- main/uart_component.{h,cc}
新增 uart_send_face(x,y) 发送 face:x,y\r\n 协议
extern "C" 链接名配合 face_tracker 的弱符号声明
全局 TX mutex 保护所有 UART 写入,防并发帧交织
uart_send_string 同步加锁保持一致性
## 编译验证
idf.py build 通过,固件 2.51MB / 剩余 1.46MB (36% free)
当前 face_tracker 未被 application 激活(留到 T11),
UART/摄像头现有功能零影响。
## 未完成(下次继续)
- T01 硬件 probe 实机验证
- T08-T10 RP2040 端 parse_face + facetrack 双数据源改造
- T11-T15 application 接入 + 端到端联调 + 性能调优 + 最终验收
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
91 lines
3.1 KiB
C++
91 lines
3.1 KiB
C++
#pragma once
|
||
#include "sdkconfig.h"
|
||
|
||
#ifndef CONFIG_IDF_TARGET_ESP32
|
||
#include <lvgl.h>
|
||
#include <thread>
|
||
#include <memory>
|
||
#include <vector>
|
||
|
||
#include <freertos/FreeRTOS.h>
|
||
#include <freertos/queue.h>
|
||
#include <freertos/semphr.h>
|
||
|
||
#include "camera.h"
|
||
#include "jpg/image_to_jpeg.h"
|
||
#include "esp_video_init.h"
|
||
|
||
struct JpegChunk {
|
||
uint8_t* data;
|
||
size_t len;
|
||
};
|
||
|
||
class Esp32Camera : public Camera {
|
||
public:
|
||
// [T04] 人脸检测用帧引用:zero-copy 指向 mmap 缓冲区
|
||
// 使用者获得后必须在短时间内调用 ReleaseDetectionFrame 归还,否则 V4L2 流会卡死
|
||
struct FrameRef {
|
||
const uint8_t* data = nullptr;
|
||
size_t len = 0;
|
||
uint16_t width = 0;
|
||
uint16_t height = 0;
|
||
v4l2_pix_fmt_t format = 0;
|
||
uint32_t buf_index = 0; // 用于 VIDIOC_QBUF 归还
|
||
};
|
||
|
||
private:
|
||
struct FrameBuffer {
|
||
uint8_t *data = nullptr;
|
||
size_t len = 0;
|
||
uint16_t width = 0;
|
||
uint16_t height = 0;
|
||
v4l2_pix_fmt_t format = 0;
|
||
} frame_;
|
||
v4l2_pix_fmt_t sensor_format_ = 0;
|
||
#ifdef CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
|
||
uint16_t sensor_width_ = 0;
|
||
uint16_t sensor_height_ = 0;
|
||
#endif // CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
|
||
int video_fd_ = -1;
|
||
bool streaming_on_ = false;
|
||
struct MmapBuffer { void *start = nullptr; size_t length = 0; };
|
||
std::vector<MmapBuffer> mmap_buffers_;
|
||
std::string explain_url_;
|
||
std::string explain_token_;
|
||
std::thread encoder_thread_;
|
||
|
||
// [T04] 采集互斥锁:face_track 和 MCP 拍照共享 V4L2 DQBUF 单槽
|
||
// 使用 FreeRTOS 信号量(非 std::mutex)以获得 timeout 语义
|
||
SemaphoreHandle_t capture_mutex_ = nullptr;
|
||
|
||
public:
|
||
Esp32Camera(const esp_video_init_config_t& config);
|
||
~Esp32Camera();
|
||
|
||
virtual void SetExplainUrl(const std::string& url, const std::string& token);
|
||
virtual bool Capture();
|
||
// 翻转控制函数
|
||
virtual bool SetHMirror(bool enabled) override;
|
||
virtual bool SetVFlip(bool enabled) override;
|
||
virtual std::string Explain(const std::string& question);
|
||
|
||
// [T01] 最小化 V4L2 DQBUF/QBUF 探测方法
|
||
// 用途:验证 OV3660 + esp_video 底层采集链路是否正常工作
|
||
// 不做 JPEG 编码、不做 PSRAM 大分配、不触发 encoder_thread
|
||
// 调用链路:VIDIOC_DQBUF → 立即 VIDIOC_QBUF 归还
|
||
// @param elapsed_us 输出参数,返回两次 ioctl 间的耗时(微秒)
|
||
// @return 成功返回 true;streaming 未启动或 ioctl 失败返回 false
|
||
bool ProbeFrameCapture(int64_t* elapsed_us);
|
||
|
||
// [T04] 人脸检测用帧采集:超短 timeout(10ms)拿不到锁则跳帧
|
||
// 语义:人脸检测允许丢帧,拍照不允许丢
|
||
// 成功返回 true 后,out 指向的缓冲有效期到 ReleaseDetectionFrame 为止
|
||
// 必须配对调用:Capture 成功 → Release 归还(否则 V4L2 队列耗尽)
|
||
bool CaptureForDetection(FrameRef* out);
|
||
|
||
// [T04] 归还人脸检测帧:配对 CaptureForDetection
|
||
// 内部执行 VIDIOC_QBUF 将缓冲归还给 V4L2 驱动,并释放 capture_mutex_
|
||
bool ReleaseDetectionFrame(const FrameRef& ref);
|
||
};
|
||
|
||
#endif // ndef CONFIG_IDF_TARGET_ESP32
|