核心变更: - face_tracker.cc: YUYV→YVYU 序列修正(byte[1]=V, byte[3]=U), 基于 JPEG Dump 诊断工具验证 OV3660 FORMAT_CTRL00=0x61 实际是 YVYU - face_tracker.cc: 启动时 base64 打印一帧 JPEG 到串口,用于肉眼验证 - config.h: XCLK 20MHz→10MHz,给飞线信号完整性 2x 裕度 - scripts/auto_capture_jpeg.py: 自动串口抓帧工具(DTR/RTS 复位 + base64 解码) - scripts/extract_jpeg_from_log.py: 从日志离线提取 JPEG - Coglet项目分析与开发指南.md: 新增"六点六"章节,汇总 Phase 01 主要矛盾(画面可辨识≠模型可识别)、YUV→RGB 色偏三层原因、 esp-dl 模型输入分布敏感性、延迟分析、三方案对比、方案 B 突破口 - docs/: 新增 2 篇 OV3660 相关 CSDN 参考资料 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
827 lines
42 KiB
Markdown
827 lines
42 KiB
Markdown
# Coglet 项目分析与开发指南
|
||
|
||
## 一、项目简介
|
||
|
||
**Coglet** 是 YouTube 创作者 Will Cogley(原 Nilheim Mechatronics)开发的 **DIY 桌面 AI 伴侣机器人**,目前在 Kickstarter 众筹中。项目包含 3D 打印外壳、PCB 电路板和双 MCU 软件系统。
|
||
|
||
### 系统架构
|
||
|
||
采用**双 MCU 架构**,三个核心模块通过 UART 串联:
|
||
|
||
```
|
||
ESP32-S3 ←── UART 115200 ──→ RP2040 (Pico) ←── UART 921600 ──→ Grove Vision AI V2
|
||
(AI/语音/网络) (舵机/动画) (摄像头/人脸检测)
|
||
```
|
||
|
||
| 芯片 | 职责 |
|
||
|------|------|
|
||
| **ESP32-S3** | AI 对话、语音识别/合成、WiFi 网络、MCP 协议(基于小智 XiaoZhi 固件) |
|
||
| **RP2040 (Raspberry Pi Pico)** | 9 个舵机控制、动画表情、人脸追踪(MicroPython) |
|
||
| **Grove Vision AI V2** | 摄像头人脸检测 |
|
||
|
||
### ESP32 固件核心功能
|
||
|
||
- 连接方式:WiFi / ML307 Cat.1 4G
|
||
- 语音交互:流式 ASR + LLM(Qwen/DeepSeek)+ TTS
|
||
- 离线唤醒:ESP-SR 本地语音唤醒
|
||
- 通信协议:WebSocket 或 MQTT+UDP
|
||
- 音频编码:OPUS
|
||
- 显示:OLED/LCD + 表情动画
|
||
- 多语言:中文/英文/日文
|
||
- MCP 协议:设备端 MCP(控制扬声器、LED、舵机、GPIO)+ 云端 MCP(智能家居)
|
||
|
||
### RP2040 控制的舵机(共 9 个)
|
||
|
||
| 舵机代号 | 功能 |
|
||
|---------|------|
|
||
| EYL / EYR | 左右眼水平转动 |
|
||
| PIT | 头部俯仰 |
|
||
| YAW | 头部偏航 |
|
||
| MOU | 嘴巴开合 |
|
||
| LID | 眼睑眨眼 |
|
||
| 其他 | 耳朵舵机等 |
|
||
|
||
### RP2040 状态机
|
||
|
||
`idle` → `speaking` → `listening` → `thinking` → `happy` → `neutral` → `calibration`
|
||
|
||
---
|
||
|
||
## 二、GitHub 仓库总览
|
||
|
||
### 核心仓库
|
||
|
||
| 仓库 | 地址 | Stars | 说明 |
|
||
|------|------|-------|------|
|
||
| **Coglet 主仓库** | https://github.com/will-cogley/Coglet | 135 | 3D 打印件、PCB 原理图、文档 |
|
||
| **CogletESP 固件** | https://github.com/will-cogley/CogletESP | 5 | ESP32 固件 + RP2040 代码(fork 自小智) |
|
||
|
||
### 相关参考仓库
|
||
|
||
| 仓库 | 地址 | 说明 |
|
||
|------|------|------|
|
||
| 小智上游原版 | https://github.com/78/xiaozhi-esp32 | CogletESP 的上游项目 |
|
||
| EyeMech_Epsilon | https://github.com/will-cogley/EyeMech_Epsilon | Coglet 眼球机构的技术前身,RP2040 + MicroPython |
|
||
|
||
### CogletESP 分支列表
|
||
|
||
| 分支名 | Commits | 说明 |
|
||
|--------|---------|------|
|
||
| `main` | 789 | 上游同步的主分支 |
|
||
| **`CogletESP`** | 758 | **Coglet 专用分支(无 ESP32 摄像头版本,保留 LCD 显示)** |
|
||
| **`camera-version`** | 755 | **ESP32 OV3660 摄像头版本(需 PCB 飞线,禁用 LCD)** |
|
||
| `esp32_camera` | - | ESP32 摄像头分支 |
|
||
| `v1` | 669 | v1 稳定版 |
|
||
| `bed-operator` | - | 功能分支 |
|
||
| `fix_resampler` | - | 重采样器修复 |
|
||
| `fix_rndis` | - | RNDIS 修复 |
|
||
| `listening_wakeword` | - | 唤醒词监听 |
|
||
| `nt26_board` | - | NT26 开发板适配 |
|
||
|
||
### CogletESP 分支 vs camera-version 分支(关键区别)
|
||
|
||
> **重要区分**:这里的"摄像头"指 **ESP32-S3 上直连的 OV3660 DVP 摄像头**(小智 AI 视觉功能),**不是** Grove Vision AI V2。Grove Vision AI V2 是独立模块接在 RP2040 上,两个分支都支持它。
|
||
|
||
两个分支仅 **2 个文件**不同,都在 `main/boards/bread-compact-wifi-s3cam/` 目录下:
|
||
|
||
| 对比项 | `CogletESP` 分支 | `camera-version` 分支 |
|
||
|--------|------------------|----------------------|
|
||
| ESP32 摄像头(OV3660) | 默认引脚,与 PSRAM 冲突 | 飞线后的新引脚映射 |
|
||
| LCD 显示 | 正常支持 | **禁用**(引脚被摄像头占用,用 DummyDisplay 替代) |
|
||
| PCB 改动 | 无需飞线 | **需要 3 根飞线**(35→14, 36→41, 37→42) |
|
||
| 摄像头画面 | - | 设置 HMirror + VFlip 翻转 |
|
||
| JTAG 引脚(GPIO 39-42) | 默认用途 | 释放给摄像头使用 |
|
||
| Grove Vision AI V2 | 支持 | 支持 |
|
||
|
||
#### GPIO 引脚映射差异(部分关键引脚)
|
||
|
||
| 功能 | `CogletESP` 分支 | `camera-version` 分支 |
|
||
|------|------------------|----------------------|
|
||
| MIC_WS | GPIO 1 | GPIO 4 |
|
||
| MIC_SCK | GPIO 2 | GPIO 5 |
|
||
| MIC_DIN | GPIO 42 | GPIO 6 |
|
||
| SPK_DOUT | GPIO 39 | GPIO 7 |
|
||
| SPK_BCLK | GPIO 40 | GPIO 15 |
|
||
| SPK_LRCK | GPIO 41 | GPIO 16 |
|
||
| LED | GPIO 48 | GPIO 3 |
|
||
| CAMERA D7 | GPIO 16 | GPIO 14(飞线 35→14) |
|
||
| CAMERA VSYNC | GPIO 6 | GPIO 41(飞线 36→41) |
|
||
| CAMERA HREF | GPIO 7 | GPIO 42(飞线 37→42) |
|
||
|
||
#### 如何选择分支?
|
||
|
||
- **带 ESP32 OV3660 摄像头** → 用 `camera-version` 分支 + PCB 做 3 根飞线,但**没有 LCD 屏**
|
||
- **不带 ESP32 摄像头 / 想保留 LCD 屏** → 用 `CogletESP` 分支
|
||
- **仅使用 Grove Vision AI V2 人脸追踪** → 两个分支都可以,这是 RP2040 端的功能,与 ESP32 分支无关
|
||
|
||
### Coglet 主仓库目录结构
|
||
|
||
```
|
||
Coglet/
|
||
├── 3D Printing Files/
|
||
│ └── CogletB34Parts.3mf # 3D 打印零件
|
||
├── PCB/
|
||
│ ├── CogNogV1_0.pdf # 原理图 PDF
|
||
│ └── CogletESP_2026-02-24.epro # EasyEDA 工程文件
|
||
├── CogletESP/ # Git 子模块 → CogletESP 仓库
|
||
├── Translated Docs for XiaoZhi AI/ # 14 份小智文档英文翻译
|
||
│ ├── 固件烧录指南
|
||
│ ├── WiFi 配置
|
||
│ ├── 面包板接线
|
||
│ ├── ESP-IDF 开发环境搭建
|
||
│ └── 摄像头接线 ...
|
||
└── README.md
|
||
```
|
||
|
||
### 已知 PCB 问题(CogNog V1.0)
|
||
|
||
- USB-C 接口过紧
|
||
- **摄像头引脚与 PSRAM 冲突**(GPIO 35/36/37 被 PSRAM Octal SPI 占用,详见下方飞线说明)
|
||
- 体积电容不足
|
||
- 摄像头画面水平翻转(camera-version 分支已通过 `SetHMirror(true)` 修正)
|
||
|
||
### 翻译文档索引(Translated Docs for XiaoZhi AI)
|
||
|
||
主仓库中 `Translated Docs for XiaoZhi AI/` 包含 13 份小智文档英文翻译,其中与搭建相关的:
|
||
|
||
| 文件 | 内容 |
|
||
|------|------|
|
||
| **DIY Breadboard for Xiaozhi AI...** | 完整硬件清单和接线教程(最详细,18MB) |
|
||
| **[Latest] Wiring Tutorial...Camera** | **带摄像头的最新接线教程** |
|
||
| Setting up ESP IDF 5.5.2... | ESP-IDF 开发环境搭建(Windows) |
|
||
| Flash Tool_Web Terminal... | 网页烧录工具(无需 IDF 环境) |
|
||
| Configure Device Wi-Fi... | WiFi 配网和设备添加 |
|
||
| Steps for Wake Word Change... | 唤醒词修改 |
|
||
|
||
---
|
||
|
||
## 三、项目状态
|
||
|
||
- **活跃度**:积极开发中(最后更新 2026 年 3 月)
|
||
- **成熟度**:早期阶段 / 工作进行中
|
||
- Releases 页面为空,**无预编译固件**
|
||
- PCB 存在多个已知问题
|
||
- 没有专用的 Coglet 板级配置(使用通用 bread-compact 系列)
|
||
- **众筹**:Kickstarter 进行中
|
||
- **社区**:Discord + QQ 群
|
||
|
||
---
|
||
|
||
## 四、摄像头版本完整搭建流程(camera-version)
|
||
|
||
> 你使用的是**带摄像头**的版本,以下是从零到运行的完整操作流程。
|
||
|
||
### 4.1 为什么需要飞线?
|
||
|
||
**这是 CogNog V1.0 PCB 的设计缺陷,不是故意的设计。**
|
||
|
||
ESP32-S3 的 PSRAM 使用 Octal SPI 接口,占用了 GPIO 35/36/37。但 V1.0 PCB 把摄像头的三个信号线(D7、VSYNC、HREF)也接到了这三个引脚上。当摄像头和 PSRAM 同时工作时,引脚冲突导致摄像头无法使用。
|
||
|
||
**所有使用 CogNog V1.0 PCB + 摄像头的用户都必须飞线。** 目前只有 V1.0 版本的 PCB。
|
||
|
||
### 4.2 硬件准备清单
|
||
|
||
| 部件 | 说明 |
|
||
|------|------|
|
||
| CogNog V1.0 PCB | 需要飞线修改 |
|
||
| ESP32-S3 模组 | 带 PSRAM(如 N16R8) |
|
||
| OV3660 摄像头模组 | DVP 接口 + 排线 + 转接板 |
|
||
| Grove Vision AI V2 | 人脸追踪模块 + 15pin 排线适配器 |
|
||
| INMP441 麦克风 | I2S 数字麦克风 |
|
||
| 扬声器 | I2S 输出 |
|
||
| 9x 舵机 | **必须 180° 标准舵机**(如 KPower M0090 或 MG90S 180° 金属齿轮版),详见下方舵机选型说明 |
|
||
| Raspberry Pi Pico (RP2040) | 舵机控制 |
|
||
| 6 Pin PicoBlade 连接线 | RP2040 ↔ ESP32 UART |
|
||
|
||
### 4.3 PCB 飞线操作(3 根线)
|
||
|
||
在 CogNog V1.0 PCB 上,**切断原走线并飞线到新引脚**:
|
||
|
||
| 原始引脚 | 飞线到 | 摄像头信号 | 说明 |
|
||
|----------|--------|-----------|------|
|
||
| GPIO 35 | **GPIO 14** | CAMERA_PIN_D7 | 数据位 7 |
|
||
| GPIO 36 | **GPIO 41** | CAMERA_PIN_VSYNC | 垂直同步 |
|
||
| GPIO 37 | **GPIO 42** | CAMERA_PIN_HREF | 行参考信号 |
|
||
|
||
> **注意**:camera-version 分支还释放了 JTAG 引脚(GPIO 39-42)给摄像头使用,代码中通过 `gpio_reset_pin()` 实现。
|
||
|
||
### 4.4 代码下载
|
||
|
||
```bash
|
||
# 第 1 步:克隆 camera-version 分支(ESP32 固件 + RP2040 代码都在里面)
|
||
git clone -b camera-version https://github.com/will-cogley/CogletESP.git
|
||
|
||
# 第 2 步:克隆主仓库(文档、PCB 原理图、3D 打印件)
|
||
git clone https://github.com/will-cogley/Coglet.git
|
||
```
|
||
|
||
克隆后的目录结构:
|
||
|
||
```
|
||
CogletESP/ # camera-version 分支
|
||
├── main/
|
||
│ └── boards/
|
||
│ └── bread-compact-wifi-s3cam/
|
||
│ ├── config.h # GPIO 引脚映射(飞线后的版本)
|
||
│ └── compact_wifi_board_s3cam.cc # 板级初始化(禁用 LCD,启用摄像头)
|
||
├── components/
|
||
├── RP2040/ # RP2040 MicroPython 代码
|
||
│ ├── main.py # 主控程序、舵机状态机、UART 通信(ESP32 + Grove Vision AI V2)
|
||
│ ├── servoclass.py # 舵机驱动(PWM 50Hz,平滑运动)
|
||
│ └── animation.py # 动画定义(思考、开心、热身等)
|
||
├── CMakeLists.txt
|
||
└── sdkconfig
|
||
```
|
||
|
||
### 4.5 编译烧录 ESP32 固件(macOS)
|
||
|
||
```bash
|
||
# 1. 进入项目目录
|
||
cd CogletESP
|
||
|
||
# 2. 激活 IDF 环境
|
||
source ~/esp/esp-idf/export.sh
|
||
|
||
# 3. 设置目标芯片
|
||
idf.py set-target esp32s3
|
||
|
||
# 4. 配置板级选项
|
||
idf.py menuconfig
|
||
# → 找到板级配置,选择 bread-compact-wifi-s3cam
|
||
|
||
# 5. 编译
|
||
idf.py build
|
||
|
||
# 6. 查看可用串口
|
||
ls /dev/cu.usb*
|
||
|
||
# 7. 烧录并监控
|
||
idf.py -p /dev/cu.usbmodem14101 flash monitor
|
||
# 串口名根据实际替换(/dev/cu.usbmodem* 或 /dev/cu.usbserial*)
|
||
|
||
# 8. 退出监视器:Ctrl + ]
|
||
```
|
||
|
||
### 4.6 烧录 RP2040(MicroPython)
|
||
|
||
**第 1 步:刷入 MicroPython 固件**
|
||
|
||
下载地址:https://micropython.org/download/RPI_PICO/
|
||
|
||
**版本选择**:下载 **v1.24.x ~ v1.25.x 稳定版** `.uf2` 文件。代码只用基础 API(Pin、PWM、UART),v1.20+ 稳定版都兼容。**不要下载 preview/nightly 预览版。**
|
||
|
||
**硬件说明**:CogNog V1.0 PCB 上 RP2040 是直接焊接的(不是独立的 Raspberry Pi Pico 开发板),PCB 上有两个相关按键:
|
||
- **SW1(Boot Switch)**:位于 Flash Memory 旁,即 BOOTSEL 功能,用于进入 UF2 烧录模式
|
||
- **Run Switch**:位于 RP2040 旁,连接 RUN 引脚,即**复位按钮**。使用场景:
|
||
- 上传新的 .py 文件后,按一下重启 RP2040 使新代码生效(等同于 `mpremote reset`)
|
||
- 舵机行为异常时,按一下重启恢复到初始状态
|
||
- 配合 SW1 进入烧录模式(见下方方式 B)
|
||
|
||
操作(macOS / Windows 通用):
|
||
|
||
**方式 A — USB 未连接时:**
|
||
1. **按住 SW1(Boot Switch)** → 通过 RP2040 的 USB-C 口连接电脑 → 松开 SW1
|
||
2. 电脑上出现 `RPI-RP2` U 盘(macOS 在 Finder,Windows 在资源管理器)
|
||
3. 将 `.uf2` 文件拖入 U 盘
|
||
4. RP2040 自动重启
|
||
|
||
**方式 B — USB 已连接时(更常用):**
|
||
1. **按住 SW1(Boot Switch)不松手**
|
||
2. **按一下 Run Switch(复位)然后松开**(只按一下,不用保持)
|
||
3. **松开 SW1** — 整个过程约 1-2 秒,关键是按 Run Switch 时 SW1 必须处于按下状态
|
||
4. 电脑上出现 `RPI-RP2` U 盘
|
||
5. 将 `.uf2` 文件拖入 U 盘
|
||
6. RP2040 自动重启
|
||
|
||
> **原理**:Run Switch 让 RP2040 复位重启,重启瞬间 SW1 被按着 → RP2040 检测到 BOOTSEL 为低电平 → 进入 UF2 bootloader 模式。
|
||
>
|
||
> **提示**:如果 RP2040 已经刷过 MicroPython 且只需要更新 .py 文件,不需要重新进入 bootloader 模式,直接用 `mpremote` 上传即可(见第 2 步)。
|
||
|
||
**第 2 步:上传 .py 文件**
|
||
|
||
> **注意**:`CogletESP` 分支有 4 个文件(含 `coms.py`),`camera-version` 分支有 3 个文件(无 `coms.py`)。请使用对应分支的文件。
|
||
|
||
#### macOS
|
||
|
||
方式 A — mpremote(推荐):
|
||
|
||
```bash
|
||
pip install mpremote
|
||
cd CogletESP/RP2040
|
||
|
||
mpremote cp main.py :main.py
|
||
mpremote cp servoclass.py :servoclass.py
|
||
mpremote cp coms.py :coms.py # 仅 CogletESP 分支需要
|
||
mpremote cp animation.py :animation.py
|
||
|
||
mpremote reset # 重启运行
|
||
mpremote repl # 查看串口输出(调试用)
|
||
```
|
||
|
||
mpremote 本质就是一个串口文件传输工具,把 .py 文件拷贝到 Pico 的文件系统里。Pico 上电后自动运行 main.py,不需要搭建任何 MicroPython 编译环境。
|
||
|
||
方式 B — Thonny(图形界面):
|
||
|
||
```bash
|
||
brew install --cask thonny
|
||
```
|
||
|
||
1. 打开 Thonny → 右下角选择 `MicroPython (Raspberry Pi Pico)`
|
||
2. 逐个打开 .py 文件 → 另存为 → 选择 `Raspberry Pi Pico` → 保存同名
|
||
3. 保存完成后 Pico 重启自动运行 `main.py`
|
||
|
||
#### Windows
|
||
|
||
方式 A — mpremote(推荐):
|
||
|
||
```powershell
|
||
# 1. 安装 mpremote(需要 Python 3.x,从 python.org 下载安装,勾选 Add to PATH)
|
||
pip install mpremote
|
||
|
||
# 2. 进入 RP2040 目录
|
||
cd CogletESP\RP2040
|
||
|
||
# 3. 上传文件(Pico 连接 USB 后 Windows 会分配 COM 口,mpremote 自动识别)
|
||
mpremote cp main.py :main.py
|
||
mpremote cp servoclass.py :servoclass.py
|
||
mpremote cp coms.py :coms.py # 仅 CogletESP 分支需要
|
||
mpremote cp animation.py :animation.py
|
||
|
||
mpremote reset # 重启运行
|
||
mpremote repl # 查看串口输出(调试用,Ctrl+] 退出)
|
||
```
|
||
|
||
> **Windows 注意事项**:
|
||
> - 如果 `mpremote` 找不到设备,打开**设备管理器 → 端口(COM 和 LPT)**,确认 Pico 对应的 COM 口(如 COM3),然后用 `mpremote connect COM3 cp main.py :main.py` 指定端口
|
||
> - 如果设备管理器中看不到 COM 口,需要安装驱动:MicroPython 固件刷入后 Pico 通常免驱,若未识别可尝试拔插 USB 或换 USB 口
|
||
|
||
方式 B — Thonny(图形界面,适合新手):
|
||
|
||
1. 下载安装 Thonny:https://thonny.org/ (选择 Windows 版本)
|
||
2. 打开 Thonny → 右下角选择 `MicroPython (Raspberry Pi Pico)`
|
||
3. 逐个打开 .py 文件 → 文件 → 另存为 → 选择 `Raspberry Pi Pico` → 保存同名
|
||
4. 保存完成后 Pico 重启自动运行 `main.py`
|
||
|
||
### 4.7 配置 Grove Vision AI V2(人脸追踪)
|
||
|
||
**不需要手动刷底层固件**,但需要部署一个人脸检测模型。
|
||
|
||
**操作步骤:**
|
||
|
||
1. 用 USB-C 线将 Grove Vision AI V2 连接到电脑
|
||
2. 用 Chrome 浏览器访问 **SenseCraft AI 平台**:https://sensecraft.seeed.cc/
|
||
3. 模型下载地址:https://sensecraft.seeed.cc/ai/model/deploy?id=60094&uniform_type=36&name=%E4%BA%BA%E8%84%B8%E6%A3%80%E6%B5%8B&adapteds=11&adapteds=12&adapteds=14&task=1
|
||
4. 选择一个**人脸检测模型**(推荐 YOLO Face Detection,输入尺寸 **224x224**)
|
||
5. 点击部署,等待上传完成
|
||
6. 断开 USB,将模块接回 RP2040 的 UART
|
||
|
||
**为什么是 224x224?** 代码中 `pixel_centre = 112` 即 224/2,表明模型输入分辨率为 224x224。
|
||
|
||
**工作原理:**
|
||
|
||
```
|
||
RP2040 发送 AT+INVOKE=1,0,1 (SSCMA AT 协议)
|
||
↓
|
||
Grove Vision AI V2 运行人脸检测推理
|
||
↓
|
||
返回 JSON,包含 "boxes" 字段 → [x, y, w, h, ...]
|
||
↓
|
||
RP2040 计算人脸偏移量:x_offset = boxes[0] - 112, y_offset = boxes[1] - 112
|
||
↓
|
||
驱动舵机追踪:
|
||
- EYL/EYR(眼球左右) → 跟随 x_offset
|
||
- PIT(头部俯仰) → 跟随 y_offset
|
||
- YAW(底座旋转) → 眼球偏离中心 >20° 时延迟跟随
|
||
↓
|
||
deadzone = 20 像素(避免微小抖动触发舵机)
|
||
```
|
||
|
||
### 4.8 硬件连接
|
||
|
||
```
|
||
┌─────────────┐ UART 115200 ┌──────────────┐ UART 921600 ┌───────────────────┐
|
||
│ ESP32-S3 │◄──────────────────►│ RP2040 │◄──────────────────►│ Grove Vision AI V2│
|
||
│ │ TX→RX(GP5) │ (Pico) │ TX(GP0)→RX │ │
|
||
│ AI/语音 │ RX←TX(GP4) │ 舵机控制 │ RX(GP1)←TX │ 人脸检测 │
|
||
│ WiFi/网络 │ GND──GND │ 动画状态机 │ │ YOLO 模型 │
|
||
└─────────────┘ └──────────────┘ └───────────────────┘
|
||
│
|
||
PWM 引脚 x9
|
||
│
|
||
┌──────┴──────┐
|
||
│ 9 个舵机 │
|
||
│ EYL/EYR/PIT │
|
||
│ YAW/MOU/LID │
|
||
│ + 耳朵等 │
|
||
└─────────────┘
|
||
```
|
||
|
||
ESP32 通过 UART 发送状态字符串给 RP2040:
|
||
`neutral` / `idle` / `listening` / `speaking` / `thinking` / `happy`
|
||
→ RP2040 根据状态执行对应舵机动画
|
||
|
||
### 4.9 舵机选型说明(重要)
|
||
|
||
> **实测踩坑记录**:使用 MG90S 360° 连续旋
|
||
转版舵机后,耳朵舵机转到目标角度后无法停止,持续堵转导致齿轮发出刺耳声音、舵机严重发烫,有烧毁风险。更换为 180° 标准舵机后问题解决。
|
||
|
||
#### 必须使用 180° 标准舵机的原因
|
||
|
||
Coglet 的 9 个舵机全部采用**角度控制**模式(代码中 `set_target(角度)` 命令舵机转到指定角度并保持),这只有 180° 标准舵机才能实现。
|
||
|
||
**180° 标准舵机 vs 360° 连续旋转舵机的核心区别:**
|
||
|
||
| 对比项 | 180° 标准舵机(如 KPower M0090) | 360° 连续旋转舵机(如 MG90S 360° 改装版) |
|
||
|--------|-------------------------------|-------------------------------------|
|
||
| **内部结构** | 有电位器(可变电阻),实时反馈当前角度 | 拆除/固定了电位器,失去角度反馈能力 |
|
||
| **PWM 信号含义** | 对应**目标角度**(1ms=0°, 1.5ms=90°, 2ms=180°) | 对应**旋转速度和方向**(1.5ms=停止, <1.5ms=反转, >1.5ms=正转) |
|
||
| **控制逻辑** | 闭环控制:收到 PWM → 对比目标与当前角度 → 自动转到位停住 | 开环控制:收到 PWM → 按速度持续旋转,永不停止 |
|
||
| **能否锁定位置** | 能,到达角度后施力保持 | 不能,停转后无保持力,外力可推动 |
|
||
| **旋转范围** | 0°~180° 精确定位 | 360° 无限旋转,无法精确定位 |
|
||
|
||
#### 为什么 360° 舵机无法通过代码适配?
|
||
|
||
网上有"定时转动"方案(全速旋转 N 毫秒 → 发停止信号),原理是 `转动时间 = (500ms / 360°) × 目标角度`。但这对 Coglet **完全不可行**:
|
||
|
||
1. **误差累积无法修正**:没有位置反馈,每次转动误差 ±5°~10°。Coglet 的动画是高频反复运动(说话时嘴巴每 250ms 开合一次、耳朵每秒摆动),运行几分钟后累积误差可达几百度,位置完全跑飞
|
||
2. **个体差异大**:每台舵机电机特性、齿轮间隙不同,无法统一校准"时间-角度"关系。换一台舵机就要重新标定
|
||
3. **无法保持位置**:代码中头部俯仰、眼球等需要停在某个角度并持续施力保持。360° 舵机停转后没有保持力,手一碰就偏了
|
||
4. **实时性失效**:Coglet 需要实时响应 AI 状态变化动态调整角度,定时方案无法做到实时同步
|
||
5. **负载和温度影响转速**:电压波动、温度变化都会改变旋转速度,使时间估算更不准确
|
||
|
||
#### 推荐舵机型号
|
||
|
||
| 型号 | 说明 | 参考价格 |
|
||
|------|------|---------|
|
||
| **KPower M0090**(官方推荐) | 9g 模拟金属齿轮 180° 舵机 | ~¥8-12/个 |
|
||
| **MG90S 180° 金属齿轮版** | 注意必须是 **180° 版**,不带 "continuous/360" 字样 | ~¥5-8/个 |
|
||
|
||
> **购买注意**:MG90S 有 180° 和 360° 两个版本,外观完全一样。购买时务必确认商品标题或参数中标注 "180°",避免买到 360° 连续旋转版。
|
||
|
||
#### 9 个舵机角度范围详解
|
||
|
||
代码中所有舵机的 PWM 映射为 0°~180°(脉宽 500μs~2500μs),各舵机通过 `min_angle` / `max_angle` 限定实际运动范围(源码:`animation.py` 第 14-23 行):
|
||
|
||
| 舵机代号 | 功能 | GPIO 引脚 | 角度范围 | 最大跨度 | 运动说明 |
|
||
|---------|------|----------|---------|---------|---------|
|
||
| **YAW** | 底座旋转 | GP6 | 10°~170° | 160° | 头部左右转动,人脸追踪时由 Grove Vision AI V2 驱动 |
|
||
| **ROL** | 颈部侧倾 | GP7 | 30°~120° | 90° | 头部左右歪头,happy 状态下 70°↔110° 摆动 |
|
||
| **PIT** | 头部俯仰 | GP8 | 1°~80° | 79° | 头部上下点头,thinking 状态下 50°↔80° 摆动 |
|
||
| **MOU** | 嘴巴开合 | GP19 | 5°~150° | 145° | speaking 时 70°↔130° 每 250ms 切换(张嘴/闭嘴) |
|
||
| **EYL** | 左眼球 | GP12 | 30°~150° | 120° | 左右看,人脸追踪时跟随 x 偏移量 |
|
||
| **EYR** | 右眼球 | GP13 | 30°~150° | 120° | 与 EYL 同步运动 |
|
||
| **LID** | 眼睑 | GP14 | 30°~160° | 130° | 30°=闭眼,110°~160°=睁眼,随机眨眼动画 |
|
||
| **EAL** | 左耳 | GP15 | 60°~150° | 90° | happy 状态下 100°↔160° 摆动 |
|
||
| **EAR** | 右耳 | GP16 | 30°~120° | 90° | happy 状态下 60°↔120° 摆动 |
|
||
|
||
> **注意**:以上角度范围是代码中的软件限位,基于官方 3D 打印件和 KPower M0090 舵机校准。如果使用其他舵机或自制外壳,可能需要调整 `animation.py` 中的 `min_angle` / `max_angle` 避免堵转。
|
||
|
||
> **MOU 引脚差异**:`CogletESP` 分支 MOU 使用 **GP19**,`camera-version` 分支 MOU 使用 **GP9**。请根据实际 PCB 版本确认。
|
||
|
||
#### 校准模式使用方法
|
||
|
||
CogNog V1.0 PCB 上有一个 **SW11(Calibration Switch)** 拨动开关,连接 RP2040 的 GPIO20。代码中 GPIO20 配置了内部上拉电阻:
|
||
|
||
```python
|
||
mode = Pin(20, Pin.IN, Pin.PULL_UP)
|
||
is_calibrating = (mode.value() == 1) # 高电平 = 校准模式
|
||
```
|
||
|
||
**SW11 逻辑:**
|
||
|
||
| SW11 状态 | GPIO20 电平 | 模式 | 表现 |
|
||
|----------|-----------|------|------|
|
||
| 断开(GPIO20 悬空) | 高(内部上拉到 1) | **校准模式** | 所有舵机归 90° 中位,LED 闪烁 |
|
||
| 接通(GPIO20 接 GND) | 低 | **正常运行模式** | 响应 ESP32 状态指令,执行动画 |
|
||
|
||
> **提示**:如果不确定 SW11 哪个位置对应哪种模式,两个位置都试一下,**LED 闪烁的那个就是校准模式**。
|
||
|
||
#### 校准与组装流程
|
||
|
||
**前提**:RP2040 已刷入 MicroPython 固件并上传 .py 文件。
|
||
|
||
```
|
||
第 1 步:进入校准模式
|
||
└→ 将 SW11 拨到校准位置(LED 闪烁)
|
||
|
||
第 2 步:通电
|
||
└→ 给 RP2040 和舵机供电
|
||
└→ 所有 9 个舵机自动转到 90° 中位(staggered startup,逐个上电避免电流冲击)
|
||
|
||
第 3 步:安装舵机臂
|
||
└→ 在 90° 中位状态下,将舵机臂安装到需要的朝向
|
||
└→ 拧紧舵机臂固定螺丝
|
||
└→ ⚠️ 关键:必须在通电校准状态下安装,否则角度对不上
|
||
|
||
第 4 步:组装机械结构
|
||
└→ 将舵机装入 3D 打印外壳
|
||
└→ 连接所有机械连杆
|
||
|
||
第 5 步:验证
|
||
└→ 将 SW11 拨回正常运行模式
|
||
└→ 观察舵机是否能正常运动,无堵转
|
||
└→ 如有堵转,调整 animation.py 中对应舵机的 min_angle / max_angle
|
||
|
||
第 6 步:连接 ESP32
|
||
└→ 接上 ESP32 UART(TX→GP5, RX→GP4)
|
||
└→ ESP32 发送状态指令,舵机开始响应 AI 对话动画
|
||
```
|
||
|
||
> **校准模式不需要断开 ESP32**:校准模式在主循环中每帧都强制覆盖舵机状态(`main.py` 第 153-155 行),即使 ESP32 发送了指令也会被忽略。
|
||
|
||
### 4.10 camera-version 的代价与限制
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| **LCD 屏被禁用** | 摄像头占用了原 LCD 引脚,代码用 DummyDisplay 空实现替代 |
|
||
| **音频引脚全部重映射** | MIC 从 GPIO 1/2/42 移到 4/5/6,SPK 从 GPIO 39/40/41 移到 7/15/16 |
|
||
| **内存压力** | 摄像头 + PSRAM 同时运行可能偶发内存不足(作者提到 "appears to run out of memory sometimes") |
|
||
| **摄像头画面翻转** | 已通过代码设置 `SetHMirror(true)` + `SetVFlip(true)` 修正 |
|
||
|
||
---
|
||
|
||
## 五、无摄像头版本搭建流程(CogletESP 分支)
|
||
|
||
> 如果不使用 ESP32 OV3660 摄像头,想保留 LCD 显示,使用此方案。
|
||
|
||
### 5.1 代码下载
|
||
|
||
```bash
|
||
git clone -b CogletESP https://github.com/will-cogley/CogletESP.git
|
||
git clone https://github.com/will-cogley/Coglet.git
|
||
```
|
||
|
||
### 5.2 ESP32 编译烧录
|
||
|
||
与摄像头版本相同(参见 4.5),但 menuconfig 中的板级配置可能不同,PCB **无需飞线**。
|
||
|
||
### 5.3 RP2040 烧录
|
||
|
||
MicroPython 固件刷入方式与摄像头版本相同(参见 4.6),但 **RP2040 代码两个分支不同**:
|
||
- `CogletESP` 分支有 **4 个文件**:`main.py`、`servoclass.py`、`coms.py`、`animation.py`
|
||
- `camera-version` 分支只有 **3 个文件**(无 `coms.py`,UART 通信集成在 `main.py` 中)
|
||
|
||
请使用对应分支的 RP2040 目录文件上传。
|
||
|
||
### 5.4 Grove Vision AI V2
|
||
|
||
与摄像头版本完全相同(参见 4.7),Grove Vision AI V2 的人脸追踪功能由 RP2040 端控制,与 ESP32 分支选择无关。
|
||
|
||
---
|
||
|
||
## 六、常见问题
|
||
|
||
| 问题 | 解决方案 |
|
||
|------|---------|
|
||
| Python 环境冲突 | `idf.py fullclean` 后重新编译 |
|
||
| Bootloader CMake 缓存不匹配 | `rm -rf build/bootloader build/bootloader-prefix` |
|
||
| 找不到串口设备 | 安装 CP2102/CH340 驱动,检查 USB 线是否支持数据传输 |
|
||
| menuconfig 中找不到板级配置 | 确认在正确的分支(`camera-version` 或 `CogletESP`) |
|
||
| 摄像头不工作 | 确认已完成 3 根飞线(35→14, 36→41, 37→42)并使用 `camera-version` 分支 |
|
||
| 偶发内存不足 | camera-version 已知问题,摄像头 + PSRAM 同时运行时可能出现 |
|
||
| Grove Vision AI V2 无响应 | 确认已通过 SenseCraft 部署人脸检测模型,UART 波特率 921600 |
|
||
| 舵机不动 | 检查 RP2040 是否正确上传 .py 文件(CogletESP 分支 4 个 / camera-version 分支 3 个),UART 连接是否正常 |
|
||
| 舵机堵转、发烫、齿轮刺耳声 | **使用了 360° 连续旋转舵机**,必须更换为 180° 标准舵机(详见 4.9 舵机选型说明) |
|
||
| 舵机持续朝一个方向旋转不停 | 同上,360° 舵机收到非 90° 信号会持续旋转。180° 舵机会转到目标角度后自动停住 |
|
||
|
||
---
|
||
|
||
## 六点五、Phase 01 单摄像头人脸追踪 踩坑经验(2026-04-17 ~ 04-20)
|
||
|
||
> **背景**:尝试用 ESP32 上的 OV3660 摄像头做人脸检测,通过 UART 发送人脸坐标给 RP2040,驱动眼球/YAW 舵机追踪。详细规划/执行文档见 [docs/phase-01-face-tracking/](docs/phase-01-face-tracking/)。
|
||
>
|
||
> **现状**:代码骨架全部完成(15 个任务 T01-T15),ESP32 端 + RP2040 端端到端联通,但摄像头帧数据解析仍有问题(box 固定伪激活),未完全通过验收。
|
||
|
||
### 6.5.1 编译 / 配置类踩坑
|
||
|
||
| # | 坑 | 原因 | 解决方案 |
|
||
|---|---|-----|---------|
|
||
| 1 | 板级配置被重置为 `BREAD_COMPACT_WIFI`(无摄像头版) | `idf.py fullclean` / `set-target` 可能触发 sdkconfig 重置到 defaults | 显式编辑 `sdkconfig` 确认 `CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI_CAM=y` |
|
||
| 2 | `esp-dl 3.3.0` 与 `esp-sr 2.2.x` 版本冲突 | esp-dl 需要 esp-dsp 1.7.0,esp-sr 2.2 钉 esp-dsp 1.6.0 | 升级 `esp-sr` 到 `~2.3.1`(已切到 esp-dsp 1.7.0) |
|
||
| 3 | Bootloader CMake 缓存不匹配,编译报 `source does not match` | ESP-IDF 路径历史变更(如 `esp-idf/v5.4.2/esp-idf/` → `esp-idf/`) | `rm -rf build/bootloader build/bootloader-prefix` 重新编译 |
|
||
| 4 | `ESP_LOGI` 用 `%lld` 输出变成字符串 `"ldus"` | ESP-IDF nano newlib 不完全支持 `%lld` | 改用 `%lu` + `(unsigned long)` 强转(probe 用时 < 71 分钟,uint32 足够) |
|
||
| 5 | `esp_video 1.3.1/1.4.1` 的 DVP 启动报 `xclk_freq is not set` | esp_video 源码 `dvp_video_device.c` 构造 `esp_cam_ctlr_dvp_config_t` 时缺失 `xclk_freq` 字段(IDF 5.4.2 不支持 `external_xtal`) | 升 esp_video 到 1.4.1 后**手动 patch** managed_components 源码,在 `#else` 分支加 `.xclk_freq = 20000000,` |
|
||
|
||
### 6.5.2 摄像头数据链路踩坑
|
||
|
||
| # | 坑 | 原因 | 解决方案 |
|
||
|---|---|-----|---------|
|
||
| 6 | `/dev/video2` 打不开,errno=2 No such file | sdkconfig 中没有启用任何 sensor driver(`CONFIG_CAMERA_OV3660` 默认 `n`) | sdkconfig 启用对应摄像头:`CONFIG_CAMERA_OV3660=y` + 具体分辨率/格式 |
|
||
| 7 | V4L2 REQBUFS count 默认为 1(DVP 路径),DMA 饥饿陈旧数据 | `esp32_camera.cc` 中 `req.count = strcmp(...) == 0 ? 2 : 1;` 对 DVP 用单 buffer | 改为 `req.count = 2;` 让 DMA 有双缓冲持续更新 |
|
||
| 8 | 分区表 OTA 分区 3.94MB 容纳不下 4.23MB 固件 | 加了 esp-dl + human_face_detect 后固件膨胀 | 修改 `partitions/v2/16m.csv`:ota_0/ota_1 各 5MB,assets 缩到 5.875MB(16MB 模组够用) |
|
||
| 9 | 摄像头输出画面全黑,Y 值只有 0~8 | **镜头表面出厂保护膜未撕掉** | 用指甲抠边缘撕掉透明保护膜(极薄不易察觉) |
|
||
| 10 | 画面持续偏暗 | 室内光照不足 / 镜头指纹灰尘 | 白天窗边或明亮台灯下测试 |
|
||
|
||
### 6.5.3 esp-dl 人脸检测踩坑
|
||
|
||
| # | 坑 | 原因 | 解决方案 |
|
||
|---|---|-----|---------|
|
||
| 11 | MSR_S8_V1 模型固定在 `box=[0, y, 40, y+40]` 误识别 | 模型输入 160×120(4:3),摄像头输出 240×240(1:1),ImagePreprocessor letterbox 填充左右各 20 列灰条,模型在灰条上产生假激活 | 改用输入正方形的 `ESPDET_PICO_224_224_FACE`(224×224) |
|
||
| 12 | ESPDET 模型仍然输出固定 box `[233, 158, 94, 239]`(数学上不可能:x1 > x2) | 怀疑模型对 out-of-distribution 输入 fallback 到默认 anchor | 尝试中:手动 YUYV→RGB888 转换 + 字节序验证 |
|
||
| 13 | 字节序判断反复:RGB565LE / BE / YUYV 都试过 | OV3660 FORMAT_CTRL00=0x61 设置是"RGB565 byte-swapped",但实测数据更像 YUYV | 通过 `frame debug` 打印前 16 字节诊断,按多种格式对比解读 |
|
||
|
||
### 6.5.4 任务调度踩坑(Core 亲和 & 崩溃)
|
||
|
||
| # | 坑 | 原因 | 解决方案 |
|
||
|---|---|-----|---------|
|
||
| 14 | 唤醒词检测后触发 LED 时崩溃(`Interrupt wdt timeout`)| face_tracker 绑 Core 0,esp-dl 推理占 150ms,RMT LED 驱动在同核抢不到 spinlock 超 300ms | face_tracker 改绑 **Core 1**(音频空闲时让出 CPU) |
|
||
| 15 | GDMA ISR `InstrFetchProhibited` at PC=0x00000000 | esp_video 1.4.1 底层 DMA 回调指针变 NULL(疑似 ESP-IDF 5.4.2 兼容性 bug) | 未完全修复。降低 FPS 到 5 能推迟崩溃。待升级 ESP-IDF 或改用低级 esp_cam_ctlr API |
|
||
| 16 | 弱符号 `uart_send_face` 链接失败 | C++ 名字修饰问题 | `extern "C"` 包裹 weak 前置声明 + strong 定义,确保 C 链接名一致 |
|
||
|
||
### 6.5.5 RP2040 端踩坑
|
||
|
||
| # | 坑 | 原因 | 解决方案 |
|
||
|---|---|-----|---------|
|
||
| 17 | 进入 idle 后眼球卡在最后追踪位置不回中 | 原 `facetrack()` idle 时直接 return,没有回中机制 | 新增 `_recenter_tracking_servos()`,在 `grove_active` 从 True→False 或首次进入 idle 时一次性回中到 90° |
|
||
| 18 | ESP32 持续发误识别坐标(-95/+40 等固定值) | 眼球持续累加偏移直到撞到机械限位(30°/150°) | 属 ESP32 侧 bug,但 RP2040 侧加 `staticflag` 去重 + 回中机制缓解 |
|
||
| 19 | mpremote `could not enter raw repl` | Pico 主循环阻塞或端口被旧 monitor 占用 | 拔插 USB 复位 Pico → 立刻 `mpremote cp` 抢时间窗 |
|
||
| 20 | 两个分支 RP2040 代码不同 | `CogletESP` 分支 4 个 .py(含 `coms.py`),`camera-version` 分支 3 个(无 `coms.py`) | 使用时对应分支代码,不要混用 |
|
||
|
||
### 6.5.6 硬件踩坑
|
||
|
||
| # | 坑 | 原因 | 解决方案 |
|
||
|---|---|-----|---------|
|
||
| 21 | 3 根飞线(35→14, 36→41, 37→42)是否到位 | CogNog V1.0 PCB 设计缺陷,PSRAM 和摄像头 D7/VSYNC/HREF 冲突 | 万用表测导通,确保原 GPIO 35/36/37 与摄像头断开,新 GPIO 14/41/42 接通 |
|
||
| 22 | 舵机堵转、发烫、齿轮刺耳声 | 误用 **360° 连续旋转舵机**(MG90S 360° 版外观和 180° 一样) | 更换为 **KPower M0090** 或 **MG90S 180° 金属齿轮版**(详见 4.9 节) |
|
||
| 23 | 烧录固件后 `Compile time` 仍是旧版 | 用 IDE 内置烧录工具可能烧到错误路径 / 没完整烧录 | 改用命令行 `idf.py -p /dev/cu.usbmodemXXX flash monitor`,烧完立即 monitor 看 `Compile time` 验证 |
|
||
|
||
### 6.5.7 调试方法论
|
||
|
||
| 方法 | 用途 |
|
||
|------|-----|
|
||
| **frame debug 打印前 16B 字节值** | 按多种格式(YUYV / RGB565LE / RGB565BE)对比解读,判断 sensor 实际输出 |
|
||
| **zero_bytes 比例统计** | 判断画面亮度(>30% 大概率镜头遮挡或极暗;<5% 画面正常) |
|
||
| **box 数学合法性** | `box[0] > box[2]` 或 `box[2] == width` 这种异常值说明模型崩了(OOD 默认输出) |
|
||
| **对比不同画面输入下 box 是否变化** | 遮住摄像头 / 白墙 / 人脸 → 如果 box 完全一致,一定是模型输入路径异常 |
|
||
| **Compile time 验证烧录生效** | 烧录后 monitor 看 `I (xxx) app_init: Compile time: ...` 必须是最新编译时间 |
|
||
| **xtensa-esp32s3-elf-addr2line** | 把 crash backtrace 的地址转成源码文件:行号 |
|
||
|
||
### 6.5.8 Phase 01 未解决问题(遗留)
|
||
|
||
1. ❌ **box 固定伪激活**:无论遮住摄像头、白墙、真实人脸,box 都稳定输出 `[233, 158, 94, 239]`(或 MSR 模型的 `[0, *, 40, *]`)。已验证:
|
||
- pix_type 不是根因(YUYV / RGB565LE / RGB565BE / RGB888 手动转换都不行)
|
||
- 双 buffer 修复了数据更新问题,但 box 仍固定
|
||
- 模型切换(MSR → ESPDET)改变了 box 坐标但仍固定
|
||
- 推断:模型对某种 out-of-distribution 输入 fallback 到默认 anchor
|
||
|
||
2. ❌ **GDMA ISR 崩溃**:每 ~30 秒触发一次 `InstrFetchProhibited` at PC=0x00000000,属 esp_video 1.4.1 / ESP-IDF 5.4.2 底层 bug
|
||
|
||
3. ⚠️ **端到端未验收**:RP2040 端 Bug 3(回中机制)已修,但因 ESP32 侧 box 仍固定,整条链路的"眼球跟随真实人脸"未通过
|
||
|
||
---
|
||
|
||
## 六点六、Phase 01 核心矛盾与解决方案分析(2026-04-21 ~ 04-22)
|
||
|
||
> 基于 JPEG Dump 诊断工具的大量实验(9 次迭代尝试),本节汇总当前主要矛盾、根本原因和方案选择。
|
||
|
||
### 6.6.1 主要矛盾:画面可辨识 ≠ 模型可识别
|
||
|
||
**诊断工具**:在 face_tracker.cc 里加 JPEG Dump 代码,每次启动 base64 打印一帧 JPEG,Mac 端 Python 脚本抓取保存为 `.jpg` 文件肉眼验证。
|
||
|
||
**验证结果**:
|
||
|
||
| 观察 | 事实 |
|
||
|------|------|
|
||
| 手动撕掉镜头保护膜后,JPEG 画面可**清晰看到戴眼镜的人脸、手部、背景** | ✅ 摄像头硬件 + 飞线 + DVP 通路完全正常 |
|
||
| 画面**整体偏紫绿**(RGB565 模式)或**偏绿**(YUV422 模式) | 🟡 软件层 YUV→RGB 色彩矩阵偏差,不是硬件问题 |
|
||
| 同样的摄像头输入,esp-dl `HumanFaceDetect` **无论什么 pix_type 都输出固定 box** | ❌ 深层集成问题 |
|
||
|
||
**核心矛盾**:
|
||
|
||
> 人眼能辨识的画面(因为有上下文知识"绿色的这个 = 人脸"),轻量级 CNN 模型无法识别(只看像素数值分布)。esp-dl 官方模型用**正常色彩的标准人脸数据集**训练,我们的偏色画面在训练集里找不到对应模式 → 模型 fallback 到默认 anchor → box 恒定。
|
||
|
||
### 6.6.2 YUV→RGB 色偏的三层原因
|
||
|
||
#### 第 1 层:**YUV→RGB 色彩矩阵公式不完全匹配 BT.601**
|
||
|
||
- OV3660 输出 YUV **限幅范围**:Y ∈ [16, 235], U/V ∈ [16, 240](中值 128)
|
||
- 手写转换公式假定 Y ∈ [0, 255] **全范围**(JFIF 标准):
|
||
```cpp
|
||
int r = Y + 1.402 * (V - 128); // 错:没有黑电平偏移,整体偏暗
|
||
```
|
||
- 正确应为(BT.601):
|
||
```cpp
|
||
int y_scaled = 1.164 * (Y - 16); // 减黑电平、放大到全范围
|
||
int r = y_scaled + 1.596 * (V - 128);
|
||
```
|
||
|
||
#### 第 2 层:**OV3660 AWB(自动白平衡)未启用或响应慢**
|
||
|
||
- 默认寄存器序列中,AWB 可能关闭或慢响应
|
||
- 导致 U/V 有**全局偏移**:画面整体偏绿/紫
|
||
- Grove Vision AI V2 内置 ISP 硬件自动白平衡,**我们读原始 YUV buffer 没有**
|
||
|
||
#### 第 3 层:**OV3660 FORMAT_CTRL00 = 0x61 的实际含义**
|
||
|
||
- `bit[7:4] = 0x6` = RGB565
|
||
- `bit[3:0] = 0x1` = byte-swap 序列
|
||
- 在 Kconfig RGB565 模式下,sensor 实际输出可能是 **YVYU sequence**(Y-V-Y-U)而非标准 YUYV,导致 U/V 解读时互换
|
||
- 修正方向:在 Kconfig 改用 YUV422 模式(FORMAT_CTRL00=0x30,标准 YUYV)
|
||
|
||
### 6.6.3 esp-dl 模型输入分布敏感
|
||
|
||
即使色彩完全校正正确,轻量级模型(MSR_S8_V1 仅 60KB、ESPDET_PICO_224_224_FACE 约 500KB)对 RGB 分布偏差**极其敏感**。具体要求:
|
||
|
||
| 要求 | 解释 |
|
||
|------|------|
|
||
| RGB 通道均值接近训练集 | ImageNet 类数据集 RGB 均值约 (124, 116, 104) |
|
||
| 归一化范围精确 | ESPDET 用 `(pixel-0)/255`,要求 pixel ∈ [0, 255] 全范围 |
|
||
| 无严重色偏 | 偏绿会让模型前几层卷积产生"异常激活",后续全部 fallback |
|
||
| 无边缘伪影 | letterbox 填充不能和画面内容对比度过强 |
|
||
|
||
> **为什么 Grove Vision AI V2 一定能行**:Grove 用 Himax WiseEye HX6538 专用 AI 视觉处理器,内置 ISP + 针对自己 sensor 训练的**专用人脸检测模型**,从硬件到模型端到端自闭环。esp-dl 是通用框架,需要用户自己保证数据质量。
|
||
|
||
### 6.6.4 sensor 硬件 JPEG 模式的局限
|
||
|
||
OV3660 支持硬件 JPEG 编码(`CAMERA_OV3660_DVP_JPEG_1280X720_12FPS`),但实测失败:
|
||
|
||
- `Esp32Camera::Capture()` 默认不协商 JPEG pix_fmt,报 `no supported pixel format found`
|
||
- 启用 `CONFIG_XIAOZHI_CAMERA_ALLOW_JPEG_INPUT=y` 后能协商,但 `bytesused=0` —— DMA 没采到 JPEG 帧
|
||
- 推测 sensor 硬件 JPEG 需要特殊的 DVP 帧同步处理,xiaozhi 的 V4L2 mmap 路径不兼容
|
||
|
||
结论:硬件 JPEG 路径此项目未打通,**需走软件 JPEG 编解码**。
|
||
|
||
### 6.6.5 延迟分析:JPEG 中转路径
|
||
|
||
走 `xiaozhi Capture() → JPEG → esp-dl sw_decode_jpeg → RGB888 → HumanFaceDetect` 路径的延迟估算:
|
||
|
||
| 阶段 | 耗时 | 说明 |
|
||
|------|------|------|
|
||
| 摄像头采集一帧 | ~40ms | 24 FPS 间隔 |
|
||
| xiaozhi Capture() 软件 JPEG 编码 | 50-80ms | 240×240 YUV→RGB→JPEG |
|
||
| esp-dl sw_decode_jpeg 解码 | 30-50ms | JPEG → RGB888 |
|
||
| HumanFaceDetect 模型推理 | 150ms | ESPDET_PICO_224 |
|
||
| UART 发送坐标 | 1ms | 240 bytes @ 115200 |
|
||
| **ESP32 端总延迟** | **~270ms** | |
|
||
| RP2040 UART RX + parse | 2ms | |
|
||
| 舵机 PWM + 物理转动 | 20-80ms | 机械响应时间 |
|
||
| **端到端总延迟(脸动→眼球动)** | **~300-350ms** | |
|
||
|
||
**对比**:
|
||
- **人眼感知"流畅跟随"阈值**:< 500ms
|
||
- **Grove Vision AI V2**:~100-150ms(专用硬件)
|
||
- **JPEG 中转方案**:~300ms ✅ 可接受
|
||
- **人眨眼速度**:~400ms
|
||
|
||
### 6.6.6 三个路径选择
|
||
|
||
| 方案 | 预计工时 | 成功率 | 备注 |
|
||
|------|---------|-------|------|
|
||
| **A. 继续深挖 esp-dl(改色彩矩阵、启 AWB、fork 预处理)** | 8-10 小时 | ⭐⭐(20-30%)| 涉及 ov3660 寄存器调优 + 模型内部调试 |
|
||
| **B. JPEG 中转路径(走 xiaozhi 完整 Capture + esp-dl sw_decode_jpeg)** | 2-3 小时 | ⭐⭐⭐⭐(70-80%)| **推荐**。`take_photo` 已证明 Capture() 色彩正常 |
|
||
| **C. 退回 Grove Vision AI V2(项目原设计)** | 2 小时 + ¥200 | ⭐⭐⭐⭐⭐(100%)| 官方 turnkey 方案,稳妥 |
|
||
|
||
### 6.6.7 方案 B 的关键突破口
|
||
|
||
**关键发现**:xiaozhi 的 `self.camera.take_photo` MCP 功能拍的照片**云端 AI 能清晰识别**,说明 `Capture()` 函数内部有正确的色彩处理(白平衡、色彩矩阵、JPEG 标准编码)。
|
||
|
||
**未尝试的真正路径**:
|
||
```
|
||
Esp32Camera::Capture()
|
||
↓ 内部完整 pipeline(色彩正常的 JPEG)
|
||
JPEG buffer
|
||
↓ esp-dl sw_decode_jpeg(esp-dl 官方 example 路径)
|
||
标准 RGB888 画面(色彩 100% 匹配训练集)
|
||
↓
|
||
HumanFaceDetect → 真正的 box
|
||
```
|
||
|
||
**之前的失败路径**:我一直绕过 `Capture()` 用 `CaptureForDetection()` 直接拿 V4L2 mmap 的原始 YUV buffer,缺少 xiaozhi 的色彩校正。
|
||
|
||
### 6.6.8 验证方案 B 可行性的最简方法
|
||
|
||
**不用改代码**:
|
||
|
||
1. 烧录当前 YUV422 模式的固件
|
||
2. 通过小智对话说"**帮我拍张照看看**"或"**你看见什么了**"
|
||
3. AI 云端返回画面描述
|
||
|
||
- 如果 AI 说能**清晰看到人脸/房间物体** → 证明 `Capture()` 色彩正常 → **方案 B 可行性 80%+**
|
||
- 如果 AI 说看不清或描述错乱 → `Capture()` 也有色偏 → 需考虑方案 C
|
||
|
||
### 6.6.9 当前代码状态快照(2026-04-22)
|
||
|
||
- `main/face_tracker.cc`:手动 YUYV→RGB888 转换 + pix_type=RGB888(失败路径,保留代码)
|
||
- `main/face_tracker.cc`:JPEG Dump 诊断代码(每次启动拍一张 YUYV JPEG)
|
||
- `sdkconfig`:`CAMERA_OV3660_DVP_YUV422_240X240_24FPS=y`(画面偏绿但结构清晰)
|
||
- 固件编译通过,烧录正常,face_tracker 启动正常
|
||
- 症状:box 恒定 `[233, 158, 94, 239]`,眼球卡在极限位置不跟随
|
||
|
||
---
|
||
|
||
## 七、参考资源
|
||
|
||
| 资源 | 地址 |
|
||
|------|------|
|
||
| Coglet GitHub 主仓库 | https://github.com/will-cogley/Coglet |
|
||
| CogletESP 固件仓库 | https://github.com/will-cogley/CogletESP |
|
||
| 小智 ESP32 原版 | https://github.com/78/xiaozhi-esp32 |
|
||
| 眼球机构参考 | https://github.com/will-cogley/EyeMech_Epsilon |
|
||
| Coglet 组装视频 | https://www.youtube.com/watch?v=-7I-jFSNP2E |
|
||
| MicroPython 固件下载 | https://micropython.org/download/RPI_PICO/ |
|
||
| ESP-IDF 官方文档 | https://docs.espressif.com/projects/esp-idf/zh_CN/v5.4.2/ |
|
||
| MicroPython 官方文档 | https://docs.micropython.org/en/latest/ |
|
||
| mpremote 工具文档 | https://docs.micropython.org/en/latest/reference/mpremote.html |
|
||
| SenseCraft AI 平台(Grove Vision AI V2 模型部署) | https://sensecraft.seeed.cc/ |
|
||
| Grove Vision AI V2 Wiki | https://wiki.seeedstudio.com/grove_vision_ai_v2/ |
|