实现 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>
7.3 KiB
7.3 KiB
Phase 01 执行进度追踪
由于本仓库非 git 仓库,用本文件替代 commit 作为原子进度追踪。 每完成一个任务追加一行;遇到偏差记录
[!]条目。
任务状态表
- [~] T01 摄像头硬件 V4L2 probe —— 代码完成,硬件验证待用户
- T02 Kconfig 开关 + FPS choice
- T03 esp-dl + human_face_detect 依赖
- T04 Esp32Camera CaptureForDetection + 双超时 mutex
- T05 face_tracker.{h,cc} 骨架 + CMake 条件编译
- T06 集成 HumanFaceDetect 推理 + 坐标归一化(代码部分;实测待 T12)
- 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 最终验收
执行日志
-
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 通过后烧录验证
- 新增
-
T02 完成:2026-04-17
- 在
main/Kconfig.projbuild的 Camera Configuration menu 末尾新增XIAOZHI_ENABLE_FACE_TRACKING+ FPS choice(5/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
- 第一轮偏差:PLAN 原定
-
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*目录后重新编译
- 将
-
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()头部用栈上 RAIICaptureLockGuard以 portMAX_DELAY 加锁,确保任何 return 路径都解锁idf.py build通过,固件 2.47MB / 剩余 1.47MB (37% free)
-
T05 完成:2026-04-17 - 新增: main/face_tracker.{h,cc};修改: main/CMakeLists.txt
face_tracker.h:extern "C"导出 3 个接口:face_tracker_start/stop/get_fpsface_tracker.cc: 三重保护- Kconfig 层面(批次 1 已加 depends on IDF_TARGET_ESP32S3)
- 代码层面
#if defined(CONFIG_XIAOZHI_ENABLE_FACE_TRACKING) && defined(CONFIG_IDF_TARGET_ESP32S3)守卫 - 构建层面: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_faceT07 完成后,uart_component.cc 提供的 strong symbol 自动覆盖弱符号 调用处加if (uart_send_face != nullptr)判空(弱符号未定义时为 NULL) - 此偏差属于"修复 T06 的前置依赖缺失",无需架构层面变更,已内联解决
- 采取方案:face_tracker.cc 中用
-
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)
- 包含
-
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 2):PLAN 示例中 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为 T(strong 定义),face_tracker.cc.obj中为 w(weak 引用)。弱符号覆盖链生效。最终 ELF 暂时没这些符号是因为 T11 未做, application 未调用 face_tracker_start,触发链接器 DCE 把整个 face_tracker 子图剔除。 T11 接入后会自动拉入 uart_send_face 的 strong 实现。 - 未添加 test hook(PLAN DoD 中提到的
uart_send_face(42,-30)临时调用), 留给 T12 端到端联调时用真实 face_tracker 数据验证