File size: 2,813 Bytes
4cc0d6c
4417ac0
4cc0d6c
 
4417ac0
 
 
 
 
 
 
 
 
 
 
 
4cc0d6c
4417ac0
 
4cc0d6c
 
4417ac0
 
 
 
 
 
 
 
 
 
 
 
 
 
4cc0d6c
 
 
4417ac0
 
 
 
4cc0d6c
 
 
 
 
4417ac0
 
 
 
4cc0d6c
 
4417ac0
4cc0d6c
4417ac0
 
 
 
 
 
 
 
 
 
 
 
4cc0d6c
4417ac0
4cc0d6c
 
4417ac0
 
 
4cc0d6c
4417ac0
4cc0d6c
4417ac0
 
 
4cc0d6c
4417ac0
 
 
 
4cc0d6c
4417ac0
4cc0d6c
4417ac0
 
4cc0d6c
4417ac0
 
 
4cc0d6c
4417ac0
 
4cc0d6c
4417ac0
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
"""
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