File size: 3,637 Bytes
4cc0d6c
 
 
4417ac0
4cc0d6c
 
 
 
 
 
 
 
 
 
 
 
4417ac0
4cc0d6c
f8dc0df
4cc0d6c
 
4417ac0
 
4cc0d6c
f8dc0df
4cc0d6c
 
 
 
 
f8dc0df
4cc0d6c
4417ac0
4cc0d6c
 
 
7980741
4cc0d6c
 
 
 
f8dc0df
 
 
 
 
4cc0d6c
 
 
f8dc0df
4cc0d6c
 
 
 
 
4417ac0
 
 
 
4cc0d6c
4417ac0
 
4cc0d6c
 
 
 
4417ac0
4cc0d6c
4417ac0
4cc0d6c
 
 
 
4417ac0
 
4cc0d6c
 
4417ac0
4cc0d6c
 
 
 
 
 
 
4417ac0
 
4cc0d6c
 
 
 
 
4417ac0
 
4cc0d6c
4417ac0
 
4cc0d6c
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
"""
Gradio Demo: Human → G1 Robot Motion Retargeting

上传 AMASS 格式 NPZ 文件,推理并展示 3D 机器人动画,导出 bmimic 格式。
"""
import os
import sys
import tempfile

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

import torch
# PyTorch 2.6 兼容 patch
_torch_load = torch.load
torch.load = lambda *args, **kwargs: _torch_load(*args, weights_only=kwargs.pop('weights_only', False), **kwargs)

import numpy as np
import gradio as gr
import spaces

from inference import load_all, infer_single
from visualize import create_skeleton_animation, XML_PATH
from convert_bmimic import convert_to_bmimic

# ---------- 启动时加载模型(CPU)----------
print("Loading model...")
model, smplx_model, betas, smplx_mean, smplx_std, g1_mean, g1_std, device = load_all()
print(f"Model loaded on {device}")


@spaces.GPU
def predict(input_file):
    """推理、可视化、导出 bmimic 格式。"""
    if input_file is None:
        return None, None, "Please upload an AMASS NPZ file."

    file_path = input_file if isinstance(input_file, str) else input_file.name

    if not file_path.endswith('.npz'):
        return None, None, "Error: only .npz files are supported."

    # ZeroGPU: 在 @spaces.GPU 函数内才有 GPU,需要将模型移到 CUDA
    gpu_device = torch.device('cuda')
    model.to(gpu_device)
    smplx_model.to(gpu_device)

    result, timing = infer_single(
        file_path, model, smplx_model, betas,
        smplx_mean, smplx_std, g1_mean, g1_std,
        gpu_device, apply_filter=True,
    )

    if result is None:
        return None, None, "Error: sequence too short (< 4 frames)."

    # 转换为 bmimic npz 格式 (30fps → 50fps)
    bmimic_data = convert_to_bmimic(result, XML_PATH, device, tgt_fps=50.0, src_fps=30.0)
    output_path = os.path.join(tempfile.gettempdir(), 'g1_motion.npz')
    np.savez(output_path, **bmimic_data)

    # 渲染 3D 机器人动画视频
    video_path = create_skeleton_animation(
        result['dof'], result['root_rot_quat'], result['root_trans'],
    )

    T = result['dof'].shape[0]
    T_bmimic = bmimic_data['joint_pos'].shape[0]
    info = (
        f"Frames: {T} ({T / 30:.1f}s @ 30 FPS) → bmimic: {T_bmimic} frames @ 50 FPS\n"
        f"Preprocess: {timing['preprocess']:.2f}s | "
        f"Inference: {timing['infer']:.2f}s | "
        f"Postprocess: {timing['postprocess']:.2f}s | "
        f"Total: {timing['total']:.2f}s\n"
        f"Output keys: fps, joint_pos, joint_vel, body_pos_w, body_quat_w, "
        f"body_lin_vel_w, body_ang_vel_w"
    )

    return video_path, output_path, info


# ---------- Gradio UI ----------
demo = gr.Interface(
    fn=predict,
    inputs=gr.File(label="Upload AMASS NPZ file", file_types=[".npz"]),
    outputs=[
        gr.Video(label="G1 Robot Animation"),
        gr.File(label="Download Result (bmimic NPZ)"),
        gr.Textbox(label="Info", lines=3),
    ],
    title="Human → G1 Robot Motion Retargeting",
    description=(
        "Upload human motion capture data in AMASS format (.npz), "
        "and get Unitree G1 humanoid robot motion in BeyondMimic format.\n\n"
        "**Input**: AMASS NPZ with fields `trans/root_orient/pose_body` "
        "(or `transl/global_orient/body_pose`), optionally `mocap_frame_rate`.\n\n"
        "**Output**: bmimic NPZ (50 FPS) with `joint_pos`, `joint_vel`, "
        "`body_pos_w`, `body_quat_w`, `body_lin_vel_w`, `body_ang_vel_w`."
    ),
    examples=[["examples/sample_motion.npz"]] if os.path.exists("examples/sample_motion.npz") else None,
    cache_examples=False,
)

if __name__ == '__main__':
    demo.launch()