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

110 lines
7.3 KiB
Markdown
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.

# Phase 01 执行进度追踪
> 由于本仓库非 git 仓库,用本文件替代 commit 作为原子进度追踪。
> 每完成一个任务追加一行;遇到偏差记录 `[!]` 条目。
## 任务状态表
- [~] T01 摄像头硬件 V4L2 probe —— 代码完成,硬件验证待用户
- [x] T02 Kconfig 开关 + FPS choice
- [x] T03 esp-dl + human_face_detect 依赖
- [x] T04 Esp32Camera CaptureForDetection + 双超时 mutex
- [x] T05 face_tracker.{h,cc} 骨架 + CMake 条件编译
- [x] T06 集成 HumanFaceDetect 推理 + 坐标归一化(代码部分;实测待 T12
- [x] T07 uart_send_face + uart mutex
- [ ] T08 RP2040 parse_face + static 去重
- [ ] T09 RP2040 main.py incoming_commands 识别 face:
- [ ] T10 RP2040 facetrack() 改造D-07 idle return
- [ ] T11 application.cc 接入 face_tracker_start
- [ ] T12 端到端联调
- [ ] T13 性能调优
- [ ] T14 关开关回归测试
- [ ] T15 最终验收
## 执行日志
- [x] T01 代码部分完成2026-04-17
- 新增 `ProbeFrameCapture()``main/boards/common/esp32_camera.{h,cc}`
-`main/application.cc``Start()` 末尾插入 probe 调用(`#ifndef CONFIG_IDF_TARGET_ESP32` 守卫)
- 硬件验证部分待用户接 USB 后在 T02/T03 通过后烧录验证
- [x] T02 完成2026-04-17
-`main/Kconfig.projbuild` 的 Camera Configuration menu 末尾新增
`XIAOZHI_ENABLE_FACE_TRACKING` + FPS choice5/10/15
- 采用 PLAN_CHECK NOTE-1 方案 B`depends on IDF_TARGET_ESP32S3`
只支持 S3与 CMake 排除逻辑对齐
- [!] T03 偏差2026-04-17 — 依赖版本冲突 阻塞批次 1
- **第一轮偏差**PLAN 原定 `esp-dl==3.2.0` + `human_face_detect==0.4.1` 不兼容
registry 数据显示 human_face_detect 0.4.1 实际依赖 `esp-dl ~3.3.0`
- 自动修正为 `esp-dl ~3.3.0`
- **第二轮偏差blocking**`esp-dl 3.3.0` 要求 `esp-dsp ==1.7.0`
但项目已有 `esp-sr ~2.2.0` 要求 `esp-dsp ==1.6.0`,互斥
- 此为真正的版本冲突,已停下汇报 orchestrator
- [x] T03 偏差已解决2026-04-17 —— 用户决策方案 A升级 esp-sr
-`idf_component.yml``esp-sr``~2.2.0` 升级为 `~2.3.1`
- esp-sr 2.3.x 已切换到 esp-dsp 1.7.0,与 esp-dl 3.3.0 兼容
- `idf.py reconfigure` 通过esp-dl 3.3.x / esp-dsp 1.7.0 / esp-sr 2.3.1 / human_face_detect 0.4.1 全部就绪
- 编译遇到 bootloader CMake 缓存不匹配(与 IDF 路径历史变更有关),已清理 `build/bootloader*` 目录后重新编译
- [x] T04 完成2026-04-17 - 修改文件: main/boards/common/esp32_camera.{h,cc}
- `esp32_camera.h`: 新增公开结构体 `FrameRef`data/len/width/height/format/buf_index
+ `CaptureForDetection(FrameRef*)` / `ReleaseDetectionFrame(const FrameRef&)` 声明
+ 私有成员 `SemaphoreHandle_t capture_mutex_`
- `esp32_camera.cc`: 构造函数末尾 `xSemaphoreCreateMutex()`,析构函数 `vSemaphoreDelete`
实现 `CaptureForDetection`10ms timeout 拿不到锁即返回 false 跳帧,成功后不解锁)
实现 `ReleaseDetectionFrame`VIDIOC_QBUF 归还 + 释放 mutex
`Capture()` 头部用栈上 RAII `CaptureLockGuard` 以 portMAX_DELAY 加锁,确保任何 return 路径都解锁
- `idf.py build` 通过,固件 2.47MB / 剩余 1.47MB (37% free)
- [x] T05 完成2026-04-17 - 新增: main/face_tracker.{h,cc};修改: main/CMakeLists.txt
- `face_tracker.h`: `extern "C"` 导出 3 个接口:`face_tracker_start/stop/get_fps`
- `face_tracker.cc`: 三重保护
1) Kconfig 层面(批次 1 已加 depends on IDF_TARGET_ESP32S3
2) 代码层面 `#if defined(CONFIG_XIAOZHI_ENABLE_FACE_TRACKING) && defined(CONFIG_IDF_TARGET_ESP32S3)` 守卫
3) 构建层面CMakeLists.txt `if(NOT CONFIG_IDF_TARGET_ESP32S3) list(REMOVE_ITEM SOURCES "face_tracker.cc")`
骨架任务 pin Core 0 / 优先级 2 / 栈 8KB每秒打印 `hello from core 0`
- `idf.py build` 通过,固件 2.47MB / 剩余 1.47MB (face_tracker.cc.obj 已被编译链接)
- [!] T06 偏差2026-04-17 - PLAN 中 T06 依赖 T07 的 uart_send_face 符号,但批次 2 未做 T07
- 采取方案face_tracker.cc 中用 `__attribute__((weak))` 前向声明 `uart_send_face`
T07 完成后uart_component.cc 提供的 strong symbol 自动覆盖弱符号
调用处加 `if (uart_send_face != nullptr)` 判空(弱符号未定义时为 NULL
- 此偏差属于"修复 T06 的前置依赖缺失",无需架构层面变更,已内联解决
- [x] T06 完成代码部分2026-04-17 - 修改: main/face_tracker.cc
- 包含 `human_face_detect.hpp` / `dl_image_define.hpp` / `dl_detect_define.hpp`
- 构造 `HumanFaceDetect()`(默认 model_type 由 CONFIG_DEFAULT_HUMAN_FACE_DETECT_MODEL 决定)
- 任务主循环:`vTaskDelayUntil(period)` 按 Kconfig FPS → CaptureForDetection →
组装 img_t (YUYV) → detector->run(img) → ReleaseDetectionFrame → 坐标归一化
- 坐标公式严格遵守 RESEARCH Pitfall 7`cx * 224 / width - 112`(匹配 RP2040 deadzone=20
- PLAN 未定义多人脸排序,补充健壮性:遍历 list 挑 score 最高的 result避免多脸摇摆
- 启动时打印 `PSRAM after detector init` 供 R2 OOM 风险追踪
- 每 10 秒打印 `face stats: hit/miss/fps`
- `idf.py build` 通过,固件 2.50MB / 剩余 1.46MB (36% free) — 相比 T05 +30KB
(esp-dl 推理库 + human_face_detect 模型注册表代码被链接)
- **实测部分待 T12**:需烧录后将人脸对准摄像头验证 score / infer 时长 / FPS
若 score < 0.5 则进入决策点 D-B改为 DL_IMAGE_PIX_TYPE_RGB565LE
- [x] T07 完成2026-04-17 - 修改: main/uart_component.{h,cc}
- `uart_component.h`: 新增 `uart_send_face(int,int)` 声明 `extern "C"` 包裹
以保证 C 链接名匹配 face_tracker.cc `extern "C" __attribute__((weak))` 前置声明
其他函数保持原 C++ 修饰名不变不影响 main.cc/display.cc 现有调用
- `uart_component.cc`:
* 新增 `static SemaphoreHandle_t s_uart_tx_mutex` `uart_init_component()` 末尾创建
* `uart_send_string()` 整体加 mutex 保护防止与 uart_send_face 并发撕包
* `uart_signal_start/stop` 经由 uart_send_string 间接加锁无需重复保护
* 新增 `extern "C" void uart_send_face(int,int)`snprintf 24 字节栈缓冲
加锁后 `uart_write_bytes(buf,n)` + `uart_write_bytes("\r\n",2)`与现有格式一致
- [!] 小偏差Rule 2PLAN 示例中 header 未用 extern "C" face_tracker.cc 的弱符号
前置声明是 C 链接strong 实现必须也是 C 链接才能覆盖 weak extern "C" 包裹解决
- `idf.py build` 通过固件 0x280760 = 2.51MB / 剩余 36% (1.46MB)相比 T06 几乎持平
( +数百字节符合 PLAN T07 "< 1KB" 预期)
- **nm 验证**`libmain.a` `uart_send_face` Tstrong 定义`face_tracker.cc.obj`
中为 wweak 引用)。弱符号覆盖链生效最终 ELF 暂时没这些符号是因为 T11 未做
application 未调用 face_tracker_start触发链接器 DCE 把整个 face_tracker 子图剔除
T11 接入后会自动拉入 uart_send_face strong 实现
- 未添加 test hookPLAN DoD 中提到的 `uart_send_face(42,-30)` 临时调用
留给 T12 端到端联调时用真实 face_tracker 数据验证