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 review 与 Paper Readiness 为准。
0. 一页结论¶
当前 phys refine 已经借用了 Grasp-and-Lift 的一部分骨架:
- 用低维 hand control 驱动 MuJoCo rollout
- 物体运动由接触和动力学自然演化
- 用滑窗 CMA-ES 优化 hand control
- 用 object-local contact target 约束 fingertip
但它仍然保留了两个对我们最不合适的前提:
contact frames仍然主要由 preprocess / recon hand 推断。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_frames和contact_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 找最近邻。
这会导致两个问题:
- 接触区域被过早压缩,信息损失太大。
- 一旦 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自动发现 finercontact assignment - 最后才用 recon hand 做弱约束
而不是:
- 先用 recon hand 定时段
- 再用 recon hand 定 anchor
- 最后让 physics 去解释这些先验
4. 新的 formulation¶
4.1 总原则¶
新的 phys refine 不应该再把问题写成“先预测 contact,再优化 hand”。
更合理的写法是:
object motion直接给出 coarse interaction timingcontact hand是 latent hand assignmentcontact body / link是 latent surface assignmentcontact 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-awarestate 变化 - 直接得到宽松的
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 自动发现
4.4 Contact anchor 从多 link surface candidates 中自动发现¶
新的目标不是一个 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_valuesobject_joint_velocityobject_root_pos / quat与速度support_specobject_rigid_pose_mode
它的输出不应该再是:
active_hand = left/right- 单一
contact_frames
而应该是:
allowed_handsglobal_contact_windowscontact_confidence[t, left/right]- 可选的
contact_windows_by_hand
如果实现上想更激进一点,甚至可以先不输出逐帧 contact_confidence,只输出:
global_contact_windowsallowed_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 在窗口内自动发现¶
新的流程应该是:
- 用 object motion 得到宽松的 contact windows。
- 给定当前 hand rollout,估计每帧每个 fingertip 的最佳
body + local point。 - 固定这组 assignment,优化 hand control。
- 重复第
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 != sharedlock_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_handscontact_confidence[t, hand]assignment[t, hand, fingertip]
换句话说,系统应该允许:
- 左手主导,右手辅助
- 右手主导,左手辅助
- 双手同时接触
- 某些窗口左手参与,另一些窗口右手参与
- 某些时段完全无手接触
8. 对当前代码的建议落点¶
8.1 pipeline/physics/refine/data_loader.py¶
应当做两件事:
- 把
_load_recon_contact_supervision(...)改成以 object motion 为主的contact supervision构造器。 - 把
_resolve_contact_point_spec(...)改成multi-link contact surface loader。
新的输出应该更像:
allowed_handsinteraction_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_idtarget_local_pointtarget_weightvalid / 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 assignmentper-tip target local pointsoft 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 对优化器的误导。
Phase 2:把单一 contact body 改成 multi-link candidates¶
目标:
- 不再返回单一
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 rootfreejoint。free object已扩成完整版本:rollout 会记录并回写 physics root pose / quat / vel,assignment 迭代也会跟随 rollout root 轨迹更新。phys_refine现在会用default physics config + runtime overlay解析配置,而不是要求 runtime 里必须给出完整phys_refine块。
10.1 P0:最小闭环¶
pipeline/physics/refine/types.py已新增数据结构,替代了当前单点ContactPointSpec。 至少需要:InteractionWindowSpecContactSurfaceCandidate-
PerTipContactAssignment -
pipeline/physics/refine/data_loader.py中的接触时序入口已经改成基于 object motion 的窗口构造器,不再调用infer_hand_contact_labels(...)。 输入优先使用: playback["joint_qpos"]root_pos / root_quatobject_rigid_pose_mode输出改成:interaction_windowsenabled_hands-
可选
contact_confidence -
pipeline/physics/phys_refine.py主入口已经改掉,不再从 recon hand 读contact_frames并据此决定主分支。 新的分支逻辑应是: hand_contact_mode == none或interaction_windows为空时走no-contact mode-
只要 object 有 motion 且允许有手参与,就进入
mano_contact_mode -
pipeline/physics/refine/data_loader.py中的_update_case_instance(...)已更新。 metadata 不再写: weak_contact_schedule_from_preprocess_handsactive_hand而改成:interaction_windows_from_object_motionenabled_hands-
可选
contact_confidence -
配置里已经补了 object-motion window 参数:
interaction_window_source: object_motionmotion_contact_vel_thresholdmotion_contact_pose_delta_thresholdmotion_contact_root_vel_thresholdmotion_contact_dilationmotion_contact_merge_gap现在shared / fixed object看 articulation,free object额外看 root 线速度阈值。
10.2 P1:接触区域与 assignment¶
pipeline/physics/refine/data_loader.py中的_resolve_contact_point_spec(...)已经升级为load_contact_surface_candidates(...)。 目标是:- D3DHOI 保留原始
contact_vertices集合,不再先求均值 -
非标注数据返回所有 active / manipulable link 的顶点候选,而不是一个 centroid
-
pipeline/physics/grasp_and_lift_utils.py已新增build_contact_targets_local_multilink(...)。 输出不再是单一: targets_localvalid_mask而应包含:target_body_idtarget_local_pointtarget_weight-
target_valid维度至少覆盖T x hand x fingertip。 -
pipeline/physics/refine/contact.py中的_run_mano_contact_mode(...)已去掉对单一contact_spec的依赖,并加入外层 assignment 迭代。 当前实现流程是: estimate assignmentrun CMA-
update assignment -
pipeline/physics/refine/simulation.py中以下两项已改成 per-tip / per-body assignment: _rollout_mano_pca_physics_multi(...)_mano_pca_cmaes_objective_multi(...)从:single contact_body_idcontact_targets_local_by_handhard valid_mask改成:per-tip body assignmentlocal target-
soft weightloss_contact也改成 weighted contact loss。 -
pipeline/physics/refine/simulation.py中 target 回投影逻辑也已经改掉,不再假设所有 target 都挂在同一个contact_body_id上。 每个 tip 的 world target 都应从各自的:body_idlocal_point动态计算。
10.3 P2:手观测与人体回写¶
-
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
-
pipeline/physics/refine/human_dof.py和pipeline/physics/refine/human_refine.py已改成优先吃 physics 估出来的:contact targetassignment而不是继续依赖 recon 导出的contact_prior_weights_by_hand
10.4 当前边界¶
-
proxy分支已经删除。 当前 phys refine 不再在场景里插入 hand proxy mocap sphere,也不再保留对应 metadata 字段。 -
free object已进入主路径。 当前实现会: - 在
free模式下导出 object rootfreejoint - 在 rollout 中记录 physics root pose / quat / vel
- 在目标函数中加入 root pose / rotation / velocity soft tracking
-
把 physics root 轨迹回写到
result_phys.npz和playback_traj_phys.npz -
目前仍然只做 hand-only interaction。 先把下面四种模式做稳:
leftrightboth-
none -
渲染仍然依赖 MuJoCo headless GL backend。 现在主优化/导出已经和视频渲染解耦:即使 render backend 初始化失败,
phys_refine主结果也会保留,并写出render_warning.txt而不是整 stage 失败。
10.5 实现验收¶
下面这些条目已经完成:
-
hand_contact_mode=both时,即使 recon hand 很差,也能仅靠 object motion 进入manorefine。 - 不再要求
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 -> fixedper_frame -> freebuild_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.npz、physics_fit.json、playback_traj_phys.npz、mujoco_phys.mp4、hand_phys.mp4 free真实短序列端到端验收已完成:- 输入:
output/validation/results/laptop_free_6f/result.pt - 输出:
output/validation/phys_refine_free_6f_20260422/ - 已生成:
result_phys.npz、physics_fit.json、playback_traj_phys.npz、mujoco_phys.mp4、hand_phys.mp4 - 该 case 来自 legacy
demo_laptop_ours结果,已先转换成当前result.ptschema 再执行 phys refine - 本机渲染验证结论:
- fixed case 显式
MUJOCO_GL=egl可渲染 - free case 显式
MUJOCO_GL=osmesa可渲染
11. 明确不做的事¶
- 不把 recon hand 当 contact truth。
- 不把单个
contact body当默认 universal anchor。 - 不假设只有一只手参与。
- 不把多个分离 contact episode 压成一个长区间。
- 不为了贴合 noisy hand recon 去牺牲 contact causality。
12. 一句话版本¶
对于我们的数据,phys refine 不该是“拿 noisy recon hand 去 hard infer contact,再让 physics 去配合它”;而应该是“物体一旦出现 motion change,就直接给出 coarse interaction windows,然后让 physics 在窗口内自动发现 finer contact,而 hand 只做弱初始化与弱约束”。