Mujoco 学习系列(三)机器人状态IO与仿真操作

这篇博客是继上一篇如何写机器人描述文件 xml 后的更进一步,因为我们不可能从 GUI 中读机器人各个关节或状态信息,所以需要用 mujoco 接口从仿真器中获取位置、速度、力等数据,同时也能够向仿真中写入数据。


1. 获取 mjData 状态

首先以最简单的滑块为例,定义一个简单的描述文件 merge.xml,这个描述文件中滑块在地面上,并且提供了一个执行器,运行后你可以通过仿真器右侧的 Control 面板来给滑块施加位置环伺服。

<mujoco>
  <worldbody>
    <light diffuse="0.5 0.5 0.5" pos="0 0 5" dir="0 0 -1"/> 

    <geom name="ground" type="plane" size="5 5 0.1" pos="0 0 0" 
          rgba="0.5 0.5 0.5 1" solimp="0.9 0.95 0.001" solref="0.02 1"/>

    <body name="slider" pos="0 0 0.1">
      <joint name="slide_joint" type="slide" axis="1 0 0" damping="0.2" range="-2 2"/>
      <geom type="box" size="0.2 0.1 0.1" rgba="0 0.5 0.8 1" mass="0.5" />
    </body>
  </worldbody>

  <actuator>
    <position name="position" joint="slide_joint" kp="100" kv="10"/>
  </actuator>
</mujoco>

然后编写一个 python 脚本 demo.py,因为关节信息是基础元素,所以可以直接从 data 数据类型中获得:

import mujoco
import mujoco.viewer

# 载入模型
model_path = "./merge.xml"
model = mujoco.MjModel.from_xml_path(model_path)
data = mujoco.MjData(model)

with mujoco.viewer.launch_passive(model, data) as viewer:
    while viewer.is_running():
        mujoco.mj_step(model, data)
        
        # 获取滑块位置:直接读取关节位置
        slider_pos = data.qpos[0]  # 这里只有一个滑动关节
        print(f"Slider position: {slider_pos:.4f}")
        
        viewer.sync()

运行示例:

(mujoco) $ python demo.py

拖拽右侧 Control 面板的位置滑块可以看见 terminal 中的位置数据信息变化:

在这里插入图片描述

通过查看官网文档中对 MjData 数据类型的描述可获得更丰富的属性。由于API接口的统一性,你可以直接调用结构体里面的对象,上面的python脚本中就是直接获取了 qpos 成员变量:

在这里插入图片描述

这种形式一旦关节多起来就非常容易混淆,特别是还需要一个一个去辨别索引号,因此也可以通过下面的方式找到指定 joint 的信息:

import mujoco
import mujoco.viewer

model_path = "./merge.xml"
model = mujoco.MjModel.from_xml_path(model_path)
data = mujoco.MjData(model)

with mujoco.viewer.launch_passive(model, data) as viewer:
    while viewer.is_running():
        mujoco.mj_step(model, data)
        
        # 用name的方式找到明确的关节
        joint_id = model.joint(name="slide_joint").id
        slider_pos = data.qpos[joint_id]
        print(f"Slider position: {slider_pos:.4f}")
        
        viewer.sync()

这就是我在前两篇博客中一直强调的 给关节显示声明name属性 的意义,如果你在 xml 中没有显示定义关节名,那么在获取关节时就只能一个一个去找对应的 index 。复杂构型的机器人光是这一步就可以消耗掉一天的时间。


2. 获取 sensor 状态

mujoco 还提供了一种方式是通过传感器 sensor 获取机器人状态,以这种方式获取时需要在 xml 文件中定义传感器及其类型,这里以关节位置传感器 jointpos 为例,对上面的 xml 文件进行修改:

<mujoco>
  <worldbody>
    <light diffuse="0.5 0.5 0.5" pos="0 0 5" dir="0 0 -1"/> 

    <geom name="ground" type="plane" size="5 5 0.1" pos="0 0 0" 
          rgba="0.5 0.5 0.5 1" solimp="0.9 0.95 0.001" solref="0.02 1"/>

    <body name="slider" pos="0 0 0.1">
      <joint name="slide_joint" type="slide" axis="1 0 0" damping="0.2" range="-2 2"/>
      <geom type="box" size="0.2 0.1 0.1" rgba="0 0.5 0.8 1" mass="0.5" />
    </body>
  </worldbody>

  <actuator>
    <position name="position" joint="slide_joint" kp="100" kv="10"/>
  </actuator>

  <!-- 添加关节位置传感器 -->
  <sensor>
    <jointpos name="joint_sensor" joint="slide_joint" />
  </sensor>
</mujoco>

上面的文件中通过使用 <sensor> 标签定义了一个针对 slide_joint 的关节位置传感器,那么 python 脚本中也需要进行一些修改:

import mujoco
import mujoco.viewer

model_path = "./merge.xml"
model = mujoco.MjModel.from_xml_path(model_path)
data = mujoco.MjData(model)

with mujoco.viewer.launch_passive(model, data) as viewer:
    while viewer.is_running():
        mujoco.mj_step(model, data)
        
        # 获取滑块位置:通过joint
        joint_id = model.joint(name="slide_joint").id
        slider_pos = data.qpos[joint_id]
        # 获取滑块位置:通过sensor
        sensor_id = model.sensor(name="joint_sensor").id
        sensor_value = data.sensordata[sensor_id]
        print(f"Slider position: {slider_pos:.4f} | Sensor position: {sensor_value:.4f}")
        
        viewer.sync()

运行后可以发现有持续的值打印:

(mujoco) $ python demo.py 

在这里插入图片描述

此时你可能会问,我都有关节信息了为啥还要额外添加一个传感器来获取相同的信息,而且两者的读数还完全一样。那是因为我们现在的示例还是非常初级的阶段,我个人在使用时通常会将两者同时获取,理由如下:

  1. joint 数据是真值,而 sensor 是外界能够直接获取的状态估计,为此在仿真环境中可以对传感器数据添加噪声以更贴近实际情况;
  2. 当前只对关节状态进行了仿真,传感器还有 camera、touch、force 等诸多模式;
  3. 可以通过设置环境属性来影响传感器的输出,比如添加电场、磁场等;

更多传感器及其类型可以通过官网链接查看:

在这里插入图片描述


3. 设置 joint 值

通过代码可以获取机器人 joint 值自然也可对其进行设置,这是机器人控制的基础功能,编写 xml 文件如下:

<mujoco>
  <worldbody>
    <light diffuse="0.5 0.5 0.5" pos="0 0 5" dir="0 0 -1"/> 

    <geom name="ground" type="plane" size="5 5 0.1" pos="0 0 0" 
          rgba="0.5 0.5 0.5 1" solimp="0.9 0.95 0.001" solref="0.02 1"/>

    <body name="slider" pos="0 0 0.1">
      <joint name="slide_joint" type="slide" axis="1 0 0" damping="0.2" range="-2 2"/>
      <geom type="box" size="0.2 0.1 0.1" rgba="0 0.5 0.8 1" mass="0.5" />
    </body>
  </worldbody>

  <actuator>
    <position name="position" joint="slide_joint" kp="100" kv="10"/>
  </actuator>
</mujoco>

再写一个 python 脚本用来给机器人位置以线性方式进行增长:

import mujoco
import mujoco.viewer

model_path = "./merge.xml"
model = mujoco.MjModel.from_xml_path(model_path)
data = mujoco.MjData(model)

with mujoco.viewer.launch_passive(model, data) as viewer:
    step = 0
    while viewer.is_running():
        # 施加控制信号,使滑块向右滑动
        ctrl_signal = min(1.0, 0.000001 * step)  # 控制信号线性增长,最大为1(由 ctrlrange 限制)
        data.ctrl[0] = ctrl_signal

        mujoco.mj_step(model, data)
        
        # 读取并打印滑块位置
        slider_pos = data.qpos[0]
        print(f"Step {step} | Control: {ctrl_signal:.3f} | Slider Position: {slider_pos:.4f}")

        viewer.sync()
        step += 1

同样可以通过指定关节名的形式修改位置状态,推荐使用这种:

import mujoco
import mujoco.viewer

model_path = "./merge.xml"
model = mujoco.MjModel.from_xml_path(model_path)
data = mujoco.MjData(model)

# 获取 actuator 的索引
actuator_id = model.actuator(name="position").id

# 启动可视化窗口
with mujoco.viewer.launch_passive(model, data) as viewer:
    step = 0
    while viewer.is_running():
        # 设置控制信号(使用 actuator_id 替代 data.ctrl[0])
        ctrl_signal = min(1.0, 0.000001 * step)
        data.ctrl[actuator_id] = ctrl_signal

        mujoco.mj_step(model, data)
        print(f"Step {step} | ctrl={ctrl_signal:.3f} | pos={data.qpos[0]:.4f}")
        viewer.sync()
        step += 1

运行后可以看到滑块在缓慢移动,即关节位置以线性的方式被修改:

(mujoco) $ python demo.py

在这里插入图片描述


4. 不开启GUI界面运行

后面的绝大多数实验会在 GPU 服务器上运行,GPU 服务器通常不会有 GUI 相关的可执行文件,mujoco 实现无 GUI 运行也非常简单,下面这个例子就写了一个循环,当超时后自动结束仿真:

import mujoco
import mujoco.viewer
import time

model_path = "./merge.xml"
model = mujoco.MjModel.from_xml_path(model_path)
data = mujoco.MjData(model)

# 获取 actuator 的索引
actuator_id = model.actuator(name="position").id

# 仿真主循环
step = 1
start_time = time.time()
while True:
    ctrl = min(1.0, 0.000001 * step)
    data.ctrl[actuator_id] = ctrl

    mujoco.mj_step(model, data)

    pos = data.qpos[0]
    print(f"Step {step} | ctrl={ctrl:.3f} | pos={pos:.4f}")
    step += 1
    if time.time() - start_time > 5.0:
        break
    
print("Simulate done.")

运行结果如下:

(mujoco) $ python demo.py

Step 537599 | ctrl=0.538 | pos=0.5375
Step 537600 | ctrl=0.538 | pos=0.5376
Step 537601 | ctrl=0.538 | pos=0.5376
Step 537602 | ctrl=0.538 | pos=0.5376
Step 537603 | ctrl=0.538 | pos=0.5376
Simulate done.
...


5. 重置仿真

重置方针条件也是进行大规模试验的基本需求,如果每次仿真结束都重启程序的话会导致大量资源消耗在仿真器初始化上,大模型训练和测试都以 K 为单位起步,因此 mujoco 也提供了重置仿真环境的功能:

import mujoco
import mujoco.viewer
import time

model_path = "./merge.xml"
model = mujoco.MjModel.from_xml_path(model_path)
data = mujoco.MjData(model)

actuator_id = model.actuator(name="position").id

start_time = time.time()
step = 0

with mujoco.viewer.launch_passive(model, data) as viewer:
    while viewer.is_running():
        ctrl_signal = min(1.0, 0.000001 * step)
        data.ctrl[actuator_id] = ctrl_signal

        mujoco.mj_step(model, data)
        print(f"Step {step} | ctrl={ctrl_signal:.3f} | pos={data.qpos[0]:.4f} | during time={time.time()-start_time}")
        viewer.sync()
        step += 1
        
        if time.time() - start_time > 5:
            start_time = time.time()
            print("Ready to reset simulation")
            mujoco.mj_resetData(model, data)    # 重置仿真环境
            print("Simulation reset done")
            step = 0    

上面的代码核心就是这句 mujoco.mj_resetData(model, data) ,运行后每隔 5s 就会重置一次环境,滑块会瞬间跳回到初始位置。

在这里插入图片描述

除了一口气将仿真环境完全重制外,还可以结合第 3 节的内容通过给关节赋值来让关节到指定姿态上,这种情况一般会用在 VLA 模型某个case执行失败后,让其回到如第 5 秒的姿态后以该姿态为初始条件重新生成动作序列。


6. 固定视角录制仿真视频

固定视角录制仿真环境中的视频也是基础需求之一,这可以用来训练模型和 bad case review,使用下面的代码可以实现 mp4 格式的视频录制:

import mujoco
import imageio
import numpy as np
import time
import mujoco
import mujoco.viewer

model_path = "./merge.xml"
model = mujoco.MjModel.from_xml_path("./merge.xml")
data = mujoco.MjData(model)
renderer = mujoco.Renderer(model, width=640, height=480)

actuator_id = model.actuator(name="position").id

# 视频录制
frames = []
total_steps = 10000
record_fps = 30                         # 录制的帧率
record_time_iter = 1.0 / record_fps     # 通过fps计算两帧的时间间隔
last_frame_time = time.time()           # 上一帧的时间
g_start_time = time.time()

# 启动可视化窗口
with mujoco.viewer.launch_passive(model, data) as viewer:
    step = 0
    while viewer.is_running():
        ctrl_signal = min(1.0, 0.000001 * step)
        data.ctrl[actuator_id] = ctrl_signal

        mujoco.mj_step(model, data)
        print(f"Step {step} | ctrl={ctrl_signal:.3f} | pos={data.qpos[0]:.4f}")
        viewer.sync()
        step += 1
        
        # 录制视频
        if time.time() - last_frame_time > record_time_iter:
            renderer.update_scene(data)
            frame = renderer.render()
            frames.append(np.copy(frame))
            last_frame_time = time.time()
            
        # 超时跳出
        if time.time() - g_start_time > 10:
            break
            
imageio.mimsave("./simulation.mp4", frames, fps=30)
print(f"total simulate time: {time.time() - g_start_time}")

运行后可以在当前路径下生成一个 simulation.mp4 文件:

(mujoco) $ python demo.py
	...
total simulate time: 10.277767
(mujoco) $ ll | grpe mp4
-rw-rw-r--  1 gaohao gaohao  35K May 21 14:22 simulation.mp4

通常来说固定视角的视频录制是用来批量生成仿真数据的,由于 mujoco 的框架设计,你可以在 GPU 服务器上一口气创建数万个仿真环境(假设资源充足的话),然后同时生成视频数据用于后续模型训练。


至此,有关 mujoco 最基础的原生功能已经介绍完了,后面的博客将会结合 ROS、Isaac Sim、VLA 等进行联合使用。总体而言 mujoco 在使用上比较简洁,虽然GUI显示效果和多样性落后于 Rviz、Isaac,但能够以非常小计算资源跑起来就是很优秀的作品,因为在 VLA 领域中模型本身就非常吃算力,如果仿真又吃掉一大块资源容易导致模型 out of memory。

<think>嗯,用户想了解或使用MuJoCo进行仿真,需要教程或解决方案。首先,我应该先确认用户对MuJoCo的了解程度,但根据问题描述,用户可能已经有一些基础,比如知道MuJoCo是做机器人仿真的,但需要具体的入门指导。 接下来,我需要整理MuJoCo的基本信息,比如安装步骤、核心概念、基础操作流程,还有相关的学习资源。用户提到的引用[1]和[2]中提到MuJoCo的正向和反向动力学,以及开源项目,这些可以作为参考点。 安装部分,用户可能需要知道如何获取许可证,安装Python包,以及验证安装。核心概念方面,MJCF文件结构、刚体动力学、传感器执行器、接触模型这些是关键点,需要简明扼要地解释。 然后,基础操作流程分步骤说明,从模型创建到仿真循环,再到数据可视化。这部分需要结合代码示例,比如使用mujoco库加载模型、创建数据对象、仿真循环的代码结构。引用[2]中提到的正向动力学和状态更新,可以在仿真步骤部分引用。 学习资源方面,官方文档、教程、社区论坛和开源项目都是重要的。用户提到的开源项目Self balancing robot simulation可以作为示例,引导用户参考实际项目学习。 可能用户还会遇到问题,所以需要提供常见问题解答,比如模型加载失败、仿真不稳定、如何添加传感器等。这部分要基于常见错误和经验给出解决方案。 最后,生成相关问题,覆盖安装、核心概念、调试、应用场景和高级功能,这样用户可以根据需要进一步探索。需要确保每个问题都能引导用户深入不同方面的学习。 总结下来,回答需要结构清晰,步骤分明,结合代码示例和引用,同时提供进一步学习的资源和问题,帮助用户逐步掌握MuJoCo的使用。</think>### MuJoCo 仿真使用教程解决方案 #### 一、MuJoCo 基础准备 1. **安装许可证** - 从[官方网站](https://mujoco.org/)获取许可证并下载 MuJoCo 2.3.0+ 版本,支持 Windows/Linux/macOS 系统。 - 安装 Python 接口:`pip install mujoco`,推荐使用虚拟环境[^1]。 - 验证安装: ```python import mujoco model = mujoco.MjModel.from_xml_path("scene.xml") ``` 2. **核心概念** - **MJCF 文件**:基于 XML 的模型描述格式,定义机器人结构、关节、传感器等[^2]。 - **刚体动力学**:支持连续时间正向/反向动力学计算,适用于高精度物理仿真。 - **传感器执行器**:可模拟 IMU、力矩传感器、电机等组件。 - **接触模型**:支持精确的碰撞检测摩擦计算。 #### 二、基础操作流程(代码示例) ```python import mujoco import matplotlib.pyplot as plt # 1. 模型加载 model = mujoco.MjModel.from_xml_path("humanoid.xml") data = mujoco.MjData(model) # 2. 仿真循环 for _ in range(1000): mujoco.mj_step(model, data) # 正向动力学计算 # 3. 数据可视化 plt.plot(data.qpos) # 关节位置变化曲线 plt.show() ``` #### 、进阶应用场景 1. **强化学习训练** - OpenAI Gym 集成,搭建训练环境: ```python import gymnasium as gym env = gym.make("Humanoid-v4") ``` 2. **控制器设计** - 实现 PD 控制: ```python Kp = 100.0 # 比例增益 Kd = 10.0 # 微分增益 target_pos = 0.5 data.ctrl[0] = Kp*(target_pos - data.qpos[0]) - Kd*data.qvel[0] ``` #### 四、学习资源推荐 1. **官方文档**:[MuJoCo Programming Guide](https://mujoco.readthedocs.io/) 2. **视频教程**:YouTube 官方频道提供动力学原理详解 3. **开源项目参考**:如双足机器人步态优化项目 #### 五、常见问题解答 | 问题现象 | 解决方案 | |---------|----------| | 模型加载失败 | 检查 XML 文件层级结构,确保`<body>`标签正确嵌套 | | 仿真不稳定 | 降低仿真步长 `model.opt.timestep`,或增加数值积分精度 | | 传感器数据异常 | 确认传感器在 MJCF 中的定义位置类型 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值