Initial snapshot: hand motion pipeline (Dyn-HaMR + dex-retargeting + SPIDER)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 14:13:49 +08:00
commit 7e4ef6f98b
7002 changed files with 1452249 additions and 0 deletions
+185
View File
@@ -0,0 +1,185 @@
# HandFlow
**Fully Generative 4D Hand Recovery with Flow Matching.**
HandFlow reconstructs 4D (temporal 3D) hand poses from monocular RGB video. At its core is a Rectified Flow Denoiser: HaMeR ViT-H extracts image features, combined with 2D skeletons as multimodal conditioning, to directly regress a sequence of MANO parameters. ViPE SLAM can optionally supply camera geometry to reconstruct the hand trajectory in the world coordinate frame.
> This repository is the **V1 open-source release**: it includes inference + visualization demos only, without training code.
---
## Pipeline
A single entry point, `scripts/demo.py`, produces two mp4s per run: a camera-view mesh **overlay** and an **orthographic** view. The `--fix_camera` flag selects how camera geometry is obtained:
```
demo.py
┌──────────────────────────────────────────────────────────────────┐
│ Video (--input) │
│ ├─ Intrinsics: --intrinsics fx,fy,cx,cy | auto | │
│ │ unset → generic default 600,600,W/2,H/2 │
│ ├─ Camera geometry (c2w): │
│ │ --fix_camera → c2w = identity, ViPE skipped │
│ │ (default) → ViPE SLAM estimates c2w │
│ ├─ Online HaMeR → FM Denoiser → MANO FK → verts_cam (m) │
│ └─ verts_world = c2w · verts_cam │
│ │
│ → overlay.mp4 clean camera-space MANO mesh over the RGB frames │
│ → ortho.mp4 --fix_camera : level side view, fixed camera │
│ (default) : world topdown — camera trajectory │
│ line + per-frame hand mesh │
└──────────────────────────────────────────────────────────────────┘
```
- **`--fix_camera`** (fixed camera, e.g. a tripod-mounted rig like DexYCB): ViPE is skipped, `c2w` is identity, and the orthographic view assumes a **level, fixed camera** — a side view of the camera-space hand motion (the camera stays at the origin; only the hand moves).
- **default** (moving camera, e.g. a head-mounted rig like HOT3D): ViPE SLAM estimates `c2w`; the orthographic view is a world-space trajectory view (camera trajectory line + per-frame hand mesh). Because SLAM trajectory error accumulates, the world result looks slightly more "skewed" than the overlay; the orthographic view is exactly where this camera motion becomes visible.
---
## Installation
### 1. Clone (with submodules)
```bash
git clone --recurse-submodules https://github.com/mxxu00/HandFlow.git
cd HandFlow
# If you already cloned without submodules:
git submodule update --init --recursive
```
Submodules:
- `third_party/hamer` — HaMeR ViT-H backbone ([geopavlakos/hamer](https://github.com/geopavlakos/hamer))
- `third_party/vipe` — ViPE SLAM ([nv-tlabs/vipe](https://github.com/nv-tlabs/vipe), only needed in moving-camera mode)
### 2. Inference + rendering environment
```bash
bash setup_env.sh # creates conda env handflow (Python 3.10 + torch 2.7.0+cu128)
conda activate handflow
pip install -e third_party/hamer # HaMeR and its dependencies
```
> `setup_env.sh` also builds **pytorch3d** (the Phong renderer) from source, which requires `nvcc`; if the build fails, see the [pytorch3d install guide](https://github.com/facebookresearch/pytorch3d/blob/main/INSTALL.md). `--fix_camera` mode then works without the ViPE environment.
### 3. ViPE environment (only needed in moving-camera mode, optional)
ViPE ships a CUDA extension that must be compiled in a **separate conda environment**; `demo.py` invokes it automatically via `conda run -n vipe` whenever `--fix_camera` is **unset**.
```bash
bash setup_vipe_env.sh # creates conda env vipe (with nvcc + Eigen, compiles vipe_ext)
```
> The first build requires internet access to download Eigen 3.4. ViPE's torch version (2.7.0+cu128) matches handflow, so the extension compiles correctly.
---
## Weights & Data
Download the following weights yourself and specify their paths via environment variables:
| Environment variable | Description | Source |
|---|---|---|
| `HAMER_CKPT` | `hamer.ckpt` (HaMeR ViT-H weights) | [HaMeR releases](https://github.com/geopavlakos/hamer) |
| `DETECTOR_CKPT` | `detector.pt` (YOLO hand detector, **from WiLoR**) | [WiLoR](https://github.com/rolpotamias/WiLoR) `pretrained_models/detector.pt` |
| `MANO_ROOT` | MANO model directory (`.pkl`) | [mano.is.tue.mpg.de](https://mano.is.tue.mpg.de) (register to download) |
| `HANDFLOW_NORMALIZATION_STATS` | `normalization_stats.npz` | [HuggingFace](https://huggingface.co/mxxu00/HandFlow) |
| `--fm_ckpt` | HandFlow FM checkpoint (`handflow_denoiser.pt`) | [HuggingFace](https://huggingface.co/mxxu00/HandFlow) (passed as a CLI argument) |
Example setup (add to `~/.bashrc`):
```bash
export HAMER_CKPT=/path/to/hamer.ckpt
export DETECTOR_CKPT=/path/to/detector.pt
export MANO_ROOT=/path/to/mano
export HANDFLOW_NORMALIZATION_STATS=/path/to/normalization_stats.npz
```
> **HandFlow weights** (`normalization_stats.npz` + `handflow_denoiser.pt`) are hosted on [HuggingFace](https://huggingface.co/mxxu00/HandFlow). Download both with:
>
> ```bash
> hf download mxxu00/HandFlow --local-dir ./weights
> ```
>
> Then point `HANDFLOW_NORMALIZATION_STATS` at `normalization_stats.npz` and pass `--fm_ckpt ./weights/handflow_denoiser.pt` to `demo.py`.
---
## Demo
Two short sample clips are bundled in `demo/` — one from [DexYCB](https://dex-ycb.github.io/) (fixed camera) and one from [HOT3D](https://hot3d.github.io/) (moving camera). The commands below run the full pipeline on each.
**DexYCB — fixed camera (no ViPE needed):**
```bash
conda activate handflow
python scripts/demo.py --input demo/dexycb_sample.mp4 \
--fm_ckpt ./weights/handflow_denoiser.pt \
--intrinsics 616.2495,615.87665,321.6139,244.8281 \
--fix_camera --output_dir output/dexycb
# → output/dexycb/overlay.mp4
# → output/dexycb/ortho_side.mp4
```
**HOT3D — moving camera (requires the vipe env):**
```bash
python scripts/demo.py --input demo/hot3d_sample.mp4 \
--fm_ckpt ./weights/handflow_denoiser.pt \
--intrinsics 609.7035,609.7035,707.6459,704.7342 \
--output_dir output/hot3d
# → output/hot3d/overlay.mp4
# → output/hot3d/ortho_topdown.mp4
```
---
## Usage
```bash
# Your own video, known intrinsics, fixed camera (e.g. webcam on a stand)
python scripts/demo.py --input video.mp4 --fm_ckpt <ckpt> \
--intrinsics fx,fy,cx,cy --fix_camera --output_dir output/demo
# Your own video, known intrinsics, moving camera (ViPE estimates c2w only)
python scripts/demo.py --input video.mp4 --fm_ckpt <ckpt> \
--intrinsics fx,fy,cx,cy --output_dir output/demo
# Your own video, unknown intrinsics, moving camera (ViPE estimates intrinsics + c2w)
python scripts/demo.py --input video.mp4 --fm_ckpt <ckpt> \
--intrinsics auto --output_dir output/demo
```
Key options:
- `--intrinsics` `fx,fy,cx,cy` (recommended) | `auto` (ViPE estimates; moving-camera only) | unset → generic default `600,600,W/2,H/2`.
- `--fix_camera` : fixed-camera mode (skip ViPE, `c2w=identity`, level side ortho view). Incompatible with `--intrinsics auto`.
- `--view` `topdown|side` : ortho view direction. Defaults to `side` under `--fix_camera`, else `topdown`.
> Left-hand videos must be mirrored to right-hand first (the FM model is trained on right-hand data).
---
## Project Layout
```
HandFlow/
├── scripts/
│ └── demo.py # unified demo: cam overlay + orthographic view
├── demo/ # bundled demo clips (dexycb_sample.mp4, hot3d_sample.mp4)
├── model/ # Rectified Flow Denoiser + feature extractors
├── utils/ # inference / MANO FK / online HaMeR / ViPE worker
├── visualization/ # pytorch3d Phong renderer (overlay + orthographic trajectory) + video I/O
├── preprocessing/ # crop utilities (used by online_hamer)
├── configs/ # model.yaml + inference.yaml (paths via ${oc.env:})
├── third_party/
│ ├── hamer/ # submodule
│ └── vipe/ # submodule (optional, moving-camera mode only)
├── setup_env.sh # inference environment
└── setup_vipe_env.sh # ViPE environment (optional, moving-camera mode only)
```
---
## TODO List
The current release (V1) ships **inference + visualization only**. The following components are planned for future release:
- [ ] Training code
- [ ] Data preprocessing code
- [ ] Evaluation code
+217
View File
@@ -0,0 +1,217 @@
# L20 左手专家轨迹 HDF5 交付要求
**接口版本:`l20_tracking_v1`**
**交付对象:视频重定向/示教数据同事**
**当前用途:浮动腕部与全部手指参考关节的轨迹回放,后续用于学习策略。**
本文是可独立分享的数据交付说明,与当前仓库校验器一致。它不是通用 HDF5 标准,也不是完整 DexSchema。
请先交付 **1 条短样例**,确认坐标、尺度、关节映射后再批量导出。
## 1. 要交什么
交付重定向后的 **L20 左手机器人参考状态轨迹**:
- 腕部/手根的三维位置与姿态。
- 约定模型的全部手指关节角,包括从动关节。
- 同步时间戳与逐帧有效标记。
- 模型身份、坐标变换、尺度和数据来源说明。
不是原始人手关键点、MANO 参数或电机实测指令。关节状态不能直接等同于执行器控制命令。
本阶段不要求图像、物体位姿、奖励、接触标签、速度、加速度、电流或力矩;缺少这些内容不需要补零伪造。
## 2. 文件结构(必填)
文件扩展名建议使用 `.hdf5`;HDF5 根属性通过 `file.attrs` 保存,**不要创建名为 `attrs` 的 group**。
```text
demonstrations.hdf5
├── 根属性:schema_version、embodiment、hand_side、asset_sha256、root_link、
│ provenance、source_description、metric_scale_provenance、scale_to_meters
├── metadata/
│ ├── joint_names UTF-8 [J]
│ └── world_from_source float64 [4, 4]
└── episodes/
├── demo_000000/
│ ├── time float64 [T]
│ ├── wrist_position float32 [T, 3]
│ ├── wrist_quaternion float32 [T, 4]
│ ├── joint_position float32 [T, J]
│ └── valid bool [T]
└── demo_000001/
└── 同样结构,可以有不同帧数 T
```
- 至少一个 episode;名称必须为 `demo_` 加六位数字,如 `demo_000012`。
- 每条 episode 至少两帧;各字段的 T 必须相同。
- 一个文件共享模型、关节顺序及源世界标定;标定、模型或关节布局改变时另建文件。
- 当前约定资产的 **J=21**,不是从“L20”名称推断出的自由度。21 个状态关节不代表 21 个独立执行器。
## 3. 根属性
除 `scale_to_meters` 外,下列属性均为非空 UTF-8 字符串。
| 属性 | 内容与要求 |
| --- | --- |
| `schema_version` | 固定为 `l20_tracking_v1` |
| `embodiment` | 固定为 `L20` |
| `hand_side` | 固定为 `left` |
| `asset_sha256` | 双方约定的原始模型依赖包哈希,64 位小写十六进制;见第 6 节 |
| `root_link` | 固定为当前模型的 `hand_base_link` |
| `provenance` | 实际专家重定向数据填 `expert_retargeted`;程序生成的测试数据填 `synthetic` |
| `source_description` | 视频/轨迹标识、重定向工具及版本、世界原点与 X/Y 朝向、源坐标约定、腕点到机器人根部的映射依据、质量限制 |
| `metric_scale_provenance` | 米制尺度如何获得,标定方法/版本或估算方法、可信程度与已知误差;不得把估算写成实测 |
| `scale_to_meters` | **有限正浮点标量**,记录已应用的源位置尺度系数;不是要求读取方再缩放 |
例:源位置以毫米表示,则 `scale_to_meters=0.001`;输出位置仍须已经是米。
源数据已经是米时可填 `1.0`,但必须有实际依据,不能仅为通过校验填写。
## 4. 字段与坐标语义
| 路径 | 类型 / shape | 单位及语义 |
| --- | --- | --- |
| `metadata/joint_names` | HDF5 UTF-8 string `[J]` | `joint_position` 每列对应的模型关节名称,严格使用第 5 节顺序 |
| `metadata/world_from_source` | `float64 [4,4]` | 已应用的源世界系到交付世界系刚体变换;矩阵平移部分单位 m |
| `episodes/.../time` | `float64 [T]` | 秒;每条轨迹从 0 开始,严格递增 |
| `episodes/.../wrist_position` | `float32 [T,3]` | `hand_base_link` 的 **link 原点**在交付世界系中的 `[x,y,z]`,单位 m |
| `episodes/.../wrist_quaternion` | `float32 [T,4]` | **`[w,x,y,z]`**;将根 link 局部向量主动旋转到交付世界系 |
| `episodes/.../joint_position` | `float32 [T,J]` | 参考关节角,单位 rad;零位和正方向与约定模型一致 |
| `episodes/.../valid` | `bool [T]` | 整帧是否有效;不是每关节标记,不使用 0/1 整数数组代替 bool |
### 坐标与姿态
1. 交付世界系采用 **右手系、Z 向上**,在一个 episode 内固定,不随腕部移动。
2. 根部位姿不是人手腕点、机器人质心或 USD default prim 位姿。重定向端必须先完成到
`hand_base_link` 坐标系的映射,并说明映射依据。
3. 记录的尺度和刚体变换满足:
```text
p_world = R_world_from_source @ (scale_to_meters * p_source) + t_world_from_source
```
此式用于源位置点;人手腕点到机器人根部的额外映射仍须由重定向端完成。
导出的 `wrist_position` 和 `wrist_quaternion` **已经在交付世界系**。
接收端不会再次应用该矩阵或尺度。
4. `world_from_source` 最后一行为 `[0,0,0,1]`,旋转部分正交且行列式为 +1,不能混入尺度或镜像。
已统一世界系时可以使用单位矩阵,但不能用单位矩阵掩盖未知标定。
5. 有效四元数必须单位化,范数容差 `1e-4`;相邻两帧都有效时,四元数点积必须非负。
`q` 与 `-q` 表示同一旋转,导出时应统一相邻符号;不能混用 `xyzw` 或欧拉角。
6. 不要为了适配当前合成测试而把真实轨迹自动平移到 `z=0.4`,或把起始姿态强制设为单位四元数。
场景对齐必须有明确、可追溯的坐标约定。
## 5. 关节顺序与联动
当前 `joint_names` 必须严格等于下列列表;这是数据清单顺序,**不是仿真运行时 DOF 顺序**。
```python
joint_names = [
"index_dip", "index_mcp_pitch", "index_mcp_roll", "index_pip",
"middle_dip", "middle_mcp_pitch", "middle_mcp_roll", "middle_pip",
"pinky_dip", "pinky_mcp_pitch", "pinky_mcp_roll", "pinky_pip",
"ring_dip", "ring_mcp_pitch", "ring_mcp_roll", "ring_pip",
"thumb_cmc_pitch", "thumb_cmc_roll", "thumb_cmc_yaw", "thumb_ip", "thumb_mcp",
]
```
- 不允许重名、缺列、多列、静默重排或用其他手型的关节代替。
- 每个有效帧必须满足清单 `joints[].lower_rad/upper_rad`,校验容差为 `1e-6 rad`。
精确限位以随交付约定的 JSON 清单为准,避免使用文档四舍五入值作为限位。
- 当前源模型定义以下联动;数据中仍须包含这些从动关节列:
```text
thumb_ip = 1.02 * thumb_mcp
index_dip = 0.89 * index_pip
middle_dip = 0.89 * middle_pip
ring_dip = 0.89 * ring_pip
pinky_dip = 0.89 * pinky_pip
```
偏置均为 0 rad,有效帧等式残差绝对值不得超过 `1e-3 rad`。
- 联动关系和各关节限位必须同时满足;不能单独裁剪从动关节而破坏联动。
- 这些是约定模型的约束,不是已经核实的硬件电机协议。重定向映射有疑问时先确认,不要擅自独立控制从动关节。
## 6. 模型身份与随附清单
本仓库当前清单:
[`assets/robots/dex_hand/linkerhand_g20_left/tracking_manifest.json`](assets/robots/dex_hand/linkerhand_g20_left/tracking_manifest.json)
当前原始模型依赖包 `asset_sha256`:
```text
6c8f35358f481cf604ee802588017f30c1661c0f38e1187c49e5340db3830538
```
这是**当前版本快照**。请由仿真方提供同版 `tracking_manifest.json` 与约定模型,数据方确认使用该模型后填写哈希。
不得把上述哈希复制到实际使用了其他模型的数据中。
- 哈希不是 HDF5 文件自身哈希,不是仅入口 USD 的哈希,也不是浮动控制覆盖层的哈希。
- 它由完整解析资源包的相对路径与各文件 SHA-256 汇总生成,使用仓库工具获取,不自行猜测算法。
- 模型依赖内容或名称变化时必须重新对齐。分享本文给同事时,**请同时附上同版清单**,不要只发送失效的仓库相对链接。
## 7. 时间同步、无效帧与缺失数据
- `time[0]` 必须精确等于 `0.0`,之后严格递增;不得有重复、倒序时间戳。
- 允许非均匀采样,不强制视频帧率等于仿真控制频率。保留实际时间间隔,不通过伪造时间戳“拉齐”数据。
- 同一帧腕部、姿态和关节角必须已经同步;异步数据须由交付方先明确对齐方式。
- 任一必需部分缺失或不可信,整帧 `valid=false`。至少一帧有效;全部无效的 episode 会被拒绝。
- **所有数值都必须有限,包括无效帧,不能出现 NaN/Inf。** 无效帧可以用明确的有限占位值,
但必须保留 `valid=false`,并在来源说明中解释。无效帧四元数不要求单位化。
- 必填 dataset 不能省略;禁止把缺失帧补零后标成有效,或把插值/估算值冒充测量。
- 当前重采样 CLI 只接受全有效 episode,不跨无效片段插值。建议交付方将连续有效片段显式切成
独立 episode,并为每段重新设置从 0 开始的时间;原片段来源需可追溯。
## 8. 导出注意事项
使用 `h5py` 时,字符串必须显式声明 UTF-8,数值类型按契约转换:
```python
# 仅展示 dtype 写法;不是完整数据生成器,不包含真实轨迹。
import h5py
import numpy as np
utf8 = h5py.string_dtype(encoding="utf-8")
# metadata.create_dataset("joint_names", data=joint_names, dtype=utf8)
# group.create_dataset("time", data=np.asarray(time, dtype=np.float64))
# group.create_dataset("wrist_position", data=np.asarray(position, dtype=np.float32))
# group.create_dataset("wrist_quaternion", data=np.asarray(quaternion_wxyz, dtype=np.float32))
# group.create_dataset("joint_position", data=np.asarray(q, dtype=np.float32))
# group.create_dataset("valid", data=np.asarray(valid, dtype=np.bool_))
```
不要用 `dtype="S"` 或普通 ASCII bytes 数组写 `joint_names`。不要用 float64 替代契约中的 float32,
也不要用整数时间戳或整数 `valid` 代替约定类型。
## 9. 交付及验收流程
### 数据方交付
1. 一份包含一条短轨迹的 `.hdf5` 样例,优先选择连续、全有效、缓慢运动的片段。
2. 对应模型清单/版本确认。
3. 明确的标定与重定向说明;写入根属性,也可附独立说明文件。
4. 可选:对应原视频片段或可视化,便于人工核对方向、尺度与动作,不是本格式必填字段。
### 仿真方校验
从仓库根目录运行:
```bash
export PYTHONPATH="$PWD/source/dex_workbench${PYTHONPATH:+:$PYTHONPATH}"
~/isaacsim/python.sh -m dex_workbench_tracking.cli validate /path/to/demonstrations.hdf5 \
--manifest assets/robots/dex_hand/linkerhand_g20_left/tracking_manifest.json
```
该校验不启动仿真。仿真方本地的 `python.sh` 路径不是数据文件的依赖;具备项目模块、NumPy、h5py
的普通 Python 环境也可执行同一 `-m` 命令。
- **PASS**:schema、模型身份、关节顺序、限位和已记录联动一致。
- **FAIL**:修正具体报错后重新交付,不绕过校验器或放宽断言。
- 不加 `--manifest` 只检查格式,不能证明模型/关节兼容。
- 校验通过后仍需人工核对坐标和标定,再做受限动力学回放。格式通过不等于重定向正确、动力学可执行、
策略已经可训练或真机可运行。
**注意:** 当前诊断脚本要求腕部及全部五组联动都有足够运动,用于发现失效约束;这是诊断用例要求,
不是 HDF5 格式要求。合法的静止或局部手指轨迹不应为了满足诊断而添加伪造动作。
仿真实现与已有验证结果另见 [`L20_TRACKING.md`](L20_TRACKING.md)。本文只约定数据交付,不要求数据方运行 Isaac Sim。
+99
View File
@@ -0,0 +1,99 @@
# Dyn-HaMR → dex-retargeting → SPIDER 集成说明
## 组件和职责
| 层 | 上游/实现 | 输入与输出 |
|---|---|---|
| 人手重建 | Dyn-HaMR、HaMeR、ViPE | 视频 → MANO 参数与估计世界轨迹 |
| 另一条重建入口 | HandFlow | 视频 → 时序 MANO 与相机/世界坐标结果 |
| 机器人重定向 | dex-retargeting 0.5.0 + scripts/l20_* | 21 点人手几何 → L20 右手关节及浮动腕部 |
| 接触优化实验 | SPIDER 上游 runner + L20 场景适配 | 机器人/物体参考轨迹 → 接触引导优化与回放检查 |
| 数据交付 | export_l20_right_hdf5.py | 机器人参考轨迹 → HDF5 |
集成脚本与上游项目分别保留;不能把上游算法或整个仓库归为本地个人实现。
## 当前入口和顺序
以下命令从项目根目录执行,输入文件、权重、模型和环境须先准备。这里只列出已存在入口,不代表干净机器已端到端复现。
### 1. 重建结果
Dyn-HaMR 原生入口为 `third_party/Dyn-HaMR/dyn-hamr/run_opt.py`,参数见其 README 和配置。`scripts/resume_dynhamr_2047635068.py` 是已有缓存的恢复脚本,依赖 `output/vipe_weights_downloads.txt` 及先前生成的缓存,不能当作新视频通用入口。
本地 ViPE 调用已采用 Python CLI;相关修改随源码快照保留。环境与数据路径仍需按部署机器配置。
本例期望重建产物位于 `output/results_2047635068/world_results.npz`。
```bash
third_party/Dyn-HaMR/.dynhamr/bin/python scripts/export_dynhamr_dex.py
```
产物:`output/dex_2047635068/human_joints.npz`。导出器要求恰好一条完整右手轨迹;MANO convention 按 Dyn-HaMR 原模型计算。
### 2. 重定向与平滑
```bash
MUJOCO_GL=osmesa .dex/bin/python scripts/run_dex_l20.py
MUJOCO_GL=osmesa .dex/bin/python scripts/run_dex_l20.py \
--output-dir output/l20_2047635068/jitter_audit/corrected
MUJOCO_GL=osmesa .dex/bin/python scripts/smooth_l20_offline.py
```
该分支使用 JSON 非线性标定联动。后续 SPIDER/完整回放构建器按原 URDF 重新展开线性联动;两种约束不能混称为同一个模型。
HandFlow 分支的入口为 `scripts/demo.py` → `export_handflow_dex.py` → `retarget_l20_video.py`;后者支持 `--input` 和 `--output-dir`,直接使用 URDF 线性联动。
### 3. SPIDER 实验
先准备 `third_party/l20_assets/L20/RIGHT/` 和 `third_party/bottle_model_parametric/bottle.xml` 及依赖资源。
```bash
MUJOCO_GL=osmesa .spider/bin/python scripts/prepare_spider_l20.py
MUJOCO_GL=osmesa .spider/bin/python scripts/run_spider_l20_contact.py
MUJOCO_GL=osmesa .spider/bin/python scripts/evaluate_spider_l20.py
```
准备脚本选择第 600–659 帧的 2 秒片段,并估算瓶子相对位姿。必须查看实际运行日志和评估结果;程序开始运行不等于优化完成或稳定抓取。
### 4. 1333 帧完整运动学回放和导出
```bash
MUJOCO_GL=osmesa .spider/bin/python scripts/build_l20_full_replay.py
MUJOCO_GL=osmesa .spider/bin/python scripts/verify_l20_full_replay.py
MUJOCO_GL=osmesa .spider/bin/python scripts/play_l20_full.py --render
MUJOCO_GL=osmesa .dex/bin/python scripts/export_l20_right_hdf5.py \
--source output/l20_full_replay/motion.npz \
--scene output/l20_full_replay/scene.xml \
--output output/hdf5_delivery/right_1333
```
`.dex` 环境还需可导入 h5py。最终完整回放为运动学指定状态及接触配准,不是 44 秒 SPIDER 动力学成功轨迹。瓶子位姿是估计注册;HDF5 只导出手部状态,右手约定与原左手验收要求不同。详见 HDF5 根属性和 `docs/HDF5_REQUIREMENTS.md`。
## 环境
本机使用独立环境:`.venv`(HandFlow/ViPE)、`third_party/Dyn-HaMR/.dynhamr`、`.dex`、`.spider`。保留兼容的 Torch/CUDA,避免将多个上游依赖盲目装入一个环境。
- 原 HandFlow 的 `requirements.txt` 和 setup 脚本保留历史默认值,不代表当前集成环境锁。
- `requirements/retarget.txt` 为重定向与导出依赖列表;`retarget_observed.json` 为本机观测版本,不是完整锁文件。
- Dyn-HaMR/SPIDER/ViPE 的安装步骤分别见所附上游 README、requirements 或 pyproject 文件;GPU 扩展需匹配实际 Python、Torch、CUDA。
- 示例解释器路径表示角色约定,可替换为部署环境的对应解释器。不要复制本机 venv 或其中的绝对路径 `.pth` 文件。
## 资源与代码托管
权重、MANO 参数、用户视频、缓存和实验输出不进源码包。L20 模型及标定文件也作为独立资源管理,接收方按 `EXTERNAL_ASSETS.json` 恢复原相对路径。资源使用条件分别由提供方约定;根 LICENSE 不覆盖所有第三方资源。
源码导出脚本将本机实际第三方源码以普通目录快照保存,包含本地改动,保留 LICENSE。它不依赖平台能递归拉取 Git 子模块。第三方版本和文件 SHA-256 记录在交付清单中;dex-retargeting 通过版本依赖安装。被排除的上游文件记录在 `EXCLUDED_FILES.json`,不可把源码包误认为含全部运行资源的离线安装包。
## 上传到本地代码托管平台
先在平台创建空仓库,解压源码包,在解压后的项目目录执行:
```bash
git init -b main
git add .
git commit -m "Import integrated hand reconstruction and L20 retargeting pipeline"
git remote add origin <本地平台仓库地址>
git push -u origin main
```
源码包没有原 Git 历史或 remote;当前工作目录的上游 origin 未修改。提交前可查看 `git status --short`;大数据放平台附件或独立存储。
+478
View File
@@ -0,0 +1,478 @@
# HandFlow 项目:手部动作重定向流程与踩坑记录
整理日期:2026-09-15。依据当前本地脚本、输出 README 与 JSON 验证报告整理;本次仅整理文档,没有重新执行重建、重定向或动力学优化。文中数值是保存的运行结果,不代表新一轮实验。
## 1. 当前实现与结论边界
本项目的机器人重定向流程,将视频重建的人手动作转换为机器人手的关节轨迹,再保留世界坐标下的腕部运动,并增加物体接触处理。
当前机器人分支实际消费的是 **Dyn-HaMR 的 MANO 重建结果**。仓库主 README 中 HandFlow FM 推理入口与这里新增的机器人脚本应区分;不能将这次全部输出描述为 HandFlow FM 模型直接生成的机器人动作。
| 阶段 | 已有结果 | 能支持的结论 | 尚不能支持的结论 |
|---|---|---|---|
| Dyn-HaMR 重建 | 1333 帧、两条手轨迹、相机参数与世界坐标参数 | 全长重建及渲染流程完成 | 已达到实测三维精度 |
| LEAP 基线 | 四指、固定腕部的 dex 重定向 | 人手关键点到机器人关节链路可运行 | 移动腕部或抓瓶成功 |
| L20 基础重定向 | 16 主动 + 5 被动关节,移动腕部 | 限位、联动与前向运动学一致性通过 | 真实硬件可直接执行 |
| 离线平滑 | 完整 1333 帧手部轨迹 | 保存指标显示连续性改善 | 实时滤波或无偏恢复真实运动 |
| 全长接触回放 | 44.43 秒完整视频 | 指定姿态下接触几何与连续性检查完成 | 44 秒动力学稳定抓取 |
| SPIDER | 2 秒片段优化及独立 CPU/GPU 回放 | GPU 上保持接触,明显优于原控制基线 | 通过保持门限、跨后端可靠抓取或硬件验证 |
**当前完整视频是运动学接触回放。SPIDER 只验证了两秒片段,保持门限未通过,独立 CPU 回放出现滑脱。**
## 2. 数据流、版本与关键文件
```text
2047635068.mp4
→ Dyn-HaMR / VIPE / 运动先验等上游处理
→ output/results_2047635068/world_results.npz
→ export_dynhamr_dex.py
→ output/dex_2047635068/human_joints.npz
├─ run_dex_leap.py → LEAP 固定腕部基线
└─ run_dex_l20.py → L20 JSON 非线性联动、移动腕部
→ jitter_audit/corrected/motion.npz
→ smooth_l20_offline.py
→ jitter_audit/temporal/motion.npz
├─ prepare_spider_l20.py → 线性联动、2 秒参考与场景
│ → run_spider_l20_contact.py → SPIDER 动力学结果
│ → 独立 GPU / CPU 控制回放
└─ build_l20_full_replay.py
→ 复用已准备的 SPIDER 场景及初始配准
→ 线性联动、全长接触配准
→ output/l20_full_replay/
```
这里存在真实的文件依赖:全长回放构建脚本读取 SPIDER **准备阶段**生成的场景和初始参考,不要求先跑完 SPIDER 优化。它没有把 SPIDER 的两秒优化结果扩展成整段物理结果。
| 用途 | 文件 |
|---|---|
| 导出人手关键点 | [export_dynhamr_dex.py](../scripts/export_dynhamr_dex.py) |
| L20 重定向入口 | [run_dex_l20.py](../scripts/run_dex_l20.py) |
| JSON 标定与非线性联动 | [l20_calibrated.py](../scripts/l20_calibrated.py) |
| URDF/MJCF 与关键点构建 | [l20_model_source.py](../scripts/l20_model_source.py) |
| dex 目标函数修复 | [l20_consistent_optimizer.py](../scripts/l20_consistent_optimizer.py) |
| 离线平滑 | [smooth_l20_offline.py](../scripts/smooth_l20_offline.py) |
| 抖动诊断 | [audit_l20_jitter.py](../scripts/audit_l20_jitter.py) |
| 全长接触回放构建 | [build_l20_full_replay.py](../scripts/build_l20_full_replay.py) |
| 全长独立验证 | [verify_l20_full_replay.py](../scripts/verify_l20_full_replay.py) |
| 交互回放与视频导出 | [play_l20_full.py](../scripts/play_l20_full.py) |
| SPIDER 任务准备 | [prepare_spider_l20.py](../scripts/prepare_spider_l20.py) |
| SPIDER 官方入口封装 | [run_spider_l20_contact.py](../scripts/run_spider_l20_contact.py) |
## 3. 第一步:从 MANO 参数得到 21 点与腕部轨迹
输入 `world_results.npz` 包含 `pose_body`、`betas`、`root_orient`、`trans`、`is_right` 等字段。导出脚本要求恰好有一条整段一致的右手轨迹,否则断言失败。
对选定轨迹,按批次使用 Dyn-HaMR 自带 MANO wrapper 计算:
1. 将手指姿态展开为每帧 45 维 axis-angle 参数,使用该轨迹的固定手形 `betas`。
2. 根旋转和整体平移设为零,运行 MANO 前向运动学。
3. 所有点减去腕点,得到局部 21 点 `joints`,形状为 `(T,21,3)`。
4. 使用重建的根旋转、平移再次运行 MANO,取第 0 点作为 `wrist_world`。
5. 保存根旋转、原始平移、源轨迹编号和源文件路径,便于追溯。
世界腕点需要前向计算,因为模型的腕点定义包含手形等影响;不能直接用 MANO `trans` 代替。沿用原 wrapper 也避免额外叠加手部均值造成姿态约定变化。
当前脚本把帧率写为 30,输入视频恰好也是 30 fps。换视频时必须修改这一约定以及所有下游时间轴。
来源:[上游结果说明](../output/results_2047635068/README.md)、[导出脚本](../scripts/export_dynhamr_dex.py)。
## 4. 第二步:对齐掌面,重新构造机器人目标
### 4.1 为什么需要重新构造目标
人手与 L20 的骨长、指根位置、自由度不同。直接复制人手位置会要求机器人伸到无法到达的位置;直接复制 MANO 角度也没有一一对应的机械含义。
当前做法是保留手指骨段方向,使用机器人自身骨长和指根位置。
### 4.2 掌面坐标系
令腕点为 `p[0]`,食指、中指、小指掌指关节点为 `p[5]`、`p[9]`、`p[17]`:
- `z = normalize(p[9] - p[0])`;
- 从 `p[5] - p[17]` 中去掉沿 z 的分量,归一化得到 y;
- `x = cross(y,z)`;
- 组成基底矩阵 `B = [x,y,z]`。
机器人中立姿态得到 `B_robot`,每帧人手得到 `B_human`,代码计算:
```python
align = B_human @ B_robot.T
aligned = (human_points - human_points[0]) @ align
```
上式关键点采用行向量表达;解释旋转时不要将其与列向量乘法顺序混用。
### 4.3 骨段方向映射
目标先复制机器人中立姿态。五条链是 `(1,2,3,4)`、`(5,6,7,8)`、`(9,10,11,12)`、`(13,14,15,16)`、`(17,18,19,20)`。链起点保持机器人中立位置,随后逐节构造:
\[
p_b^{target}=p_a^{target}+L_{ab}^{robot}
\frac{p_b^{aligned}-p_a^{aligned}}{\|p_b^{aligned}-p_a^{aligned}\|}.
\]
所以代码使用的是逐骨段长度适配,不是一个整体缩放系数。腕点与指根位置基本固定在机器人掌面局部空间,手指方向随视频改变。
机器人侧建立 `landmark_00` 至 `landmark_20`:关节标记主要来自关节原点,指尖来自末端 STL 端部表面的近似点。报告中的指尖拟合误差依赖这些标记定义。
## 5. 第三步:有限位逆运动学与联动
### 5.1 PositionOptimizer 的求解对象
基础 L20 使用 dex-retargeting 0.5.0 的 position 模式,SLSQP 优化 16 个主动角,通过前向运动学匹配 21 个目标点。核心目标是:
\[
\min_{q_t}\ \mathrm{SmoothL1}(FK(q_t)-P_t^{target})
+\lambda\|q_t-q_{t-1}\|^2,
\quad q_{min}\le q_t\le q_{max}.
\]
这里 SmoothL1 按位置分量聚合;不是只匹配五个指尖的欧氏距离。`normal_delta=0.002`,dex 内置低通通过 `low_pass_alpha=-1` 关闭,后续由脚本显式平滑。以上目标表达的是修复后的实现。
初始姿态来自 JSON 基准命令,主动角先裁剪到有效范围,再通过联动适配器展开。后续顺序逐帧求解,利用前一帧状态。
### 5.2 JSON 非线性联动版本
提供资产位于 `third_party/l20_assets/L20/RIGHT/`,使用 `linkerhand_g20_right.urdf` 与 `g20_right_G20.json`。
标定解释为:
\[
q_j(u)=angle\_rad_j[u]+urdf\_zero\_offset\_rad_j.
\]
具体实现:
1. 按 `motor_index` 排序 16 个主动关节。
2. 每个被动关节找到共享电机编号的主动关节。
3. 遍历 0–255 命令,排除会使同电机任一关节超过 URDF 限位的值。
4. 检查有效命令区间连续,并形成主动角上下限。
5. 从同命令下的主动角、被动角建立分段线性插值曲线。
6. 前向计算时按曲线展开被动角;反向 Jacobian 使用当前插值段斜率。
生成的 URDF 去除原线性 mimic,生成的 MJCF 去除对应 equality,避免原线性关系和 JSON 关系重复生效。原始资产保留不变。
### 5.3 量化命令
求解后按主动角寻找最近的合法命令值,生成 20 槽位 `command_u8`。11–14 槽位保持 baseline;20 槽位不等于 20 个独立电机。命令再解码成全部关节角,检查量化误差。
修正版本报告中的最大量化角误差约 0.00604 rad。该值只是当前 LUT 下的往返误差,不是硬件执行误差。JSON 中还存在复用小指曲线的字段,不能描述成每根手指均独立实测。
### 5.4 后续线性联动版本
完整接触回放与 SPIDER 按既有需求使用 URDF 线性关系:
\[
q_{passive}=a q_{active}+b.
\]
它们从平滑轨迹读取主动角,再按线性关系重算被动角,同时收紧主动角范围以满足被动角限位。**它们并非重新从人手目标完整执行一次线性版 dex 拟合,也没有继续使用 JSON 被动角。**
因此基础 JSON 轨迹的拟合指标和命令文件,不能直接作为最终线性回放的拟合精度与硬件命令。
## 6. 第四步:保留腕部运动
每帧机器人腕部世界旋转由 `root_orient` 对应旋转矩阵乘以 `align` 得到。腕部世界位置使用 MANO 计算的 `wrist_world`。
展示时以首帧腕部旋转的逆作为固定场景旋转,将首帧腕部移至 `(0,0,0.2)`。这一变换只固定一次,整段运动仍然保留。保存原始世界腕点、世界旋转、场景旋转和平移,便于追溯。
四元数保存顺序是 WXYZ;SciPy 的默认顺序是 XYZW,读写需要显式转换。相邻四元数若点积小于零,则翻转符号,避免等价四元数符号跳变。
基础回放使用 freejoint;SPIDER 和最终全长场景采用三个平移、三个旋转标量坐标。其 CSV/NPZ 布局与基础手部文件不同,不可按列号直接混读。
这里没有机械臂模型、机械臂 IK 或机械臂动力学,腕部运动不能直接转换成真实机械臂执行轨迹。
## 7. 第五步:抖动诊断与离线平滑
基础脚本先对主动角做 `sigma=1` 帧高斯平滑,再重算被动角;腕部最初直接沿用重建结果。
后续从修复版基础输出出发,再对主动角做 `sigma=2.5` 帧离线高斯平滑。被动角仍由主动角重新计算,不能独立滤波破坏联动。腕部位置同步平滑;旋转在相对首帧的旋转向量空间处理,脚本断言该片段旋转范数小于 π/2。
| 版本 | 最大关节帧间变化 | 关节二阶差分 RMS | 平均指尖拟合误差 |
|---|---:|---:|---:|
| 原基础 L20 | 4.139° | 0.12148° | 10.675 mm |
| 仅修复目标函数 | 2.418° | 0.11907° | 10.680 mm |
| 修复 + 离线平滑 | 1.529° | 0.03146° | 10.701 mm |
| 旧接触修正 | 54.375° | 1.67298° | 目标改变,不直接比较 |
平滑版本相对原基础关节二阶差分下降约 74.1%;腕部位置二阶差分分量 RMS 降至 0.114 mm,但腕点相对原轨迹最大改变 11.92 mm。
二阶差分包含真实动作加速度,不能把它直接叫作测得的噪声。高斯平滑使用未来帧,当前实现属于离线处理;输出不是零延迟实时控制器。
证据:[抖动审查](../output/l20_2047635068/jitter_audit/README.md)、[平滑验证](../output/l20_2047635068/jitter_audit/temporal/validation.json)。
## 8. 第六步:完整视频接触配准
### 8.1 物体与位姿假设
当前使用参数化瓶子,高 157.856 mm、最大直径 54.110 mm;瓶肩与瓶颈是规则设计形状。它替换了早期轮廓异常的瓶子模型。
没有当前视频逐帧物体位姿,也缺少原三目相机到 VIPE 世界坐标的外参。当前构造固定的瓶子到手掌相对变换:平移 `[0.12,-0.065,0.15]` 米,绕 x 轴旋转 `-π/2`。再使用第 600 帧做场景整体对齐。
第 130–160 帧被设为估计抓取过渡,使用平滑权重启用修正。160 帧前瓶子目标固定,后续随估计手掌关系运动。这是人为配准假设,不是视频物体跟踪。
### 8.2 求解与连续性
每个关键帧优化 22 个修正量:16 个主动角、3 个腕部平移、3 个腕部旋转。
残差包含指末端表面距离、全手对物体穿透惩罚、姿态偏移正则和相邻关键帧修正量差异。当前全长脚本的主要尺度为:
| 项 | 代码含义 |
|---|---|
| 指末端距离 | `(distance + 0.0002) / 0.004`,目标轻微压入约 0.2 mm |
| 穿透惩罚 | 超过约 0.7 mm 的穿透进入惩罚,以 1 mm 归一化 |
| 手指连续性 | 与上一求解修正量的差异进入残差 |
| 腕部修正边界 | 平移各轴 ±65 mm,旋转向量各分量 ±0.55 rad |
| 求解频率 | 从第 160 帧开始,每两帧一个关键帧,补入末帧 |
| 求解器 | SciPy `least_squares`,每关键帧最多 65 次函数评估 |
| 后处理 | 插值修正量,`sigma=1.5` 帧高斯平滑,再乘过渡权重 |
前一关键帧解作为下一次初值。插值和平滑后再次展开关节、检查限位与接触。该流程有连续性代价,但不是带硬速度/加速度约束的全局轨迹最优化。
### 8.3 回放与检查
输出场景移除驱动器,由播放器每帧赋值 `qpos` 后调用 `mj_forward`。瓶子位姿同样由文件指定;场景地板只提供参照,起始瓶子没有物理支撑。
保存报告显示:1333 帧有限,限位违规为零,线性联动误差为零,主动角最大帧间变化 1.465°;持瓶阶段 1173 帧中 1172 帧存在手瓶接触。
独立 `mj_forward` 接触流形检查最大穿透约 1.008 mm。距离查询保存了约 32.018 mm 的孤立尖峰,与接触流形不一致,应保留两者并说明差异,不能只挑有利数字,也不能据此单独确认同等深度的真实几何穿透。
来源:[完整回放说明](../output/l20_full_replay/README.md)、[验证报告](../output/l20_full_replay/validation.json)。
## 9. 第七步:SPIDER 两秒动力学实验
准备阶段使用第 600–659 帧平滑轨迹、URDF 线性联动和参数化瓶子。初始帧通过多初值 least-squares 配准,并将同一初始修正施加到整个片段,避免逐帧独立接触修补。以五个末端最近表面点建立接触标记和物体参考点。
参考插值到 2.5 ms,生成 801 个参考状态,执行 800 步。腕部和手指有位置驱动器,瓶子有搜索用虚拟驱动器。
| 配置 | 保存值 |
|---|---:|
| 后端 | MuJoCo Warp GPU |
| 仿真步长 | 0.0025 s |
| 控制更新周期 | 0.1 s |
| 预测时长 | 0.6 s |
| 样本数 | 512 |
| 每次最大迭代数 | 6 |
| 指关节力矩限制 | ±1 Nm |
| 腕平移力限制 | ±200 N |
| 腕旋转力矩限制 | ±20 Nm |
| 瓶子质量 / 摩擦系数 | 0.2308 kg / 0.6,未实测 |
运行封装调用官方 `examples/run_mjwp.py`。快速入口不支持 `contact_guidance`,所以使用标准入口。搜索阶段物体虚拟辅助增益随迭代衰减,最后清零;独立执行回放的物体增益与偏置为零,没有焊接或每步重设瓶子位姿。
保持判据:全程手—物体相对位置相对参考偏差小于 20 mm,且末帧仍有接触。
| 测试 | 平均瓶子位置误差 | 接触持续时间 | 结果 |
|---|---:|---:|---|
| 原控制 GPU 基线 | 266.90 mm | 0.035 s | 落地 |
| SPIDER GPU 优化 | 17.76 mm | 2.000 s | 保持接触,门限未通过 |
| 独立 GPU 控制回放 | 20.14 mm | 2.000 s | 保持接触,门限未通过 |
| 独立 CPU 控制回放 | 75.97 mm | 1.3675 s | 滑脱落地 |
SPIDER GPU 最大手—物体相对位置偏差为 76.64 mm,最大穿透为 3.31 mm。日志有 64 次非有限候选回报警告;最终执行轨迹有限不等于全部候选采样数值稳定。
独立 GPU 回放从新模型与初始状态出发,复用控制输入而不调用优化器,仍保持接触。这说明保持现象不只存在于优化器内部状态,但 CPU 差异说明接触与控制仍然敏感。
来源:[SPIDER 说明](../output/spider_l20_contact/README.md)、[配置](../output/spider_l20_contact/config.json)、[验证](../output/spider_l20_contact/validation.json)。
## 10. 踩过的坑:现象、证据与处理
### 10.1 环境冲突与误用启动脚本
现有重定向运行约定清空 `PYTHONPATH`、`LD_LIBRARY_PATH`,避免外部 ROS 路径与 Pinocchio 动态库造成冲突。重建导出使用 `.dynhamr`,dex 使用 `.dex`,SPIDER 使用 `.spider`;不要默认它们可以互换。
`run_dex_2047635068.sh` 实际运行的是 **LEAP** 分支。重新跑 L20 应显式调用 `run_dex_l20.py`。该差异能直接在脚本中确认。
### 10.2 将模型名字、关节数量和命令槽位混为一谈
目录名 L20、JSON `model=G20`、URDF 内部历史名称并不完全一致。实现按文件关节名工作,尚未硬件确认具体型号。21 个模型关节、16 个主动控制量、20 个命令槽位是不同概念。
### 10.3 重复施加联动约束
JSON 非线性 LUT 与 URDF 线性 mimic 不相同。基础版移除生成文件中的线性约束,由 adaptor 接管。后续切换线性版时重算被动角,不能同时保留非线性被动角并期待线性约束自动一致。
### 10.4 坐标正确不等于精度可靠
MANO wrapper、腕点定义、行列向量乘法、WXYZ/XYZW 都需要显式保持一致。已有 Pinocchio/MuJoCo FK 检查约达 1e-13 m,一致性很好,但它只支持实现约定相符,不能证明单目重建正确。
已有坐标诊断未发现该输入掌面轴逐帧翻转,不能未经检查就把所有抖动归因于坐标翻转或角度单位错误。
### 10.5 优化器目标值与梯度不一致
安装的 PositionOptimizer 返回值缺少时间正则,梯度却包含它。修复补回 `norm_delta * sum((x-last_qpos)**2)`,有限差分最大误差从约 1.20e-4 降至 1.36e-13。
但同目标对照中全段关节二阶差分只改善约 1.99%,排除接触前阶段后还略升。它是确定的实现缺陷,不能当作大幅抖动的充分解释。
### 10.6 单帧接触修补引入剧烈跳解
旧版每八帧独立优化,插值后再修穿透帧,缺少邻帧连续性代价。第 460→461 帧源 L20 最大变化约 0.270°,旧接触修正后无名指 PIP 跳变 54.375°,下一帧反跳 51.614°。
旧方案将全段穿透压到约 1.499 mm,但轨迹仍不连续。后续全长方案使用连续热启动、每两帧求解、邻帧修正正则及平滑插值。小穿透与小关节步长必须分开验证。
### 10.7 原瓶子网格与碰撞轮廓问题
早期提供的 OBJ 封口存在越界、退化三角形,仅在项目副本修复索引和朝向,保留 6146 个顶点,修复后 12288 个三角形。原始目录未修改。
早期碰撞用十段圆柱,半径存在突然缩小又扩大的台阶;视觉网格和碰撞近似也有差异。最近碰撞片段切换可能加重优化不连续,但未通过完整物理 A/B 实验量化其独立贡献。后续使用参数化瓶子,不将理想化瓶肩瓶颈宣称为实测形状。
### 10.8 几何贴合误当成物理抓稳
早期球体接口与旧瓶子实验均出现几何接触收敛但自由物体滑脱。旧瓶子三秒测试末尾相对位移约 17.56 m,与滑落后持续自由落体有关。即使初始静态接触力分配残差很小,也未保证动态保持。
处理原则是分别报告目标点误差、表面间距、穿透、自由物体保持和控制回放结果。`mj_forward` 回放视频无法替代 `mj_step` 动力学检查。
### 10.9 平均误差下降误当成门限通过
SPIDER 的平均位置误差从约 267 mm 降到约 18 mm,但保持判据使用全程最大相对偏差和末帧接触。平均值改善不等于全程达到 20 mm 门限。
### 10.10 GPU 与 CPU 回放不一致
初始配准改善、仿真步长从 5 ms 降到 2.5 ms 后,CPU/GPU 复核更接近,但最终 CPU 仍滑脱。只能确认后端或接触数值敏感,尚未独立定位唯一原因。下一步应先改进接触与执行器稳定性,而非只延长视频。
### 10.11 距离查询尖峰与接触流形不一致
全长结果中 `mj_geomDistance` 出现约 32 mm 的孤立穿透量,而独立 `mj_forward` 接触流形最大约 1 mm。保存原始查询和独立验证,明确报告口径;不要静默删尖峰或直接把查询异常等同真实穿模。
### 10.12 有限输出不能抹掉采样警告
SPIDER 保存轨迹有限,日志仍有非有限候选。验证需同时记录完成标志、最终数组有限性、候选警告和独立回放,不能只看进程完成。
### 10.13 离线平滑和硬编码限制
当前平滑使用未来帧并改变轨迹;帧率 30、源片段 600–659、抓取区间 130–160、右手选择和瓶子相对位姿均有特定输入假设。换视频不能仅替换一个文件名。
### 10.14 旧报告与新脚本可能属于不同版本
原 `l20_2047635068/retarget_validation.json` 保存的是历史运行,当前脚本默认已开启目标函数修复。比较修复效果应使用 `jitter_audit/corrected/` 的报告。旧阶段 README 中“尚未执行”的语句可能只描述当时阶段;当前状态应结合下游独立目录及其验证报告读取。
## 11. 输出格式与读取注意事项
| 文件 | 内容与约定 |
|---|---|
| `human_joints.npz` | `(T,21,3)` 局部人手点,米;另含世界腕点与根姿态 |
| 基础 `motion.npz` | 21 关节角、16 主动角、命令、目标点、实际点、腕位姿与场景变换 |
| 基础 `trajectory.csv` | 秒、腕部 XYZ、WXYZ 四元数、按关节名排列的角度,rad |
| 基础 `commands_u8.csv` | 帧号及 20 命令槽位,仅对应生成该文件的 JSON 版本 |
| 平滑 `temporal/motion.npz` | 平滑后的主字段与部分继承字段;`qpos_raw` 等仍可能是上游历史数据 |
| SPIDER `optimized_trajectory.csv` | 时间、33 个标量位置坐标、28 个控制输入;不等于硬件命令 |
| 全长 `motion.npz` | 对应 `scene.xml` 的完整 qpos,含手腕、手指和瓶子坐标 |
| 全长 `scene.xml` | 场景与资产引用;单独打开只显示模型,需要 NPZ 才能播放 |
读取时以各文件字段、`joint_names` 或模型关节顺序为准。平滑脚本会更新 NPZ 内命令数组,但没有另外写出平滑版 `commands_u8.csv`,不要误用基础目录旧 CSV。
## 12. 复现步骤
以下命令依据现有脚本和保存的运行说明整理,本次未重跑。它们会重新生成对应输出。工作目录固定为 `/home/timessage/HandFlow`,并假定模型、环境与上游重建已存在。
### 12.1 基础导出、重定向、平滑
```bash
cd /home/timessage/HandFlow
env PYTHONPATH= LD_LIBRARY_PATH= \
third_party/Dyn-HaMR/.dynhamr/bin/python scripts/export_dynhamr_dex.py
env PYTHONPATH= LD_LIBRARY_PATH= .dex/bin/python \
scripts/run_dex_l20.py --consistent-objective \
--output-dir output/l20_2047635068/jitter_audit/corrected
env PYTHONPATH= LD_LIBRARY_PATH= \
.dex/bin/python scripts/smooth_l20_offline.py
```
### 12.2 准备瓶子场景与初始配准
参数化瓶子已存在于 `third_party/bottle_model_parametric/`。若需要重新生成,先核对并运行 `scripts/build_parametric_bottle.py`。随后:
```bash
env PYTHONPATH= LD_LIBRARY_PATH= \
.spider/bin/python scripts/prepare_spider_l20.py
```
该步骤生成全长回放依赖的 `scene_act.xml` 和 `trajectory_kinematic_act.npz`,同时准备 SPIDER 两秒任务。
### 12.3 全长运动学回放
```bash
env PYTHONPATH= LD_LIBRARY_PATH= MUJOCO_GL=osmesa \
.spider/bin/python scripts/build_l20_full_replay.py
env PYTHONPATH= LD_LIBRARY_PATH= MUJOCO_GL=osmesa \
.spider/bin/python scripts/verify_l20_full_replay.py
env PYTHONPATH= LD_LIBRARY_PATH= MUJOCO_GL=osmesa \
.spider/bin/python scripts/play_l20_full.py --render
```
构建脚本的初始报告使用距离查询;独立验证脚本会补充接触流形检查。所以生成后应执行验证,不应把构建结束的初始 JSON 当成最终验证报告。
### 12.4 SPIDER 优化与独立复核
在可访问 GPU 的环境中运行:
```bash
env PYTHONPATH= LD_LIBRARY_PATH= \
.spider/bin/python -u scripts/run_spider_l20_contact.py
env PYTHONPATH= LD_LIBRARY_PATH= \
.spider/bin/python scripts/replay_spider_l20_gpu.py
env PYTHONPATH= LD_LIBRARY_PATH= \
.spider/bin/python scripts/evaluate_spider_l20.py
```
优化完成应结合 `SPIDER_RUN_COMPLETE`、输出数组、验证报告与独立控制回放确认,不能只看日志正在运行。
### 12.5 直接查看已有成品
```bash
/home/timessage/HandFlow/output/l20_full_replay/replay.sh
```
空格暂停/继续,左右方向键逐帧,R 重播,鼠标调整视角。
- [完整视频](../output/l20_full_replay/full_replay.mp4)
- [完整回放说明](../output/l20_full_replay/README.md)
- [平滑基础 CSV](../output/l20_2047635068/jitter_audit/temporal/trajectory.csv)
- [SPIDER 对照视频](../output/spider_l20_contact/spider_contact_comparison.mp4)
## 13. 换视频或换机器人时需要改什么
| 修改对象 | 必须检查的内容 |
|---|---|
| 输入视频 | 帧率、长度、左右手选择、重建文件路径、单位与坐标系 |
| 手模型 | URDF 关节名、限位、指尖标记、掌面轴、主动/被动关系 |
| 标定表 | 电机槽位、零位偏置、有效命令范围、曲线是否单调及是否实测 |
| 联动模式 | 明确采用 JSON 还是 URDF,生成与验证使用同一模式 |
| 物体 | 几何尺寸、视觉/碰撞一致性、物体位姿来源、质量与摩擦 |
| 接触时段 | 当前 130–160 与 600–659 帧假设不能直接沿用 |
| 动态执行 | 执行器力矩/速度能力、控制频率、接触稳定性及独立回放 |
当前代码没有覆盖任意输入:退化骨段可能导致方向归一化失败;右手轨迹选择有严格断言;短视频无法使用第 600 帧初始化;较大旋转可能触发平滑断言。新增输入应先做小段数据与模型检查,再运行全段。
## 14. 后续工作的验证顺序
以下为建议,尚未完成:
1. 对齐真实型号、联动与标定解释,确定最终采用哪一种控制模型。
2. 获取或核验物体逐帧位姿与坐标外参,降低手掌关系假设的影响。
3. 在短片段中定位接触布局、碰撞几何、执行器与 CPU/GPU 差异,先通过无辅助物体驱动的独立回放保持门限。
4. 增加明确的关节速度、加速度与控制平滑约束,单独报告接触代价和动作偏离。
5. 短片段通过后,再扩展接近、闭合、持握、抬升以及全长动力学实验。
不能用延长运动学视频、增大采样预算或降低平均误差,替代上述稳定性验证。
## 15. 主要证据索引
- [Dyn-HaMR 上游结果](../output/results_2047635068/README.md)
- [LEAP 基线与环境约定](../output/dex_2047635068/README.md)
- [L20 基础实现说明](../output/l20_2047635068/README.md)
- [修正后的基础验证](../output/l20_2047635068/jitter_audit/corrected/retarget_validation.json)
- [抖动诊断与消融](../output/l20_2047635068/jitter_audit/README.md)
- [离线平滑验证](../output/l20_2047635068/jitter_audit/temporal/validation.json)
- [旧瓶子网格修复、接触跳变与物理失败](../output/l20_2047635068/bottle_grasp/README.md)
- [全长回放最终验证](../output/l20_full_replay/validation.json)
- [SPIDER 配准与运行边界](../output/spider_l20_contact/README.md)
- [SPIDER 最终验证](../output/spider_l20_contact/validation.json)
本文件汇总的是仓库中第三方组件与本地集成脚本的行为和已有证据,不用于推断个人贡献归属。