9. 质检与三联校验¶
质检分两部分:自动检查在投影前后运行,查左右是否装反、tracker 是否漂移、手套配对是否正确,结果汇总到总览页;三联校验是人工逐条查看,把同一时刻的视频、SMPL-X 和手部触觉放在一起比对。
本章的命令都在工具包根目录下执行,沿用第 7.3 节的变量。检查结果统一写到 $WORK/out/gallery/,总览页会读取它们。
9.1 自动检查¶
以下检查在投影之前运行,只读原始录制。其中两项需要标定,所以按 batch 分别运行:
G=$WORK/out/gallery; mkdir -p $G
B=<batch>; P=$WORK/profiles/$B.json
python scripts/check_tracker_lr.py --sync-root $WORK/ego_data_sync --datasets $B --profile $P --output $G/tracker_lr.$B.json
python scripts/check_startup_stance.py --sync-root $WORK/ego_data_sync --datasets $B --profile $P --output $G/startup_stance.$B.json
python scripts/check_glove_sync.py --sync-root $WORK/ego_data_sync --datasets $B --output $G/glove_sync.$B.json
python scripts/check_hand_identity.py --sync-root $WORK/ego_data_sync --datasets $B --output $G/hand_identity.$B.json
有多个 batch 时,把各自的 tracker_lr.<batch>.json 合并成一个 tracker_lr.json,glove_sync 同理。这些文件都是 JSON 列表,直接拼接即可。
漂移检查要用投影残差,在投影之后运行:
python scripts/check_tracker_drift.py --sync-root $WORK/input/ego_data_sync \
--projected $WORK/out/projected --output $G/tracker_drift.json
| 检查 | 脚本 | 判据 |
|---|---|---|
| tracker 左右装反 | check_tracker_lr.py |
用头部安装参数求出佩戴者的左方向,再看每对 tracker 的"左减右"在这个方向上的投影。正常情况下全程为正。用开头的站姿判断:双脚一直靠得很近时,"四肢分得最开的那几帧"并不可靠 |
| tracker 漂移、脱落 | check_tracker_drift.py |
几分钟内身体可以看作刚体:头到手腕、脚踝的距离随动作起伏,但不会持续变长或变短。给出两个量:这段距离的线性趋势 trend_m,以及该 tracker 的残差从前五分之一到后五分之一的增长 residual_growth_mm |
| 手套配对与时序 | check_glove_sync.py |
MANUS 手套的根节点带朝向,把它的角速度和手腕 tracker 的角速度做互相关:峰值处的延迟就是时序偏差,交叉配对的相关性更高就说明左右反了。先重采样到 30 Hz 再差分,角速度截断到 20 rad/s 以排除坏帧。Wuji 手套的根节点没有朝向,结果记为 unsupported |
| 手套手性 | check_hand_identity.py |
根据骨架本身的手性逐帧判断每路数据来自哪只手,可以直接确定 slot |
| 起始站姿 | check_startup_stance.py |
检查开头几秒的几何关系是否合理,例如两个手腕相距好几米说明有漂移 |
| 拟合残差 | tracker_diagnostics.npz |
每个 tracker 残差的中位数超过 15 mm 记为偏高,超过 60 mm 记为异常;整体 75% 分位只有 4–12 mm |
判读时注意:
- 一动不动的 tracker 会被误判成左右装反(两个固定位姿相减也是一个固定向量),先确认它在动。
- 手套的判断是相对于手腕 tracker 的。如果手腕 tracker 本身装反了,手套的结论要反过来:实际是否交换 = 手套判为交换 XOR 手腕 tracker 判为交换。
9.2 修正¶
python scripts/fix_tracker_lr.py --report $G/tracker_lr.json \
--sync-root $WORK/ego_data_sync --output-root $WORK/ego_data_lr_fixed
python scripts/fix_glove_lr.py --report $G/glove_sync.json --tracker-report $G/tracker_lr.json \
--sync-root $WORK/ego_data_sync --output-root $WORK/ego_data_lr_fixed
- 两个脚本都支持
--dry-run,先列出会改哪些录制。 - 修正结果写到平行目录
$WORK/ego_data_lr_fixed/<batch>/<录制名>/,原始数据不会被修改。没改的文件用链接,修正副本可以直接作为输入。 - tracker 修正交换左右两路 tracker,并在 npz 里写入
tracker_lr_correction;手套修正只改写session.json,交换两只手套的slot,并在glove_lr_correction中记录依据。 - 修正后按第 7.5 节建立合并输入,投影和手部融合都从合并输入读取。如果之前已经投影过,删掉被修正录制在
references/、proposals/、projected/下的结果再重跑。
9.3 三联校验页¶
每条录制生成一个可以单独打开的 <slug>.review.html。slug 取 session_<日期>_<时间>,不含中文任务名,因为 Chrome 不加载文件名带中文的媒体文件;名字冲突时加后缀区分。run_pipeline.sh review 会自动完成以下三步:
R=$WORK/out/review; mkdir -p $R
for npz in $WORK/out/unified_30hz/*.npz $WORK/out/unified_30hz_nobody/*.npz; do
name=$(basename "$npz" .npz)
slug=$(printf '%s' "$name" | sed -E 's/^(session_[0-9]+_[0-9]+).*/\1/')
mp4=$(ls $WORK/ego_data_sync/*/"$name"/"$name".mp4 2>/dev/null | head -1)
# 1. 生成 360p、15 fps 的视频副本,用于页面内播放
[ -n "$mp4" ] && [ ! -f $R/$slug.360.mp4 ] && ffmpeg -y -v error -i "$mp4" -vf "scale=-2:360" -r 15 \
-c:v libx264 -preset veryfast -crf 32 -an -movflags +faststart $R/$slug.360.mp4
# 2. 生成数据(抽帧、压缩后的网格)
python scripts/build_review_payload.py --unified "$npz" --output-dir $R --body-model-path models/smplx --max-frames 300
[ "$name" != "$slug" ] && mv -f $R/$name.payload.json $R/$slug.payload.json && mv -f $R/$name.mesh.i8c.gz $R/$slug.mesh.i8c.gz
# 3. 生成页面
v=(); [ -f $R/$slug.360.mp4 ] && v=(--video)
python scripts/make_review_html.py $R/$slug.payload.json --slug $slug "${v[@]}"
done

在一台机器上用工具包处理一条物料称量录制得到的校验页。左上是 GoPro 视频,右上是带手指的 SMPL-X,下方是左右手 17 个区域的接触(黄色为接触)和区域随时间的接触条带。页面顶部注明了触觉阈值和抽帧情况。
页面的三栏都来自同一个 30 Hz 导出,所以拖动滑块时,视频帧、网格姿态和触觉读数天然是同一时刻,页面不需要自己去对齐:
- 第一视角视频(360p 副本);
- SMPL-X 全身网格,加上五个 tracker。每帧 10,475 个顶点,默认均匀抽取不超过 300 帧,压缩后直接写在页面里;
- 双手与触觉:手部骨架和 17 个区域的接触,接触的区域高亮并列出名称。
投影失败的录制也会生成页面(来自 unified_30hz_nobody/),只有视频和触觉,网格一栏写明失败原因。
说明
页面的数据直接写在 HTML 里(const P = …),修改旁边的 .payload.json 不会生效,要重新生成页面。网格最多 300 帧,长录制在页面上只有 0.6–2 Hz,看起来卡顿是抽帧造成的,不是编码问题。需要全帧率时直接读 unified_30hz 的 npz。
9.4 总览页¶
python scripts/build_ego_gallery.py --batch-root $WORK/out --output-dir $WORK/out/gallery \
--sync-root $WORK/input/ego_data_sync --body-model-path models/smplx --device cpu --title "EgoExplorer review"
gallery/index.html 是单个文件,双击即可打开:
- 每条录制一张卡片,包括三个固定尺寸画框(宽 1.5 m、高 2.25 m,地面线位置不变)里的骨架投影和五个 tracker 实测点、残差曲线、根节点轨迹缩略图和统计量。画框故意不自动缩放,这样"人沉到地面以下""尺度异常"这类问题一眼就能看出来;
- 可以按残差、旋转、位移排序,按任意一项检查结果筛选;失败的录制也会列出原因;
- 每张卡片链接到对应的三联校验页(默认在
../review/)。
9.5 人工复核要点¶
| 看什么 | 正常情况 | 不正常时先查 |
|---|---|---|
| 身体与视频是否同步 | 转身、伸手、下蹲与视频同时发生 | 时间戳换算、video_time_offset_s |
| 左右 | 视频里用右手拿东西,网格上也是右手 | tracker 或手套左右(9.1) |
| 脚 | 支撑脚不穿地、不悬空、不滑动 | 地面估计、L3 权重、躯干前倾上限 |
| 弯腰、深蹲、坐下 | 躯干确实前倾,而不是整个人往下沉 | 躯干前倾上限是不是还停在 15° |
| 手指 | 朝掌心弯曲,没有反折 | 手套左右、左手的镜像方向 |
| 触觉 | 接触区域和时刻与视频里手碰到物体的位置、时间一致 | 手套左右、手套健康表 |
| 残差曲线 | 平稳,在几毫米到一两厘米之间 | 绑带滑动、tracker 脱落、用错标定 |
复核结果(通过、有问题、需重录)建议按录制名记在一张表里,之后挑选发布数据时直接用。