Rdzleo e95d0c414e Phase 01 批次 1-3: 单摄像头人脸追踪基础设施
实现 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>
2026-04-17 18:24:27 +08:00

91 lines
3.1 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 成功返回 truestreaming 未启动或 ioctl 失败返回 false
bool ProbeFrameCapture(int64_t* elapsed_us);
// [T04] 人脸检测用帧采集:超短 timeout10ms拿不到锁则跳帧
// 语义:人脸检测允许丢帧,拍照不允许丢
// 成功返回 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