Spaces:
Running on Zero
Running on Zero
| """ | |
| MuJoCo 离屏渲染:DOF + root pose → 带 mesh 的机器人动画视频 (MP4)。 | |
| """ | |
| import os | |
| # 设置 MuJoCo 离屏渲染后端 (必须在 import mujoco 之前) | |
| # EGL 用于有 GPU 的环境, osmesa 用于纯 CPU 环境 (如 HF Spaces) | |
| if 'MUJOCO_GL' not in os.environ: | |
| try: | |
| import ctypes | |
| ctypes.cdll.LoadLibrary('libEGL.so.1') | |
| os.environ['MUJOCO_GL'] = 'egl' | |
| except OSError: | |
| os.environ['MUJOCO_GL'] = 'osmesa' | |
| import tempfile | |
| import numpy as np | |
| import mujoco as mj | |
| import imageio | |
| BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| XML_PATH = os.path.join(BASE_DIR, 'assets', 'g1_mocap_29dof.xml') | |
| # inference 输出 DOF 顺序 → MuJoCo XML joint 顺序的映射 | |
| JOINT_MAPPING = [ | |
| 0, 6, 12, | |
| 1, 7, 13, | |
| 2, 8, 14, | |
| 3, 9, 15, 22, | |
| 4, 10, 16, 23, | |
| 5, 11, 17, 24, | |
| 18, 25, | |
| 19, 26, | |
| 20, 27, | |
| 21, 28, | |
| ] | |
| def create_skeleton_animation(dof, rot_quat, transl, fps=30, | |
| width=1920, height=1088, | |
| camera_distance=2.5, camera_elevation=-15): | |
| """渲染 G1 机器人 mesh 动画并输出 MP4 视频。 | |
| Args: | |
| dof: (T, 29) 关节角度 | |
| rot_quat: (T, 4) wxyz 四元数 | |
| transl: (T, 3) 根位置 | |
| fps: 输出视频帧率 | |
| width, height: 渲染分辨率 | |
| camera_distance: 摄像机距离 | |
| camera_elevation: 摄像机俯仰角 (度) | |
| Returns: | |
| str: MP4 视频文件路径 | |
| """ | |
| model = mj.MjModel.from_xml_path(XML_PATH) | |
| # 确保离屏帧缓冲区足够大 | |
| model.vis.global_.offwidth = max(model.vis.global_.offwidth, width) | |
| model.vis.global_.offheight = max(model.vis.global_.offheight, height) | |
| data = mj.MjData(model) | |
| renderer = mj.Renderer(model, height=height, width=width) | |
| # 摄像机设置 | |
| camera = mj.MjvCamera() | |
| camera.distance = camera_distance | |
| camera.elevation = camera_elevation | |
| camera.azimuth = 90 | |
| pelvis_id = model.body('pelvis').id | |
| T = dof.shape[0] | |
| # 映射 DOF 顺序 | |
| mapped_dof = np.zeros((T, model.nq - 7), dtype=np.float32) | |
| for i, jm in enumerate(JOINT_MAPPING): | |
| mapped_dof[:, jm] = dof[:, i] | |
| # 渲染到视频 | |
| video_path = os.path.join(tempfile.gettempdir(), 'g1_motion.mp4') | |
| writer = imageio.get_writer(video_path, fps=fps, codec='libx264') | |
| for t in range(T): | |
| data.qpos[:3] = transl[t] | |
| data.qpos[3:7] = rot_quat[t] # wxyz, MuJoCo 原生格式 | |
| data.qpos[7:] = mapped_dof[t] | |
| mj.mj_forward(model, data) | |
| # 摄像机跟踪 pelvis | |
| camera.lookat[:] = data.xpos[pelvis_id] | |
| renderer.update_scene(data, camera=camera) | |
| img = renderer.render() | |
| writer.append_data(img) | |
| writer.close() | |
| renderer.close() | |
| return video_path | |