一、MuJoCo 简介
MuJoCo(Multi-Joint dynamics with Contact)即多关节接触动力学,是一款免费开源的物理引擎,旨在助力机器人、生物力学、图形动画等领域的研究与开发。它将速度、精度和建模能力独特融合,专为基于模型的优化,尤其是通过接触进行的优化而设计。其具备以下关键特性:
广义坐标与现代接触动力学结合:融合机器人学、生物力学引擎在广义坐标上的高效递归算法,以及游戏引擎基于优化求解接触力的现代方法。
接触动力学处理:通过凸优化问题求解接触力,支持软接触、多种约束,能统一处理摩擦接触、关节限制等情况。
丰富的建模支持:可模拟肌腱几何结构、提供通用驱动模型、拥有可重构计算流水线,还支持模型编译以优化运行时计算。
二、安装环境
1 安装 Anaconda
从Anaconda 官方网站下载对应操作系统的安装包,按向导提示完成安装。
2 创建 Conda 环境
打开 Anaconda Prompt(Windows)或终端(Linux/Mac),执行以下命令创建新环境
conda create -n mujoco_env python=3.8
其中,mujoco_env为环境名,可自行修改;python=3.9指定 Python 版本。
3 激活环境
创建好环境后,执行以下命令激活:
conda activate mujoco_env
4 安装环境
在激活的 Conda 环境中,执行以下命令安装:
pip install mujoco numpy imageio glfw
三 常用函数简介
MuJoCo提供了丰富的API和函数,用于创建、模拟和可视化物理模型。以下是一些常用API和函数的介绍及用法:
模型加载与初始化
mujoco.MjModel.from_xml_path
- 功能:从XML文件中加载MuJoCo模型。
- 用法示例:
# 从指定路径的XML文件加载模型
model = mujoco.MjModel.from_xml_path('path/to/your/model.xml')
mujoco.MjData
- 功能:创建一个与模型对应的
MjData
对象,用于存储模型在模拟过程中的各种数据,如关节位置、速度、力等。 - 用法示例:
model = mujoco.MjModel.from_xml_path('path/to/your/model.xml')
# 创建MjData对象
data = mujoco.MjData(model)
模拟控制
mujoco.mj_step
- 功能:执行一次模拟步骤,更新模型的状态(如位置、速度等)。
- 用法示例:
model = mujoco.MjModel.from_xml_path('path/to/your/model.xml')
data = mujoco.MjData(model)
# 执行一次模拟步骤
mujoco.mj_step(model, data)
data.ctrl
- 功能:用于设置模型的控制输入,例如关节的驱动力矩。
- 用法示例:
import numpy as np
model = mujoco.MjModel.from_xml_path('path/to/your/model.xml')
data = mujoco.MjData(model)
# 设置控制输入,这里假设控制输入是一个长度为3的数组
control_input = np.array([1.0, 2.0, 3.0])
data.ctrl[:] = control_input
获取模型信息
data.qpos
- 功能:获取模型中所有关节的位置(广义坐标)。
- 用法示例:
model = mujoco.MjModel.from_xml_path('path/to/your/model.xml')
data = mujoco.MjData(model)
# 获取关节位置
joint_positions = data.qpos
print(joint_positions)
data.qvel
- 功能:获取模型中所有关节的速度(广义速度)。
- 用法示例:
model = mujoco.MjModel.from_xml_path('path/to/your/model.xml')
data = mujoco.MjData(model)
# 获取关节速度
joint_velocities = data.qvel
print(joint_velocities)
data.geom('geom_name').xpos
- 功能:获取指定几何体的位置。
- 用法示例:
model = mujoco.MjModel.from_xml_path('path/to/your/model.xml')
data = mujoco.MjData(model)
# 获取名为'my_geom'的几何体的位置
geom_position = data.geom('my_geom').xpos
print(geom_position)
可视化相关
mujoco.MjvScene
- 功能:创建一个用于存储要渲染的场景信息的对象。
- 用法示例:
import mujoco
model = mujoco.MjModel.from_xml_path('path/to/your/model.xml')
data = mujoco.MjData(model)
# 创建MjvScene对象
scene = mujoco.MjvScene(model, maxgeom=10000)
mujoco.mjv_updateScene
- 功能:根据模型和数据的当前状态更新场景中的几何体、光照等信息。
- 用法示例:
import mujoco
import glfw
model = mujoco.MjModel.from_xml_path('path/to/your/model.xml')
data = mujoco.MjData(model)
scene = mujoco.MjvScene(model, maxgeom=10000)
opt = mujoco.MjvOption()
perturb = mujoco.MjvPerturb()
cam = mujoco.MjvCamera()
mujoco.mjv_defaultCamera(cam)
# 初始化GLFW并创建窗口等操作...
# 更新场景信息
mujoco.mjv_updateScene(model, data, opt, perturb, cam, mujoco.mjtCatBit.mjCAT_ALL, scene)
mujoco.mjr_render
- 功能:将更新后的场景信息渲染到指定的视口区域。
- 用法示例:
import mujoco
import glfw
model = mujoco.MjModel.from_xml_path('path/to/your/model.xml')
data = mujoco.MjData(model)
scene = mujoco.MjvScene(model, maxgeom=10000)
opt = mujoco.MjvOption()
perturb = mujoco.MjvPerturb()
cam = mujoco.MjvCamera()
mujoco.mjv_defaultCamera(cam)
# 初始化GLFW并创建窗口等操作...
viewport = mujoco.MjrRect(0, 0, 1200, 900) # 假设视口大小为1200x900
context = mujoco.MjrContext(model, mujoco.mjtFontScale.mjFONTSCALE_150)
# 渲染场景
mujoco.mjr_render(viewport, scene, context)
传感器相关
data.sensordata
- 功能:获取模型中所有传感器的数据。
- 用法示例:
model = mujoco.MjModel.from_xml_path('path/to/your/model.xml')
data = mujoco.MjData(model)
# 执行模拟步骤以更新传感器数据
mujoco.mj_step(model, data)
# 获取传感器数据
sensor_data = data.sensordata
print(sensor_data)
以上只是MuJoCo中一些常用API和函数的介绍,MuJoCo还有许多其他功能强大的API可以用于更复杂的模拟和分析任务。你可以参考MuJoCo官方文档以获取更详细的信息。
四 示例
1 创建模型配置文件
创xml文件:model1.xml
<mujoco>
<worldbody>
<!-- 机械臂底座 -->
<body name="base" pos="0 0 0">
<joint name="joint1" type="hinge" axis="0 1 0"/>
<geom name="base_geom" type="cylinder" size="0.1 0.1"/>
<body name="arm1" pos="0 0.1 0">
<joint name="joint2" type="hinge" axis="0 1 0"/>
<geom name="arm1_geom" type="cylinder" size="0.05 0.5"/>
<body name="arm2" pos="0 0.5 0">
<joint name="joint3" type="hinge" axis="0 1 0"/>
<geom name="arm2_geom" type="cylinder" size="0.05 0.3"/>
<body name="end_effector" pos="0 0.3 0">
<geom name="ee_geom" type="sphere" size="0.05"/>
</body>
</body>
</body>
</body>
<!-- 小球 -->
<body name="ball" pos="0.5 0 0.5">
<geom name="ball_geom" type="sphere" size="0.03" rgba="1 0 0 1"/>
</body>
</worldbody>
<actuator>
<!-- 为每个关节添加一个执行器 -->
<motor joint="joint1" ctrlrange="-1 1"/>
<motor joint="joint2" ctrlrange="-1 1"/>
<motor joint="joint3" ctrlrange="-1 1"/>
</actuator>
</mujoco>
2 编写python脚本
# 导入 mujoco 库,用于进行物理模拟和模型加载等操作
import mujoco
# 导入 numpy 库,用于处理数值计算,例如数组操作等
import numpy as np
# 导入 glfw 库,用于创建窗口和处理图形渲染的上下文等
import glfw
# 加载模型,从指定的 XML 文件 'model1.xml' 中读取模型信息
model = mujoco.MjModel.from_xml_path('model1.xml')
# 创建与模型对应的 MjData 对象,用于存储模型在模拟过程中的各种数据
data = mujoco.MjData(model)
# 定义模拟的总时间,单位为秒
sim_time = 20.0
# 定义控制频率,即每秒进行控制计算的次数
control_freq = 100
# 计算控制周期,控制周期是控制频率的倒数,单位为秒
control_period = 1.0 / control_freq
# 定义目标位置,这是机械臂末端要到达的目标点,用三维数组表示
target_pos = np.array([0.5, 0, 0.5])
# 定义比例系数,用于计算控制力矩,在比例控制算法中使用
kp = 10.0
# 初始化 GLFW 库,GLFW 用于创建窗口和管理图形上下文
if not glfw.init():
# 如果初始化失败,抛出异常并给出提示信息
raise Exception("GLFW could not be initialized")
# 创建一个 GLFW 窗口,指定窗口的宽度为 1200 像素,高度为 900 像素,窗口标题为 "MuJoCo Simulation"
window = glfw.create_window(1200, 900, "MuJoCo Simulation", None, None)
if not window:
# 如果窗口创建失败,终止 GLFW 并抛出异常给出提示信息
glfw.terminate()
raise Exception("GLFW window could not be created")
# 将当前的 OpenGL 上下文设置为刚刚创建的窗口的上下文
glfw.make_context_current(window)
# 创建一个 MjvScene 对象,用于存储要渲染的场景信息,maxgeom 参数指定场景中最大的几何体数量
scene = mujoco.MjvScene(model, maxgeom=10000)
# 创建一个 MjvPerturb 对象,用于处理场景中的扰动信息,例如外力等
perturb = mujoco.MjvPerturb()
# 创建一个 MjvOption 对象,用于设置场景渲染的各种选项
opt = mujoco.MjvOption()
# 创建一个 MjvCamera 对象,用于控制场景的视角
cam = mujoco.MjvCamera()
# 使用默认设置初始化相机对象
mujoco.mjv_defaultCamera(cam)
# 开始模拟循环,只要模拟时间未达到总时间且窗口没有被关闭,就继续循环
while data.time < sim_time and not glfw.window_should_close(window):
# 计算机械臂末端当前位置与目标位置之间的误差,用向量表示
error = target_pos - data.geom('ee_geom').xpos
# 根据误差和比例系数计算控制力矩,这里使用简单的比例控制算法
control_torque = kp * error
# 将计算得到的控制力矩应用到模型的控制输入中,控制机械臂的运动
data.ctrl[:] = control_torque
# 在每个控制周期内,执行多次模拟步骤,确保模拟的精度
for _ in range(int(control_period / model.opt.timestep)):
# 执行一次模拟步骤,更新模型的状态
mujoco.mj_step(model, data)
# 获取当前窗口的帧缓冲区大小,即窗口的实际渲染区域大小
viewport_width, viewport_height = glfw.get_framebuffer_size(window)
# 创建一个 MjrRect 对象,用于定义渲染的视口区域,左上角坐标为 (0, 0),宽度和高度为窗口的大小
viewport = mujoco.MjrRect(0, 0, viewport_width, viewport_height)
# 更新场景信息,根据模型和数据的当前状态更新场景中的几何体、光照等信息
mujoco.mjv_updateScene(model, data, opt, perturb, cam, mujoco.mjtCatBit.mjCAT_ALL, scene)
# 创建一个 MjrContext 对象,用于管理渲染上下文,指定字体缩放比例
context = mujoco.MjrContext(model, mujoco.mjtFontScale.mjFONTSCALE_150)
# 渲染场景,将更新后的场景信息渲染到指定的视口区域
mujoco.mjr_render(viewport, scene, context)
# 交换前后缓冲区,将渲染好的图像显示到窗口上
glfw.swap_buffers(window)
# 处理窗口的事件,例如鼠标点击、键盘输入等
glfw.poll_events()
# 每 0.1 秒打印一次当前的模拟时间和机械臂末端的位置
if data.time % 0.1 < 0.001:
print(f'time: {data.time:.2f}, end effector pos: {data.geom("ee_geom").xpos}')
#模拟结束后,终止 GLFW 库,释放相关资源
glfw.terminate()
如果环境正确,即可看到下面的演示窗口和数据