跳转至

Keyframe Binding 使用说明

这份文档说明当前 repo 里的 MotionBricks-style keyframe binding 是怎么把 canonical sit keyframe 合成到具体 SAGE object 上的,以及如何运行和检查输出。

当前实现文件:

utils/kimodo/object_keyframe_binding.py

当前 demo 输出目录:

data/hsi_primitives/object_bindings/demo_pouf_e55447

1. 目标

我们不让 Kimodo 直接“理解”某个具体 pouf/chair/sofa 的 geometry。当前做法是:

canonical seated keyframe
  + concrete SAGE object mesh
  + scene_context binding hints
  -> bound fullbody keyframe constraint

也就是仿照 MotionBricks 的 Smart Object 思路:

body-side socket / contact patch
object-side socket / support patch

然后把合成后的 fullbody keyframe 输出成 Kimodo native constraint 可用的结构。


2. 输入数据

脚本默认使用:

plan:
  utils/sage_task_generator/real_vlm_runs/20260423_drop_pen_keep_sit

target object:
  pouf:e55447

canonical keyframe library:
  data/hsi_primitives/keyframe_library

taxonomy:
  data/hsi_primitives/taxonomy/sage_sit_object_types.json

scene_context.json 里需要有:

entities[*].explicit_interaction_targets_by_mode["sit"]
entities[*].seat_front_direction_xy
entities[*].seat_front_standing_point_hint_xy
entities[*].position_xyz
entities[*].dimensions_xyz

layout_json 里需要有对应 object 的:

source_id
position
rotation

脚本会用 source_id 读取真实 SAGE object mesh:

data/selected_SAGE10k/test_scene/objects/{source_id}.ply

缺少这些字段会直接报错;当前实现不做 bbox fallback。


3. Binding 逻辑

3.1 Object-side socket

脚本先读取目标 object 的真实 mesh,并找最高处的 top surface patch:

top_patch = object_vertices[z >= top_z - 0.03]

然后用 top patch 的 xy median 作为 support surface center。

对于 pouf/ottoman/stool,当前会把 socket 沿 seat_front_direction_xy 往前移动:

front_offset = 0.34 * object_depth

这是为了避免低矮圆柱/软凳场景里,小腿插入 object volume。

当前 demo 的 object socket 大约是:

seat_center_y: 5.450
seat_contact_socket_y: 5.189

也就是说 contact point 不在 top center,而是在 top support surface 的前半部。

3.2 Body-side socket

对 canonical seated pose,脚本调用 Kimodo 的官方 SOMA skin:

kimodo.viz.soma_skin.SOMASkin

得到完整 SOMA mesh 后,从 lower pelvis / upper thigh 区域提取 body-side contact patch:

height > floor + 0.20
height < hips + 0.04
abs(lateral) < 0.28
front in [-0.30, 0.24]

然后取该 patch 的低处 vertices,得到 body_contact_socket

3.3 Rigid binding

绑定时只做刚体变换:

yaw:
  canonical heading -> seat_front_direction

translation:
  body_contact_socket -> object seat_contact_socket

然后保持 fullbody local pose 不变,输出新的:

local_rot_mats
global_rot_mats
posed_joints
root_position
smooth_root_2d
global_root_heading

4. Scoring / filtering

当前 score 不是只看 Hips/root,而是以 full SOMA mesh 为主。

主要 metrics:

contact_patch_support_ratio
contact_patch_z_gap_m
contact_patch_center_xy_error_m
full_body_penetration_vertex_count
hips_seat_offset_m
foot_max_abs_floor_error_m

Hard filters:

contact patch vertices 太少 -> invalid
contact z gap > 3.5cm -> invalid
contact patch support ratio < 0.55 -> invalid
full body penetration vertices > 50 -> invalid
hips height 不合理 -> invalid
feet-floor error > 12cm -> invalid

注意:full_body_penetration_vertex_count 是对完整 SOMA mesh 检查是否进入 object volume proxy,而不是只检查 Hips 或 top support 区域。


5. 运行命令

使用当前默认 demo:

/home/user/anaconda3/envs/isaac-sim/bin/python tools/kimodo/object_keyframe_binding.py

也可以显式指定:

/home/user/anaconda3/envs/isaac-sim/bin/python tools/kimodo/object_keyframe_binding.py \
  --plan-dir utils/sage_task_generator/real_vlm_runs/20260423_drop_pen_keep_sit \
  --target-label pouf:e55447 \
  --output-dir data/hsi_primitives/object_bindings/demo_pouf_e55447

6. 输出文件

输出目录:

data/hsi_primitives/object_bindings/demo_pouf_e55447

关键文件:

binding_manifest.json
candidates.json
top_candidate_fullbody_constraint.json
bound_pose_gallery.html
bound_poses/*.npz
mesh_data/object.json
mesh_data/human_*.json

top_candidate_fullbody_constraint.json 是后续喂给 Kimodo 的 fullbody constraint:

{
  "type": "fullbody",
  "frame_indices": [0],
  "local_joints_rot": [...],
  "root_positions": [...],
  "smooth_root_2d": [...]
}

bound_poses/*.npz 保存 bound 后的 fullbody pose 和 contact patch:

local_rot_mats
global_rot_mats
posed_joints
posed_joints_sage
root_position
smooth_root_2d
global_root_heading
body_contact_socket_sage
body_contact_patch_sage
body_contact_patch_vertex_indices
seat_contact_socket_sage

7. 可视化

当前 gallery 用完整 mesh:

object:
  SAGE object .ply mesh

human:
  Kimodo SOMASkin full SOMA mesh

如果 server 已经启动,可以打开:

http://162.105.162.47:8898/bound_pose_gallery.html

建议加 query string 强制刷新:

http://162.105.162.47:8898/bound_pose_gallery.html?v=pouf_offset_034

如果本地没有 server,可以在输出目录上启动:

python -m http.server 8898 --bind 0.0.0.0 --directory data/hsi_primitives/object_bindings/demo_pouf_e55447

8. 当前 sit-on-pouf demo 结果

最近一次生成:

num_candidates: 7
num_valid: 7
top_candidate_item_id: sit_low_seat_like_p00_seed0000

top candidate 的关键指标:

contact_patch_support_ratio: 0.675
full_body_penetration_vertex_count: 37
hips_center_distance_m: 0.124
root_y_sage: 5.326

这版是当前人工检查后较合理的 compromise:

不再明显坐到 top center 深处
不再把 invalid 穿模候选展示给用户
比 0.36 depth 更不靠前
比 0.30/0.32 depth 更少出现小腿穿入 puff

9. 当前限制

当前实现仍然是 rigid binding,不做 leg IK / pose repair。

如果一个 canonical seated pose 的小腿本身会插入低矮 object,仅靠平移 socket 会遇到 tradeoff:

socket 太靠里:
  butt support 好,但 shin/leg 容易进入 object volume

socket 太靠前:
  leg penetration 少,但人会坐到前缘

下一步更稳的方案是:

1. 为 pouf / low stool 单独筛选更多 canonical seated poses
2. 在 binding 后做 very light leg/foot IK repair
3. 用 Isaac/PhysX validation 检查最终 contact 和 penetration