跳转至

Reconstruction · Human Motion Postprocess

本页属于 Reconstruction:它负责 refinement \(\widehat H_t\),输出 Motion Smooth + World Root Contact 后的 human reference。早期 E2E 方案、诊断和 J2D loss 计划已移入 ../archive/,需要追溯时再读取。

日期:2026-04-25

本文修正上一版计划。相机是 static,不能把当前视频 overlay 抖动归因成 camera motion。

原计划拆成两步串联:camera-frame smooth → world contact opt。本版合并为 单次 joint world-space 优化:T_c2world 从 depth RANSAC 预计算固定,然后一次 Adam run 同时优化 FK-output world-space smoothness + foot contact。

参考论文:[2512.21573] World-Coordinate Human Motion Retargeting via SAM 3D Body。该论文提供三阶段框架,但没有给出足够工程细节;下面的方案必须以本 repo 的 MHR 参数定义和已有 physics pipeline 为准。


0. 当前已知事实

0.1 本项目当前 best baseline

现有视觉 best 是 D2b_body_all_euler_w5_s1p0_b0p35

upper2d ratio = 0.648
wrist2d ratio = 0.654

路径:

submodules/sam-body4d/outputs/postprocess_attribution_b009_0001_20260425/D2b_body_all_euler_w5_s1p0_b0p35

0.2 静态相机下 overlay 抖动的来源

SAM-3D-Body overlay 里相机本身不动。投影大致是:

j3d_cam = j3d_local + pred_cam_t[:, None, :]
project(j3d_cam, focal_length)

所以 overlay jitter 的候选来源是 motion 参数,而不是 camera motion:

  • global_rot
  • body_pose
  • hand_pose
  • pred_cam_t,注意这是人体在 camera frame 下的 translation / projection 输入,不是相机运动
  • shape/scale flicker
  • hand decoder wrist write-back / body decoder wrist merge

0.3 MHR 参数表示不能按普通 SMPL rotvec 处理

本 repo 的关键代码事实:

  • global_rot 是 MHR forward 接收的 Euler 参数,不是 rotvec。
  • body_pose 缓存为 (T, 133),但 mhr_forward() 实际只使用 body_pose[..., :130]
  • body_pose 不是 (J, 3) axis-angle,而是 MHR compact 参数展开后的混合表示:3DoF Euler groups、1DoF angles、translation-like params。
  • scale 缓存为 28D coefficient,经过 scale_mean + scale @ scale_comps 映射到论文所说的 68D skeleton scale。
  • 旋转 smooth / rotation loss 应该通过 mhr_forward(..., return_joint_rotations=True) 得到的 joint_global_rots 来做,而不是把 body_pose 每 3 维当 SO3。

论文里的 low-dimensional MHR latent space 是成立的,但这里的 latent 指的是 MHR 的结构化低维 rig 参数,而不是 VAE bottleneck 或任意 learned embedding。它低维是相对 mesh vertices、dense skinning state、逐点 surface motion 而言;在我们代码里对应的可优化量主要是 global_rot + body_pose[:130] + scale/shape/expr/hand 这些 MHR 参数。

因此实现上应该:

  • 在 MHR 参数空间里优化,保持 fidelity 到原始 per-frame estimate。
  • mhr_forward() 把参数解码到 j3d / joint_global_rots 后计算 temporal smoothness。
  • 不把 body_pose 简化成普通 (J, 3) rotvec,也不把 pred_pose_raw 当作一定可直接复用的稳定 latent,除非确认 forward path 支持。

相关代码:

submodules/sam-body4d/models/sam_3d_body/sam_3d_body/models/heads/mhr_head.py
submodules/sam-body4d/models/sam_3d_body/sam_3d_body/models/modules/mhr_utils.py

1. 总体 Pipeline

pre_smooth_mhr.npz
  -> [A] Identity / Scale Locking        (temporal median shape/scale)
  -> [C] World-frame Ground Estimation   (depth RANSAC,预计算固定)
  -> [B] Joint World-Space Optimization  (单次 Adam,batch mhr_forward)
       = L_latent + L_smooth(world) + L_contact(world)
  -> smoothed_world_mhr.npz + overlay + world_root / physics input

最终目标:

  • 视频 overlay 更平滑:L_latent + L_smooth 共同保证。
  • world space root 物理合理:L_contact 同时处理 foot slide + penetration。
  • 单次优化完成两件事,不需要两次独立 Adam。

Stage C(depth RANSAC)作为预计算,输出固定 T_c2world 给 Stage B。Stage B 把 T_c2world 当 constant,不参与优化。

1.1 代码合并后的默认路线

当前工程合并目标改为:

默认 human motion provider = sam3d_body
默认 world frame provider  = depth_plane_zup
默认假设                  = static camera + metric/aligned depth 能可靠看到一部分地面或支撑平面

这意味着默认路线不再要求先跑 GVHMR 来提供 T_c2world

RGB/mask/depth/K
  -> SAM3D Body per-frame MHR
  -> shape/scale lock + MHR parameter smoothing
  -> metric depth plane fit in camera frame
  -> T_c2world_depth_plane, ground_z=0
  -> smoothed camera-frame human -> Z-up world
  -> contact-aware root optimization
  -> physics input

GVHMR 路线保留为兼容 provider,而不是默认依赖:

human.model = gvhmr
  -> 继续使用现有 GVHMR motion/result.pt
  -> 继续支持现有 depth ground correction / T_c2world 读取逻辑

这里不设计自动 fallback。sam3d_bodygvhmr 由 config 明确选择;当前实验和默认配置按 sam3d_body 走。


2. Stage A:Identity / Scale Locking

2.1 目的

消除同一 track 内 body shape / skeleton scale flicker。

2.2 实现方式

论文说的是 temporal mean。我们实际缓存是:

shape: (T, 45)
scale: (T, 28)

先做 robust temporal locking:

shape_final = robust_mean_or_median(shape[t] over valid frames)
scale_final = robust_mean_or_median(scale[t] over valid frames)
shape[:] = shape_final
scale[:] = scale_final

建议比较三组:

A0: pin first frame       # 当前 SAM-Body4D / D2b 类似做法
A1: temporal mean
A2: temporal median       # 对异常帧更稳

默认从 A2 开始,因为第一帧可能不是最稳定帧。


3. Stage B:Joint World-Space Optimization

3.1 目标

单次 Adam 优化同时解决: - Camera-frame overlay 抖动(L_latent + L_smooth) - World-space foot contact / sliding / penetration(L_contact)

在 world space 做 smooth 比 camera frame 更合理:gravity 方向正确,foot contact 约束自然。

3.2 前置条件

Stage C 已完成,T_c2world 固定:

# constant, not optimized
R_c2w, t_c2w = T_c2world[:3, :3], T_c2world[:3, 3]

3.3 优化变量

global_rot: (T, 3)    # root orientation
body_pose:  (T, 130)  # mhr_forward 实际使用部分
pred_cam_t: (T, 3)    # 强 fidelity 约束,小 lam

hand_pose 已由 D0 hand smoothing 处理,不参与本次优化。

3.4 Batch mhr_forward

关键:每次 iter 对全部 T 帧做一次 batched forward,不逐帧循环:

# params: (T, D) PyTorch tensors with requires_grad=True
j3d_local, joint_global_rots = mhr_forward(global_rot, body_pose)  # (T, J, 3), (T, J, 3, 3)
j3d_cam = j3d_local + pred_cam_t[:, None, :]                        # (T, J, 3)
j3d_world = (R_c2w @ j3d_cam.reshape(T, J, 3, 1)).squeeze(-1) + t_c2w  # (T, J, 3)

mhr_forward 必须支持 PyTorch tensor 输入和 autograd。如果当前实现是 numpy,需要: - 方案 A:port 到 torch(推荐,一劳永逸) - 方案 B:torch.autograd.Function 包装,forward 走 numpy,backward 手动 Jacobian

3.5 Loss 设计

L_latent(参数保真)

L_latent =
  (1/D_g) * ||global_rot - global_rot0||^2
+ (1/D_b) * ||body_pose  - body_pose0 ||^2
+ (1/D_c) * w_cam * ||pred_cam_t - pred_cam_t0||^2

per-group normalize 避免 130D body 压倒 3D global。

L_smooth(FK-output world-space smooth)

论文原文公式(Eq. 3-5),在 world space 计算:

linear velocity:     v_t^j = j3d_world[t+1,j] - j3d_world[t,j]
linear acceleration: a_t^j = j3d_world[t+2,j] - 2*j3d_world[t+1,j] + j3d_world[t,j]
angular velocity:    ω_t^j = Log(R[t+1,j] @ R[t,j].T)
angular acceleration:α_t^j = ω[t+1,j] - ω[t,j]

L_smooth = Σ_j w_j * (
    λ_v * Σ_t ρ(||v_t^j||)
  + λ_a * Σ_t ρ(||a_t^j||)
  + λ_ω * Σ_t ρ(||ω_t^j||)
  + λ_α * Σ_t ρ(||α_t^j||)
)

Charbonnier: ρ(x) = sqrt(x^2 + ε)

joint weight w_j(论文原则:trunk > extremities > face):

root / pelvis / spine  : 1.0
shoulder / hip         : 0.8
elbow / knee           : 0.6
wrist / ankle          : 0.5
hand / foot joints     : 0.1
face                   : 0.0

主要靠 λ_a(acceleration)和 λ_α(angular acc)。λ_v 不能太大,否则真实动作被压软。

L_contact(foot contact,world space)

heel indices:left=17, right=20(MHR70 定义)

d_f = j3d_world[:, foot_idx, 2] - ground_z          # (T,),foot height above ground
p_c = exp(-d_f^2 / (2*σ_h^2))                       # soft contact prob,σ_h ≈ 0.07m

L_slide   = Σ_t p_c[t] * ||j3d_world[t+1,heel,:2] - j3d_world[t,heel,:2]||^2  # XY sliding
L_pen     = Σ_t ReLU(-d_f[t])^2                     # 穿地
L_contact = Σ_t p_c[t] * d_f[t]^2                   # 接触时贴地

总 loss

L_total = λ_latent * L_latent
        + L_smooth
        + λ_phy * (L_slide + L_pen + L_contact)

3.6 优化器与时间估算

优化器:Adam,lr=1e-3,100 iter(从好的初始点出发,不需要更多)

每次 iter 耗时估算(T=150 帧,J=70 关节,joints only,不跑 mesh skinning):

步骤 CPU 估算 GPU 估算
batch mhr_forward ~5-20ms ~0.5-2ms
loss 计算(diff、charbonnier) ~2-5ms ~0.5ms
backward ~5-20ms ~0.5-2ms
Adam step ~1ms ~0.1ms
单 iter 总计 ~13-46ms ~1.6-4.6ms

总优化时间(100 iter):

CPU: 1.3 - 4.6 秒
GPU: 0.2 - 0.5 秒

远快于渲染(per-frame render 通常 >100ms/frame)。作为预处理完全可接受。

加速关键: 1. 必须 batch forward,不能逐帧循环 2. joints only,不算 mesh vertices 3. 不需要超过 100 iter(初始点好,fidelity 项限制收敛域)

3.7 与旧 D2b / IRLS 的关系

IRLS(当前实现):
  parameter-space smooth,O(1) 解析解,快 (<100ms)
  但 FK 输出不被直接约束,j3d_cam jitter 有上限

Joint world opt(本方案):
  FK-output world-space smooth + contact,Adam 100 iter,~1-5s CPU
  j3d_world 被直接约束,contact 和 smooth 联合优化

IRLS 继续保留为 quick_smooth baseline(D2b 路线)
本方案作为 full_opt 默认路线

3.8 输出

smoothed_world_mhr.npz:
  global_rot:              (T, 3)
  body_pose:               (T, 133)  # 优化后 params + 未优化维度保持原值
  pred_cam_t:              (T, 3)
  j3d_cam:                 (T, J, 3)
  j3d_world:               (T, J, 3)
  human_root_transl_world: (T, 3)
  human_root_orient_world: (T, 3)
  contact_weights:         (T, 2)
  T_c2world:               (4, 4)
  ground_z:                scalar
  opt_time_sec:            scalar
  n_iter:                  scalar

4. Stage C:World-frame Ground Choice

4.1 新默认:SAM3D Body + depth-plane Z-up

在当前假设下:

static camera
depth 能可靠看到一部分地面或支撑平面
depth 已是 metric,或已经通过 human/object depth alignment 变成 metric

可以直接从 depth 点云拟合 camera-frame ground plane,然后构造 Z-up world:

depth + K
  -> backproject to camera-frame point cloud
  -> fit support plane: n_c^T X_c + d_c = 0
  -> choose normal sign so camera height is positive
  -> construct T_c2world_depth_plane
  -> ground_z = 0

T_c2world_depth_plane 只需要满足:

world z axis = fitted plane normal
ground plane = z=0
camera origin z > 0

world yaw 和 XY origin 在单目静态相机下没有唯一真值。第一版固定一个确定性 convention 即可,例如:

world x = camera x projected onto ground plane and normalized
world y = world z cross world x
world origin = camera-frame plane projection point, transformed so plane z=0

只要人体、物体、depth 点云和 physics 都使用同一个 T_c2world_depth_plane,yaw gauge 不影响 contact/root/physics 的基本合理性。

4.2 共享 ground-plane 估计代码

不要为 SAM3D Body 路线复制一份新的 RANSAC。应把当前 ground_align.py 里的 plane fitting 拆成共享模块:

pipeline/reconstruction/preprocess/depth/ground_plane.py

建议导出的核心函数:

@dataclass
class GroundPlaneFit:
    normal_cam: np.ndarray      # (3,), camera-frame unit normal
    offset_cam: float           # n_c^T X_c + d_c = 0
    inlier_count: int
    total_count: int
    residual_median_m: float
    camera_height_m: float
    confidence: float

def backproject_depth_sequence(depth_seq, K_seq, *, stride, subsample, masks=None) -> np.ndarray:
    ...

def fit_support_plane_camera(points_cam, *, n_iter, dist_thresh, roi_policy) -> GroundPlaneFit:
    ...

def camera_plane_to_zup_transform(fit: GroundPlaneFit) -> tuple[np.ndarray, np.ndarray]:
    # returns T_c2world, T_w2c
    ...

现有 GVHMR 路线继续复用同一套 plane fitting,但封装成另一个调用:

def correction_from_depth_plane_for_existing_world(
    depth_seq,
    K_seq,
    T_c2world_raw,
) -> GroundCorrection:
    # 等价于当前 ground_align.py 的行为:
    # 用 raw world 构造点云,在 raw Z-up 里拟合 ground,然后返回 T_correction。

这样共享的是:

depth backprojection
RANSAC / SVD plane refine
residual / confidence 统计
debug artifact 输出

区别只是输出:

sam3d_body:
  plane camera frame -> direct T_c2world_depth_plane

gvhmr:
  plane raw world frame -> T_correction @ T_c2world_raw

4.3 Human motion 加载

不需要 provider 抽象层。config 驱动的简单加载函数足够:

def load_mhr_params(cfg) -> dict[str, np.ndarray]:
    if cfg.motion.model == "sam3d_body":
        return dict(np.load(cfg.motion.sam3d_body.npz_path))
    elif cfg.motion.model == "gvhmr":
        return convert_gvhmr_result_to_mhr(cfg.motion.gvhmr.result_pt)

调用方只依赖返回 dict 的 keys(global_rot, body_pose, pred_cam_t 等),不需要 dataclass 或 interface。

4.4 Config 选择

新增一个明确字段控制 human model。默认值为 sam3d_body

reconstruction:
  preprocess:
    motion:
      enabled: true
      model: "sam3d_body"   # choices: sam3d_body, gvhmr

      sam3d_body:
        enabled: true
        run_stage_ab_smoothing: true
        shape_scale_lock: "median"
        body_smooth_variant: "B2_body_charb_ultra"
        output_name: "sam3d_body_mhr"

      gvhmr:
        enabled: false
        use_dpvo: false
        hand_refine:
          enabled: true
          refiner: "dynhamr"

    world_frame:
      source: "depth_plane_zup"     # choices: depth_plane_zup, gvhmr
      depth_plane_zup:
        stride: 5
        subsample: 10
        n_iter: 5000
        dist_thresh: 0.04
        roi_policy: "lower_scene_without_human_object"

兼容旧配置:

preprocess.motion: true

等价于:

preprocess:
  motion:
    enabled: true
    model: "sam3d_body"

旧 GVHMR 行为必须显式写:

preprocess:
  motion:
    enabled: true
    model: "gvhmr"

4.5 Ground 分支保留方式

旧实验里的 G0/G1/G2/G3 仍可保留为 ablation,但不再是默认路线:

S0_depth_plane_zup:
  默认。SAM3D Body camera-frame human + depth plane direct T_c2world.

G0_gvhmr_depth_corrected:
  兼容旧路线。GVHMR T_c2world + depth ground correction.

G1_gvhmr_raw_world:
  旧路线 ablation。未做 depth correction 的 GVHMR static world.

G2_contact_offset:
  保留 world orientation,只让 contact 优化 scalar ground_z / vertical offset.

G3_contact_plane_optional:
  研究项。优化 ground normal + offset;不作为当前合并目标。

当前默认优先级不是 fallback,而是固定配置:

default = S0_depth_plane_zup
legacy  = G0_gvhmr_depth_corrected, only when motion.model=gvhmr

4.6 论文 contact 方案的位置

论文式 contact optimizer 仍然放在 Stage D。它不负责估计完整 scene plane;它接收 Stage C 给出的 Z-up world frame 和 ground_z=0,再优化人体 root trajectory、foot sliding 和 penetration。

因此在新默认路线里,contact optimizer 的输入从:

GVHMR/depth-corrected T_c2world

改成:

depth-plane direct T_c2world_depth_plane

5. Stage D:已合并入 Stage B

Contact-aware world root optimization 已合并进 Stage B(Section 3)的 L_contact 项。不再作为独立 stage。

原 Stage D 的功能对应:

原 delta_root_world 优化  -> Stage B 中 global_rot + pred_cam_t 优化 + L_contact
原 L_slide / L_pen / L_contact_height -> Stage B Section 3.5 的 L_contact
原 soft contact probability           -> Stage B Section 3.5 的 p_c

原 Stage D 实验结果(已记录于 Section 10.3)保留作为 baseline 对比。新实现以合并后的 Stage B 为准。


6. Metrics 与实验判断

6.1 Camera-frame motion metrics

用于判断 Stage B 是否改善 overlay 抖动:

upper2d d3 p95
wrist2d d3 p95
all70 2d d3 p95
j3d_cam acceleration / jerk p95
body/global/pred_cam_t parameter deviation from pre_smooth

必须输出:

overlay_2d.mp4
4d_visualization.mp4
D2b_vs_stageB.mp4
motion_report.json

6.2 World / physics metrics

用于判断 Stage D 是否改善 physics root:

contact foot sliding distance
contact heel height error
ground penetration frame count / max depth
root acceleration p95
root deviation from init
physics retargeting visual result

Ground 对比时,至少输出:

G0_ransac_before_after.json
G2_contact_offset_before_after.json
heel_height_curves.png
foot_slide_curves.png
world_root_compare.mp4 or 3D plot

7. 实现路线图

Phase 1:修正前置验证

□ 确认 pre_smooth_mhr.npz keys / shapes
□ 确认 mhr_forward 使用 body_pose[:130]
□ 确认 pred_cam_t 进入 projection 的方式
□ 确认 depth.npy 是 metric 或已经通过 human/object depth alignment 变成 metric
□ 确认 camera intrinsics K_seq 可从 motion metadata 或 config 中稳定读取
□ 确认 depth-plane provider 输出 static T_c2world_depth_plane 和 ground_z=0
□ 渲染 D2b baseline,作为所有实验对比对象

Phase 2:Stage A + B

新建或扩展:

scripts/debug/mhr_motion_smooth_opt.py

输入:

pre_smooth_mhr.npz
D2b smpl_params.npz for baseline comparison

实验:

B0_D2b_baseline
B1_shape_scale_median_only
B2_latent_body_global
B3_latent_body_global_predcam
B4_latent_body_global_predcam_hand_light

选择标准:

优先 upper2d/wrist2d d3 p95 下降
其次 j3d_cam jerk 下降
同时检查 side-by-side overlay 不明显漂移、不动作过软

Phase 3:Stage C + D

新建:

pipeline/reconstruction/preprocess/human/providers/base.py
pipeline/reconstruction/preprocess/human/providers/sam3d_body.py
pipeline/reconstruction/preprocess/human/providers/gvhmr.py
pipeline/reconstruction/preprocess/depth/ground_plane.py
scripts/debug/world_root_contact_opt.py
scripts/compare_ground_modes.py

合并顺序:

1. 把 ground_align.py 的 RANSAC / SVD / residual 逻辑抽成 ground_plane.py。
2. 为 sam3d_body provider 生成 smoothed_camera_mhr.npz 和 camera-frame joints/verts。
3. 为 depth_plane_zup provider 直接生成 T_c2world_depth_plane。
4. 修改 world_root_contact_opt.py,使它只依赖 HumanMotionResult + WorldFrameResult。
5. 把旧 GVHMR motion 路径包进 gvhmr provider,保持显式 config 兼容。

选择标准:

depth-plane residual 小
camera height 合理
foot sliding 更小
penetration 更少
root acceleration 不恶化
root deviation 不过大
physics / Isaac 里人体不漂、不穿地

Phase 4:串联最终输出

Best Stage B camera-frame human motion
  -> depth_plane_zup world frame
  -> contact root opt
  -> physics input

最终输出需要同时包含:

smoothed_camera_mhr.npz
overlay_2d.mp4
world_root.npz
world_frame_report.json
physics_preview.mp4

8. 当前建议默认路线

当前默认路线固定为 sam3d_body + depth_plane_zup,不做自动 fallback:

1. preprocess.motion.model = sam3d_body
2. 跑 SAM3D Body per-frame MHR export。
3. 跑 Stage A:temporal median shape/scale lock。
4. 使用 metric/aligned depth + K,在 camera frame 直接拟合 support plane(Stage C)。
5. 构造 T_c2world_depth_plane,使 support plane 成为 Z-up world 的 z=0。
6. 跑 Stage B joint world-space optimization:
     - 优化变量:global_rot + body_pose[:130] + pred_cam_t
     - 每 iter 做一次 batch mhr_forward(T=150 帧,全部并行)
     - loss = L_latent + L_smooth(world) + λ_phy * L_contact(world)
     - Adam,lr=1e-3,100 iter,~1-5s CPU
7. 输出 smoothed_world_mhr.npz / overlay_2d.mp4 / world_root.npz / physics_preview.mp4。

原因:

  • 当前 case 明确假设 static camera + depth 能可靠看到一部分地面或支撑平面。
  • 在该假设下,GVHMR 不是构造 Z-up world frame 的必要依赖。
  • sam3d_body 直接产生 MHR 参数,和 Stage A/B 的优化变量一致,减少 GVHMR->SMPLX->MHR/adapter 之间的额外转换。
  • depth-plane provider 提供 scene-based ground;contact optimizer 只负责 root trajectory 的物理合理性。
  • GVHMR 路线保留为显式兼容配置:preprocess.motion.model = gvhmr

9. 参考资料

  • 主论文:[2512.21573] World-Coordinate Human Motion Retargeting via SAM 3D Body
  • MHR 模型:[2511.15586] MHR: Momentum Human Rig
  • 本项目 MHR forward:submodules/sam-body4d/models/sam_3d_body/sam_3d_body/models/heads/mhr_head.py
  • 本项目 MHR compact 参数:submodules/sam-body4d/models/sam_3d_body/sam_3d_body/models/modules/mhr_utils.py
  • 本项目 Z-up camera/world helpers:pipeline/utils/geom_utils.py
  • 本项目现有 baseline:submodules/sam-body4d/outputs/postprocess_attribution_b009_0001_20260425/D2b_body_all_euler_w5_s1p0_b0p35

10. 实验完成记录(2026-04-25)

10.1 实现脚本

已新增:

scripts/debug/mhr_motion_smooth_opt.py
scripts/debug/world_root_contact_opt.py

运行环境:

source ~/.bashrc && hoi4d_conda && conda activate cari4d

本次沙箱里 torch.cuda.is_available() == False,所以实际用 CPU 跑完。

10.2 Stage A/B:camera-frame human motion smoothing

输出根目录:

submodules/sam-body4d/outputs/paper_plan_motion_smooth_b009_0001_20260425

调参结论:

recommended_best = B2_body_charb_ultra

参数:

base:
  D0 no hand wrist write-back
  body decoder wrist
  hand_pose = median3 + gaussian17 sigma3 blend1

shape/scale:
  temporal median lock from pre_smooth_mhr.npz

body_pose:
  optimize body_pose[:130]
  Charbonnier temporal smooth in MHR parameter space
  lam_acc = 2.00
  lam_jerk = 1.00
  blend = 0.85
  iterations = 6

global_rot:
  unchanged

pred_cam_t:
  unchanged

hand_pose:
  unchanged after D0 hand smoothing

与 D2b 对比的 motion 指标:

upper2d_focus_d3 ratio      = 0.568
wrist2d_focus_d3 ratio      = 0.396
j3d_cam_focus_d3 ratio      = 0.819
body_pose_d3 ratio          = 0.226
pred_cam_focus_d3 ratio     = 1.000
deviation_ratio_vs_D2b      = 0.581
deviation_penalty           = 0.000

解释:

  • motion smooth 指标明显优于 D2b,尤其 wrist 2D jerk 和 body_pose jerk。
  • 平均 deviation 低于 D2b,没有比 D2b 更偏离 per-frame estimate。
  • pred_cam_t smoothing 组虽然能降低部分 j3d/camera-frame jerk,但因为 pred_cam_t 在 D2b 与 pre_smooth 几乎相同,任何改动都会带来很大的 deviation ratio;本轮不采用。
  • B2_body_charb_ultra_keep_first raw score 只比 B2_body_charb_ultra 低约 0.4%,但它回到 first-frame shape/scale,deviation 更高;按“效果相近时优先更少偏离和 temporal median identity”的规则,不作为推荐 best。

Best 输出:

submodules/sam-body4d/outputs/paper_plan_motion_smooth_b009_0001_20260425/B2_body_charb_ultra/smpl_params.npz
submodules/sam-body4d/outputs/paper_plan_motion_smooth_b009_0001_20260425/B2_body_charb_ultra/overlay_2d.mp4
submodules/sam-body4d/outputs/paper_plan_motion_smooth_b009_0001_20260425/B2_body_charb_ultra/4d_visualization.mp4
submodules/sam-body4d/outputs/paper_plan_motion_smooth_b009_0001_20260425/D2b_vs_B2_body_charb_ultra.mp4
submodules/sam-body4d/outputs/paper_plan_motion_smooth_b009_0001_20260425/summary.json

视频编码已验证:

overlay_2d.mp4: h264 / avc1 / yuv420p / 720x1280 / 150 frames
D2b_vs_B2_body_charb_ultra.mp4: h264 / avc1 / yuv420p / 1440x1280 / 150 frames

10.3 Stage C/D:world root/contact optimization

输入:

B2_body_charb_ultra/smpl_params.npz

输出根目录:

submodules/sam-body4d/outputs/paper_plan_world_contact_b009_0001_20260425

对比结果:

best_world_variant = G0_ransac_contact_root_opt

Ground 对比:

G0_ransac ground_z          = ~0.0
G2_contact_offset ground_z  = 0.1251
G1_raw_world contact ground = -0.0307

World/contact 指标:

G0_ransac_contact_root_opt:
  weighted_slide ratio       = 0.804
  penetration ratio          = 0.000
  contact_height ratio       = 0.874
  root_acc ratio             = 0.618
  delta_root p95             = 0.0286 m
  after penetration frames   = 0

G2_contact_offset_contact_root_opt:
  weighted_slide ratio       = 0.675
  penetration ratio          = 0.200
  contact_height ratio       = 1.330
  root_acc ratio             = 0.784
  delta_root p95             = 0.0532 m
  after penetration frames   = 7

G1_raw_world_contact_root_opt:
  weighted_slide ratio       = 0.704
  penetration ratio          = 0.058
  contact_height ratio       = 2.787
  root_acc ratio             = 0.813
  delta_root p95             = 0.0734 m
  after penetration frames   = 4

结论:

  • 对这个序列,RANSAC ground 更可靠。
  • G0_ransac_contact_root_opt 在不引入穿地的情况下减少 foot sliding、contact height error 和 root acceleration。
  • Contact-derived ground offset 虽然降低 raw slide,但引入 contact height 变差和少量 penetration,不适合作为默认替换。
  • 因此在该轮 GVHMR/RANSAC 对比实验中,world/physics 使用 G0_ransac_contact_root_opt

Best world 输出:

submodules/sam-body4d/outputs/paper_plan_world_contact_b009_0001_20260425/G0_ransac_contact_root_opt/world_root.npz
submodules/sam-body4d/outputs/paper_plan_world_contact_b009_0001_20260425/G0_ransac_contact_root_opt/report.json
submodules/sam-body4d/outputs/paper_plan_world_contact_b009_0001_20260425/G0_ransac_contact_root_opt/heel_height_and_slide.png
submodules/sam-body4d/outputs/paper_plan_world_contact_b009_0001_20260425/G0_ransac_contact_root_opt/root_xy.png
submodules/sam-body4d/outputs/paper_plan_world_contact_b009_0001_20260425/summary.json

world_root.npz 内容:

human_root_transl_world: (150, 3)
human_root_orient_world: (150, 3)
delta_root_world:        (150, 3)
joints_world:            (150, 70, 3)
joints_world_before:     (150, 70, 3)
contact_weights:         (150, 2)
T_c2world:               (4, 4)
ground_z:                (1,)

11. 代码合并清单:SAM3D Body 默认 world pipeline

11.1 Config contract

新增或规范化:

reconstruction:
  preprocess:
    motion:
      enabled: true
      model: "sam3d_body"   # default; choices: sam3d_body, gvhmr
    world_frame:
      source: "depth_plane_zup"

配置解释:

motion.model=sam3d_body:
  默认路径。直接使用 SAM3D Body MHR 参数,接 Stage A/B 和 depth-plane world。

motion.model=gvhmr:
  兼容旧路径。调用现有 GVHMR motion/result.pt 逻辑,并通过 adapter 暴露统一结果。

world_frame.source=depth_plane_zup:
  默认路径。直接从 metric/aligned depth 和 K 拟合 camera-frame support plane。

不做 runtime fallback。如果 depth plane 不满足质量阈值,当前默认行为应该 fail fast,并在 report 里写清楚残差、inlier 数和 camera height,而不是自动切到 GVHMR。

11.2 新模块与职责

pipeline/reconstruction/preprocess/human/providers/base.py
  定义 HumanMotionResult 和 provider interface。

pipeline/reconstruction/preprocess/human/providers/sam3d_body.py
  封装 SAM3D Body per-frame export、Stage A/B smoothing、camera-frame materialization。

pipeline/reconstruction/preprocess/human/providers/gvhmr.py
  封装现有 gvhmr.py,保留旧 motion/result.pt 兼容。

pipeline/reconstruction/preprocess/depth/ground_plane.py
  共享 depth backprojection、plane RANSAC、SVD refine、camera-plane -> Z-up transform。

pipeline/reconstruction/preprocess/world_frame.py
  定义 WorldFrameResult,并根据 config 调用 depth_plane_zup 或 gvhmr world provider。

ground_align.py 保留 CLI,但内部改为调用 ground_plane.py。这样旧 GVHMR depth correction 和新 SAM3D Body direct Z-up 使用同一套 plane fitting 实现。

11.3 Data contract

HumanMotionResult 至少包含:

provider
num_frames
camera_frame_npz
camera_joints
camera_vertices
pred_cam_t
focal_length
frame_paths
mask_paths
metadata

WorldFrameResult 至少包含:

source
T_c2world
T_w2c
ground_z
ground_plane_normal_cam
ground_plane_offset_cam
camera_height_m
fit_residual_median_m
fit_inlier_count
metadata

Stage D 只消费这两个 dataclass,不直接读取 GVHMR 或 SAM3D Body 私有文件。

11.4 SAM3D Body 默认流程

1. Human provider:
   SAM3D Body -> pre_smooth_mhr.npz
   -> temporal median shape/scale lock (Stage A)

2. World provider (Stage C, pre-computation):
   depth.npy + K_seq + optional human/object exclusion masks
   -> camera-frame support plane fit
   -> T_c2world_depth_plane, T_w2c_depth_plane, ground_z=0
   -> world_frame_report.json

3. Joint world-space optimization (Stage B):
   input: pre_smooth_mhr.npz + T_c2world (fixed constant)
   variables: global_rot (T,3) + body_pose (T,130) + pred_cam_t (T,3)
   each iter:
     batch mhr_forward -> j3d_local, joint_global_rots  (T, J, ...)
     j3d_world = T_c2w @ j3d_cam                        (T, J, 3)
     loss = L_latent + L_smooth(j3d_world, rots) + L_contact(j3d_world)
   Adam, lr=1e-3, 100 iter, ~1-5s CPU
   output: smoothed_world_mhr.npz

4. Physics:
   consume smoothed_world_mhr.npz (contains j3d_world, world root, contact weights).

11.5 验收标准

第一版合并完成必须输出:

pre_smooth_mhr.npz
smoothed_camera_mhr.npz
overlay_2d.mp4
world_frame_report.json
world_root.npz
heel_height_and_slide.png
root_xy.png

world_frame_report.json 必须包含:

human_provider = sam3d_body
world_source = depth_plane_zup
T_c2world
T_w2c
ground_z = 0
camera_height_m
fit_residual_median_m
fit_inlier_count / fit_total_count
roi_policy

合并后的默认配置不再要求 motion/result.pt 来自 GVHMR。只有 motion.model=gvhmr 时,才允许 Stage C/D 读取 GVHMR motion metadata。