Spaces:
Running on Zero
Running on Zero
| """ | |
| 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}") | |
| 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() | |