跳转至

Phys Refine 重设计:面向 Noisy Recon 的 Contact Latent Inference

日期:2026-04-21

Historical/provisional design record

本页保存 2026-04 的 formulation 与 implementation history,不是当前论文实验合同。下面的 roadmap 与旧 validation 路径仅供追溯;当前实现状态、正式指标和 No-Go gate 分别以 Project Guide、 Physics metric reviewPaper Readiness 为准。

0. 一页结论

当前 phys refine 已经借用了 Grasp-and-Lift 的一部分骨架:

  • 用低维 hand control 驱动 MuJoCo rollout
  • 物体运动由接触和动力学自然演化
  • 用滑窗 CMA-ES 优化 hand control
  • 用 object-local contact target 约束 fingertip

但它仍然保留了两个对我们最不合适的前提:

  1. contact frames 仍然主要由 preprocess / recon hand 推断。
  2. contact anchor 仍然被过早压缩成单个 contact body / link

这两个前提在 mocap 或高质量 hand trajectory 场景里可以成立,但在我们的 monocular recon 里通常不成立。结果就是:

  • 错误的 hand recon 会变成物理优化的强先验;
  • 优化器会优先去解释错误的接触时段;
  • 优化器会优先去解释错误的接触部位;
  • 双手或多段接触会被不必要地简化;
  • 物理优化更像在“修 recon 误差”,而不是在“推断能解释 object motion 的 contact”。

因此,新的默认原则应该是:

recon hand 只做 initialization,不做 contact truth。

更准确地说,我们不该再“先 hard infer contact,再做 phys refine”;而应该:

  • 直接用 object motion 给出粗的 interaction / contact windows
  • 再让 physics rollout 在这些窗口里自动发现更细的 hand / link / anchor assignment

也就是说,真正需要作为 latent variable 的,不是“物体到底有没有 interaction”,而是 interaction 已经发生之后的 finer contact structure。


1. 背景与问题定义

Grasp-and-Lift: Executable 3D Hand-Object Interaction Reconstruction via Physics-in-the-Loop Optimization 的前提之一,是它使用的 visual / mocap hand trajectory 足够准,因此可以把 hand trajectory 当成较强线索。

我们的设置不同:

  • 输入是 monocular video;
  • hand 区域常常很小;
  • 遮挡最严重的时段恰恰是 contact 时段;
  • 左右手可能都参与;
  • recon hand 在 wrist、fingertip、左右手判定上都可能不稳定。

所以我们不能把论文里“视觉手轨迹比较可信”的假设直接平移过来。

我们要保留的是论文的优化骨架:

  • hand-driven physics rollout
  • low-dimensional control
  • object-local contact target
  • sliding-window optimization

但必须改掉的是:

  • 用 recon hand 决定 contact frames
  • 用单个 contact body 决定所有 contact anchor

2. 当前实现中最脆弱的两处

2.1 Contact frames 由 recon hand 推断

当前 pipeline/physics/refine/data_loader.py 中的 _load_recon_contact_supervision(...) 会:

  • 读取 preprocess 阶段导出的 hand_points_cam
  • 调用 infer_hand_contact_labels(...)
  • 生成 left_contact_frames / right_contact_frames
  • 汇总成 contact_framescontact_windows

这意味着当前优化默认相信:

  • recon hand 的 3D 点是可信的
  • recon hand 到物体的关系足以决定 contact timing
  • 哪只手在接触是可以先验判定的

对我们的数据,这三个前提都太强。

2.2 Contact anchor 被压成单个 contact body

当前 pipeline/physics/refine/data_loader.py 中的 _resolve_contact_point_spec(...) 会先选一个 contact_spec

  • 若是 D3DHOI,优先用标注的 contact_vertices
  • 否则退化成某个 link 的顶点中心

后续 pipeline/physics/grasp_and_lift_utils.py 中的 build_contact_targets_local(...) 再在这个单一 link 上给 fingertip 找最近邻。

这会导致两个问题:

  1. 接触区域被过早压缩,信息损失太大。
  2. 一旦 link 选错,后面整个目标函数都会被带偏。

3. 为什么这在我们的数据上不成立

3.1 论文里的“手”更接近观测

在论文设定里,hand trajectory 是较强输入,因此用它来:

  • 初始化 wrist
  • 约束 fingertip
  • 辅助决定 contact region

是合理的。

3.2 我们这里 object motion 往往比 hand recon 更可靠

对当前数据分布,更稳的量通常是:

  • object articulation trajectory
  • 支撑关系与 support-aware state
  • root 是否固定
  • 物体在接触前后是否运动

相比之下,更不稳的量通常是:

  • fingertip 位置
  • 左右手 handedness
  • 某一帧是否真的接触
  • 接触时究竟是哪根手指碰到了哪块 link

所以推断方向应该反过来:

  • 先从 object motion 直接读出 coarse interaction timing
  • 再让 physics rollout 自动发现 finer contact assignment
  • 最后才用 recon hand 做弱约束

而不是:

  • 先用 recon hand 定时段
  • 再用 recon hand 定 anchor
  • 最后让 physics 去解释这些先验

4. 新的 formulation

4.1 总原则

新的 phys refine 不应该再把问题写成“先预测 contact,再优化 hand”。

更合理的写法是:

  • object motion 直接给出 coarse interaction timing
  • contact hand 是 latent hand assignment
  • contact body / link 是 latent surface assignment
  • contact local point 是 latent anchor

也就是说,timing 在粗粒度上不需要再由 hand recon 去推断;真正需要自动发现的是窗口内部的 finer contact structure。

只有 object motion、support prior、以及允许参与优化的手集合是相对更稳定的输入。

4.2 Hand 只做 weak cue

recon hand 的职责应该降级为:

  • 初始化 wrist trajectory
  • 初始化低维 MANO / PCA keyframe
  • 提供 joint confidence
  • 提供 optional 的 per-hand weak cue

recon hand 不再负责:

  • 判定真正的 contact frame
  • 判定真正的 active hand
  • 判定唯一的 contact body
  • 生成硬的 fingertip anchor truth

4.3 Interaction window 直接由 object motion 给出

对于 shared / fixed object

  • 主要看 articulation joint 的位移、速度、加速度
  • 结合 support-aware state 变化
  • 直接得到宽松的 interaction / contact windows

对于 free object

  • 在上面基础上,再加 root 的线速度和角速度
  • object_rigid_pose_mode == free 时,object root 通过 freejoint 真正参与 physics rollout,并额外跟踪 root pose / rotation / velocity

这里的核心判断是:

  • 只要物体开始发生由人操作引起的 motion change,就说明附近一定存在某种 interaction

因此我们不必再用 noisy recon hand 去硬判每一帧 contact = 0/1。更合理的是:

  • 用 object motion 给出粗窗口
  • 把窗口内部的 finer contact 留给 physics 自动发现

新的目标不是一个 contact_body_id,而应该是:

  • 每帧
  • 每只手
  • 每个 fingertip
  • 对应一个可选的 target_body_id
  • 一个 target_local_point
  • 一个 target_weight / confidence

也就是说,接触目标应从:

  • 单一 contact body
  • 单一 link local vertices
  • 单一 hard valid mask

改成:

  • multi-link candidate surfaces
  • per-tip per-frame local target
  • soft assignment / confidence

4.5 “让物理自动发现 contact” 的正确边界

可以让物理自动发现 contact,但这里说的“自动发现”不应理解成:

  • 从整段序列
  • 不加任何时间约束
  • 不加任何 surface candidate
  • 完全盲搜出所有接触

这在工程上通常过于困难,因为优化器要同时搜索:

  • 哪些时间段发生 interaction
  • 哪只手参与
  • 哪根 finger / palm 参与
  • 接触哪个 link
  • 接触哪个局部 anchor
  • 以及 hand control 本身

更合理的做法是:

  • object motion 先把搜索收缩到 coarse windows
  • 用 candidate surfaces 把空间搜索约束到可操作区域
  • 再让 physics 在这些约束内自动形成和更新 finer contact assignment

所以这里的关键词不是“infer hard contact”,而是:

  • motion-conditioned automatic contact discovery

5. 具体改法

5.1 先给 coarse windows,不做 hard per-frame contact label

新的 contact timing 推断器应优先吃:

  • object_joint_values
  • object_joint_velocity
  • object_root_pos / quat 与速度
  • support_spec
  • object_rigid_pose_mode

它的输出不应该再是:

  • active_hand = left/right
  • 单一 contact_frames

而应该是:

  • allowed_hands
  • global_contact_windows
  • contact_confidence[t, left/right]
  • 可选的 contact_windows_by_hand

如果实现上想更激进一点,甚至可以先不输出逐帧 contact_confidence,只输出:

  • global_contact_windows
  • allowed_hands

把更细的 per-frame / per-hand contact 留给优化过程去自动发现。

这里 hand_contact_mode 的新语义应该是:

  • left/right/both/none 只表示哪些手允许参与优化
  • 不再表示系统已经知道“真正接触的是哪只手”

5.2 Contact surface:保留候选集合,不要先求中心点

如果数据集有显式 contact_vertices

  • 直接保留顶点集合作为 candidate surface

如果没有显式标注:

  • 把所有 active / manipulable link 顶点都保留下来
  • 允许后续 assignment 在这些 link 之间切换

不要再做:

  • 先挑一个 link
  • 再把其顶点平均成一个 body_local_point

因为这一步会把“接触的是哪块区域”这个问题过早做成硬决策。

5.3 Contact assignment:让 physics 在窗口内自动发现

新的流程应该是:

  1. 用 object motion 得到宽松的 contact windows。
  2. 给定当前 hand rollout,估计每帧每个 fingertip 的最佳 body + local point
  3. 固定这组 assignment,优化 hand control。
  4. 重复第 2-3 步,直到收敛。

这是一个简化版 alternating optimization / EM,也是“让物理自动发现 contact”的可落地写法。

它的直觉很简单:

  • 当前 hand rollout 变了,最可能的 contact anchor 也应该跟着变;
  • 如果 anchor 在优化前就被写死,优化器只是在适配错误 target。

5.4 Hand observation:变成 robust weak loss

当前手部观测项不应继续被当成高置信真值。

应当改成:

  • loss_hand_joint 使用 robust loss,例如 Huber 或 Geman-McClure
  • 每个 joint 都带 confidence
  • wrist / palm / MCP 权重高
  • occluded fingertip 权重低
  • fingertip 更主要服务于 contact refinement,而不是 dense pose truth

5.5 Temporal:保留多段窗口,不再压成一个总区间

当前实现虽然记录了 contact_windows,但一些逻辑仍然倾向于把它们压成 first_contact_frame -> last_contact_frame

新的默认行为应该是:

  • 保留 disjoint windows
  • 每个 window 单独优化
  • 每个 window 单独估计 contact assignment
  • window 外默认不启用正的 contact target

是否还要额外加显式的 no-contact / no-motion prior,是后续可选设计,不是当前这份重设计的核心依赖。


6. Shared / Fixed 与 Free 两种模式的关系

6.1 Shared / Fixed object

这是当前默认更常见的模式。

在这种模式下,phys refine 的核心任务更像是:

  • 给定可信 articulation target
  • 反向求一个合理的 human contact explanation

它不需要也不应该把 object root 当成主要自由变量。

此时最重要的是:

  • contact timing 是否合理
  • contact body / anchor 是否合理
  • 双手参与是否合理
  • 接触前后物体是否符合因果关系

6.2 Free object

若进入真正的 free-object setting:

  • object_rigid_pose_mode != shared
  • lock_root = false

则整体动力学机制确实可以更接近论文。

但即便如此,也不意味着可以重新依赖 recon hand 去定义 contact truth。因为我们的核心难点仍然是 noisy recon,而不是 object mode 本身。

因此:

  • free 只改变 object state 的自由度
  • 不改变 contact timing / anchor 应该作为 latent variable 这一结论

7. 双手问题

新的 formulation 必须把 left / right / both / none 当成一等公民。

不能再默认:

  • 只有一只手真正接触
  • 接触更多的那只手就是 active_hand

更合理的表达应该是:

  • allowed_hands
  • contact_confidence[t, hand]
  • assignment[t, hand, fingertip]

换句话说,系统应该允许:

  • 左手主导,右手辅助
  • 右手主导,左手辅助
  • 双手同时接触
  • 某些窗口左手参与,另一些窗口右手参与
  • 某些时段完全无手接触

8. 对当前代码的建议落点

8.1 pipeline/physics/refine/data_loader.py

应当做两件事:

  1. _load_recon_contact_supervision(...) 改成以 object motion 为主的 contact supervision 构造器。
  2. _resolve_contact_point_spec(...) 改成 multi-link contact surface loader

新的输出应该更像:

  • allowed_hands
  • interaction_windows
  • 可选的 contact_confidence
  • contact_surface_candidates

而不是:

  • 单一 active_hand
  • 单一 contact_spec

8.2 pipeline/physics/grasp_and_lift_utils.py

build_contact_targets_local(...) 应升级成:

  • 支持 multi-link
  • 支持 per-tip per-frame assignment
  • 支持 soft weight / confidence

它的输出应该包含:

  • target_body_id
  • target_local_point
  • target_weight
  • valid / confidence

8.3 pipeline/physics/refine/contact.py

当前 contact target 基本是在优化前一次性构造好。

这里应改成:

  • 先构造候选 windows
  • 再在每轮外层迭代里重估 assignment
  • 再优化 hand control

也就是把 contact target 从 static precompute 变成 dynamic latent update。

8.4 pipeline/physics/refine/simulation.py

当前 contact loss 更接近:

  • 单一 contact_body_id
  • 单一 contact_targets_local
  • 单一 hard valid mask

这里应改成:

  • per-tip body assignment
  • per-tip target local point
  • soft confidence / weight

并把 contact loss 写成 assignment-aware 的形式。

8.5 pipeline/physics/refine/human_dof.py

人体回写也应改成吃 physics 推断出的 contact target,而不是继续回吃 recon 决定的 contact prior。


9. 推荐实施顺序

Phase 1:先把 coarse timing 从 recon hand 中剥离

目标:

  • 不再依赖 infer_hand_contact_labels(...) 决定 contact frame
  • 直接从 object motion 生成 interaction_windows

这是最小且最值得先做的一步,因为它能立刻减少 noisy hand recon 对优化器的误导。

目标:

  • 不再返回单一 ContactPointSpec
  • 支持多个 link surface candidates

这是第二个最关键步骤,因为当前单一 body 假设会把 anchor 问题过度简化。

Phase 3:引入 alternating contact assignment

目标:

  • assignment 不再预计算一次就写死
  • 每轮 hand optimization 后重估 contact target

这是让 formulation 真正稳定下来的关键。

Phase 4:把 hand observation loss 降级为 weak supervision

目标:

  • robust hand loss
  • per-joint confidence
  • wrist / palm 优先

这是让系统在 noisy recon 下不至于被 hand pose error 强行拉偏。


10. Historical implementation roadmap 与当时状态

这一节按“先去掉最糟的先验,再把 contact 变成 latent assignment”的顺序列出可直接开工的代码任务。

2026-04-22 实现状态:

  • contact_mode=mano 主路径已经切到 object motion -> interaction windows -> multi-link assignment -> MANO rollout
  • proxy 分支已经从 phys refine 主链删除,不再在 MJCF / case instance 里保留那 3 个 hand proxy mocap sphere。
  • shared 已作为 fixed 的 legacy alias 处理;shared / fixed object 不再导出 object root freejoint
  • free object 已扩成完整版本:rollout 会记录并回写 physics root pose / quat / vel,assignment 迭代也会跟随 rollout root 轨迹更新。
  • phys_refine 现在会用 default physics config + runtime overlay 解析配置,而不是要求 runtime 里必须给出完整 phys_refine 块。

10.1 P0:最小闭环

  1. pipeline/physics/refine/types.py 已新增数据结构,替代了当前单点 ContactPointSpec。 至少需要:
  2. InteractionWindowSpec
  3. ContactSurfaceCandidate
  4. PerTipContactAssignment

  5. pipeline/physics/refine/data_loader.py 中的接触时序入口已经改成基于 object motion 的窗口构造器,不再调用 infer_hand_contact_labels(...)。 输入优先使用:

  6. playback["joint_qpos"]
  7. root_pos / root_quat
  8. object_rigid_pose_mode 输出改成:
  9. interaction_windows
  10. enabled_hands
  11. 可选 contact_confidence

  12. pipeline/physics/phys_refine.py 主入口已经改掉,不再从 recon hand 读 contact_frames 并据此决定主分支。 新的分支逻辑应是:

  13. hand_contact_mode == noneinteraction_windows 为空时走 no-contact mode
  14. 只要 object 有 motion 且允许有手参与,就进入 mano_contact_mode

  15. pipeline/physics/refine/data_loader.py 中的 _update_case_instance(...) 已更新。 metadata 不再写:

  16. weak_contact_schedule_from_preprocess_hands
  17. active_hand 而改成:
  18. interaction_windows_from_object_motion
  19. enabled_hands
  20. 可选 contact_confidence

  21. 配置里已经补了 object-motion window 参数:

  22. interaction_window_source: object_motion
  23. motion_contact_vel_threshold
  24. motion_contact_pose_delta_threshold
  25. motion_contact_root_vel_threshold
  26. motion_contact_dilation
  27. motion_contact_merge_gap 现在 shared / fixed object 看 articulation,free object 额外看 root 线速度阈值。

10.2 P1:接触区域与 assignment

  1. pipeline/physics/refine/data_loader.py 中的 _resolve_contact_point_spec(...) 已经升级为 load_contact_surface_candidates(...)。 目标是:
  2. D3DHOI 保留原始 contact_vertices 集合,不再先求均值
  3. 非标注数据返回所有 active / manipulable link 的顶点候选,而不是一个 centroid

  4. pipeline/physics/grasp_and_lift_utils.py 已新增 build_contact_targets_local_multilink(...)。 输出不再是单一:

  5. targets_local
  6. valid_mask 而应包含:
  7. target_body_id
  8. target_local_point
  9. target_weight
  10. target_valid 维度至少覆盖 T x hand x fingertip

  11. pipeline/physics/refine/contact.py 中的 _run_mano_contact_mode(...) 已去掉对单一 contact_spec 的依赖,并加入外层 assignment 迭代。 当前实现流程是:

  12. estimate assignment
  13. run CMA
  14. update assignment

  15. pipeline/physics/refine/simulation.py 中以下两项已改成 per-tip / per-body assignment:

  16. _rollout_mano_pca_physics_multi(...)
  17. _mano_pca_cmaes_objective_multi(...) 从:
  18. single contact_body_id
  19. contact_targets_local_by_hand
  20. hard valid_mask 改成:
  21. per-tip body assignment
  22. local target
  23. soft weight loss_contact 也改成 weighted contact loss。

  24. pipeline/physics/refine/simulation.py 中 target 回投影逻辑也已经改掉,不再假设所有 target 都挂在同一个 contact_body_id 上。 每个 tip 的 world target 都应从各自的:

    • body_id
    • local_point 动态计算。

10.3 P2:手观测与人体回写

  1. pipeline/physics/refine/data_loader.py 中的 _load_active_hand_visual_points(...) 仍然保留,但已经只作为初始化和弱监督。 pipeline/physics/refine/simulation.py 中的 loss_hand_joint 现在是:

    • robust hand observation loss
    • 支持 per-joint confidence
    • 对 fingertip observation 默认降权
    • visual source 无效或 shape 不匹配时退到 reconstruction hand observation,并降低整体 confidence
  2. pipeline/physics/refine/human_dof.pypipeline/physics/refine/human_refine.py 已改成优先吃 physics 估出来的:

    • contact target
    • assignment 而不是继续依赖 recon 导出的 contact_prior_weights_by_hand

10.4 当前边界

  1. proxy 分支已经删除。 当前 phys refine 不再在场景里插入 hand proxy mocap sphere,也不再保留对应 metadata 字段。

  2. free object 已进入主路径。 当前实现会:

  3. free 模式下导出 object root freejoint
  4. 在 rollout 中记录 physics root pose / quat / vel
  5. 在目标函数中加入 root pose / rotation / velocity soft tracking
  6. 把 physics root 轨迹回写到 result_phys.npzplayback_traj_phys.npz

  7. 目前仍然只做 hand-only interaction。 先把下面四种模式做稳:

  8. left
  9. right
  10. both
  11. none

  12. 渲染仍然依赖 MuJoCo headless GL backend。 现在主优化/导出已经和视频渲染解耦:即使 render backend 初始化失败,phys_refine 主结果也会保留,并写出 render_warning.txt 而不是整 stage 失败。

10.5 实现验收

下面这些条目已经完成:

  • hand_contact_mode=both 时,即使 recon hand 很差,也能仅靠 object motion 进入 mano refine。
  • 不再要求 contact_frames 非空才进入 contact 分支。
  • 输出文件里不再把 active_hand 和单一 contact_point_spec 当核心结果,而是保存 interaction_windows 与 per-hand / per-tip assignment。
  • shared object 场景下,窗口外默认不启用正的 contact target;窗口内由 physics 自动发现更细的 contact。
  • left / right / both / none 四种模式在主分支逻辑上都已支持。
  • free object 场景下,physics root pose / quat / vel 会被真正 rollout、计入目标函数,并回写到 phys refine 输出。
  • partial runtime physics config 现在会自动继承 default phys refine 配置。
  • render backend 失败不再导致 phys refine 主结果丢失。

本轮实际做过的验证:

  • 相关 phys refine 文件已全部通过 py_compile
  • resolve_object_rigid_pose_mode(...) 已运行时验证:
  • shared -> fixed
  • per_frame -> free
  • build_contact_targets_local_multilink(...) 已做最小 smoke test:
  • 能在不同 frame 把 tip assignment 分配到不同 body id。
  • fixed 真实短序列端到端验收已完成:
  • 输入:output/validation/results/oven_fixed_6f/result.pt
  • 输出:output/validation/phys_refine_fixed_6f_20260422/
  • 已生成:result_phys.npzphysics_fit.jsonplayback_traj_phys.npzmujoco_phys.mp4hand_phys.mp4
  • free 真实短序列端到端验收已完成:
  • 输入:output/validation/results/laptop_free_6f/result.pt
  • 输出:output/validation/phys_refine_free_6f_20260422/
  • 已生成:result_phys.npzphysics_fit.jsonplayback_traj_phys.npzmujoco_phys.mp4hand_phys.mp4
  • 该 case 来自 legacy demo_laptop_ours 结果,已先转换成当前 result.pt schema 再执行 phys refine
  • 本机渲染验证结论:
  • fixed case 显式 MUJOCO_GL=egl 可渲染
  • free case 显式 MUJOCO_GL=osmesa 可渲染

11. 明确不做的事

  1. 不把 recon hand 当 contact truth。
  2. 不把单个 contact body 当默认 universal anchor。
  3. 不假设只有一只手参与。
  4. 不把多个分离 contact episode 压成一个长区间。
  5. 不为了贴合 noisy hand recon 去牺牲 contact causality。

12. 一句话版本

对于我们的数据,phys refine 不该是“拿 noisy recon hand 去 hard infer contact,再让 physics 去配合它”;而应该是“物体一旦出现 motion change,就直接给出 coarse interaction windows,然后让 physics 在窗口内自动发现 finer contact,而 hand 只做弱初始化与弱约束”。