NMR / app.py
RayZhao's picture
update zerogpu
f8dc0df
"""
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()