NMR / visualize.py
RayZhao's picture
update visulization
4417ac0
"""
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