赞
踩
本文档为AIR DISCOVERY LAB拾遗。当时的我还没变强。现在的我开始回顾,当时的一些文档依然有一定的意义。
Mujoco是类似Webots的一款仿真软件,用于Linux系统中。
Mujoco官网:MuJoCo — Advanced Physics Simulation
Mujoco Documentation:Overview - MuJoCo Documentation
本人采用的源码安装方法
源码安装的更多细节,请参考:Programming - MuJoCo Documentation
入门的过程我参考的这篇内容:https://colab.research.google.com/github/deepmind/mujoco/blob/main/python/tutorial.ipynb#scrollTo=T5f4w3Kq2X14
这是一个ipynb格式的文档。这个文档支持用户在线实时运行代码,因此是一个学习的非常好的平台。
首先,类似于Hello World,Mujoco有一个能够让你快速入门的代码:
- xml = """
- <mujoco>
- <worldbody>
- <geom name="red_box" type="box" size=".2 .2 .2" rgba="1 0 0 1"/>
- <geom name="green_sphere" pos=".2 .2 .2" size=".1" rgba="0 1 0 1"/>
- </worldbody>
- </mujoco>
- """
- model = mujoco.MjModel.from_xml_string(xml)
-
Mujoco本质上用的是MJCF文本来定义Mujoco模型的。Mujoco类似于urdf使用的xml格式。其中<mujoco>和<worldbody>两个标签是必须要的。
在model通过Mjmodel绑定模型之后,便可以通过mjmodel调取模型的一些参数了,比如说:model的名称、rgb信息、数量等。
不过在你调取model.geom之后,它的返回值是包含该几何体所有信息的一个结构体。你也可以通过地址访问的方法读取结构体中的所需信息。
mjData是model在仿真环境中的信息,由时间、位移和速度组成。我们将data和model绑定之后,就可以通过mjData读取mjModel在仿真中的各种信息了。除此之外,mjData还可以提供物体在世界坐标系下的位置信息。
但同时,如果我们要用到全局的坐标信息,就需要使用mj_kinematics函数了,它可以提供全局笛卡尔坐标系的信息。
在仿真之前,我们需要使用render函数将我们的模型渲染出来。在渲染之前,一般需要做如下几件事情:
- model # 定义模型
- data # 传输数据
- mujoco.mj_forward(model, data) # 将这二者传入render中
-
renderer函数一般常用以下三个内容:
- renderer = mujoco.Renderer(model) # 初次调用
- renderer.update_scene(data) # 更新渲染图
- media.show_image(renderer.render()) # 打印渲染图
-
好了。在render之后,我们可以进行simulation了。simulation最关键的一个函数是mj_step,它能够按照每一步来进行仿真。
- xml = """
- <mujoco>
- <worldbody>
- <light name="top" pos="0 0 1"/>
- <body name="box_and_sphere" euler="0 0 -30">
- <joint name="swing" type="hinge" axis="1 -1 0" pos="-.2 -.2 -.2"/>
- <geom name="red_box" type="box" size=".2 .2 .2" rgba="1 0 0 1"/>
- <geom name="green_sphere" pos=".2 .2 .2" size=".1" rgba="0 1 0 1"/>
- </body>
- </worldbody>
- </mujoco>
- """
- model = mujoco.MjModel.from_xml_string(xml)
- data = mujoco.MjData(model)
- renderer = mujoco.Renderer(model)
-
- # enable joint visualization option:
- scene_option = mujoco.MjvOption()
- scene_option.flags[mujoco.mjtVisFlag.mjVIS_JOINT] = True
-
- duration = 3.8 # (seconds)
- framerate = 60 # (Hz)
-
- frames = []
- mujoco.mj_resetData(model, data)
- while data.time < duration:
- mujoco.mj_step(model, data)
- if len(frames) < data.time * framerate:
- renderer.update_scene(data, scene_option=scene_option)
- pixels = renderer.render()
- frames.append(pixels)
-
- # Simulate and display video.
- media.show_video(frames, fps=framerate)
-

这个是比较基本的一段仿真代码。仿真时,需要先设定duration(视频时长)和framerate(帧率)。然后调用mj_step计算仿真,并调用renderer再次产生结果。同时需要注意,我们在仿真时要加入joints,并且设定joint格式才能够让环境动起来,否则没有DoF(自由度)机构是动不起来的。使能关节时,我们使用函数MjvOption。
在仿真时,可以改变一些物理参数来调整仿真结果,例如gravity和Friction(摩擦力)。
DoF,即自由度。真实世界中自由度是由xyz三坐标和rpy三轴旋转组成的。现实世界中的joints充当限制自由度的元素。但Mujoco使用joint来定义自由度。即:没有关节的地方默认不存在自由度。
接下来是对tipper-top的一个仿真实例。tipper-top是一个能够自己旋转的类似尖顶的玩具。示意视频如下:
仿真代码如下:
- tippe_top = """
- <mujoco model="tippe top">
- <option integrator="RK4"/> # 选择积分器为RK4
- <asset>
- <texture name="grid" type="2d" builtin="checker" rgb1=".1 .2 .3"
- rgb2=".2 .3 .4" width="300" height="300"/>
- <material name="grid" texture="grid" texrepeat="8 8" reflectance=".2"/>
- </asset> # 用于宏定义一个地板的结构和材料
- <worldbody>
- <geom size=".2 .2 .01" type="plane" material="grid"/>
- <light pos="0 0 .6"/>
- <camera name="closeup" pos="0 -.1 .07" xyaxes="1 0 0 0 1 2"/>
- <body name="top" pos="0 0 .02">
- <freejoint/> #设定关节为自由旋转关节,有6自由度
- <geom name="ball" type="sphere" size=".02" />
- <geom name="stem" type="cylinder" pos="0 0 .02" size="0.004 .008"/>
- <geom name="ballast" type="box" size=".023 .023 0.005" pos="0 0 -.015"
- contype="0" conaffinity="0" group="3"/>
- </body>
- </worldbody>
- <keyframe>
- <key name="spinning" qpos="0 0 0.02 1 0 0 0" qvel="0 0 0 0 1 200" />
- </keyframe> # 定义初始位置和速度
- </mujoco>
- """
- model = mujoco.MjModel.from_xml_string(tippe_top)
- renderer = mujoco.Renderer(model)
- data = mujoco.MjData(model)
- mujoco.mj_forward(model, data)
- # renderer.update_scene(data, camera="closeup")
- # media.show_image(renderer.render())
- duration = 7 # (seconds)
- framerate = 60 # (Hz)
-
- # Simulate and display video.
- frames = []
- mujoco.mj_resetDataKeyframe(model, data, 0) # Reset the state to keyframe 0
- while data.time < duration:
- mujoco.mj_step(model, data)
- if len(frames) < data.time * framerate:
- renderer.update_scene(data, "closeup")
- pixels = renderer.render()
- frames.append(pixels)
-
- media.show_video(frames, fps=framerate)
-

其中,qvel就是xyz、rpy;qpos是三个xyz参数,和四元数定义的旋转信息。
mjData中也可以存放仿真的信息,包括速度、加速度等。我们通过读取mjData的值,就可以读取得到其中的信息。并且在Python中,我们还可以通过plt.subplots函数绘制图像。
- timevals = []
- angular_velocity = []
- stem_height = []
-
- # Simulate and save data
- mujoco.mj_resetDataKeyframe(model, data, 0)
- while data.time < duration:
- mujoco.mj_step(model, data)
- timevals.append(data.time)
- angular_velocity.append(data.qvel[3:6].copy())
- stem_height.append(data.geom_xpos[2,2]);
-
- dpi = 120
- width = 600
- height = 800
- figsize = (width / dpi, height / dpi)
- _, ax = plt.subplots(2, 1, figsize=figsize, dpi=dpi, sharex=True)
-
- ax[0].plot(timevals, angular_velocity)
- ax[0].set_title('angular velocity')
- ax[0].set_ylabel('radians / second')
-
- ax[1].plot(timevals, stem_height)
- ax[1].set_xlabel('time (seconds)')
- ax[1].set_ylabel('meters')
- _ = ax[1].set_title('stem height')
-

网站中给出了一个混沌摆的仿真示例。其核心代码基本类似,但其中要设置的每一步的帧率和时间要更多也更复杂。同样,对于混沌摆而言,其中的各种物理规律也是混沌状态。
但需要注意的是,仿真过程中Timestep的设定会影响到仿真的精确度和能量的储存。在仿真时,适当提升精确度可以保证能量的稳定性和提升仿真整体效果。
同样的,仿真时timestep设置的过大,也会造成模型过快发散的问题。
在仿真时,MjvOption可以设定某些仿真信息(接触点、接触力)可视化。设定时示例如下:
- # visualize contact frames and forces, make body transparent
- options = mujoco.MjvOption()
- mujoco.mjv_defaultOption(options)
- options.flags[mujoco.mjtVisFlag.mjVIS_CONTACTPOINT] = True
- options.flags[mujoco.mjtVisFlag.mjVIS_CONTACTFORCE] = True
- options.flags[mujoco.mjtVisFlag.mjVIS_TRANSPARENT] = True
-
Mujoco的执行器和传感器的设置也是在MJCF文档中完成的。
其中,传感器的标签是<sensor/>,执行器的标签是<actuator/>.
在前期的代码配置完成之后,可以在data.ctrl中配置电机旋转速度;利用sensordata.append函数也可以读取我们设定的sensor中的传感器数据。
在scene_option中,可以根据我们的需要更改render的部分参数,使得仿真时显示出我们需要的内容。
- #@title Enable transparency and frame visualization
- # 功能:显示TF坐标变换
- scene_option.frame = mujoco.mjtFrame.mjFRAME_GEOM
- scene_option.flags[mujoco.mjtVisFlag.mjVIS_TRANSPARENT] = True
-
- #@title Depth rendering
- #功能:图像以深度信息的形式显示
- # update renderer to render depth
- renderer.enable_depth_rendering()
-
- # reset the scene
- renderer.update_scene(data)
-
- # depth is a float array, in meters.
- depth = renderer.render()
-
- # Shift nearest values to the origin.
- depth -= depth.min()
- # Scale by 2 mean distances of near rays.
- depth /= 2*depth[depth <= 1].mean()
- # Scale to [0, 255]
- pixels = 255*np.clip(depth, 0, 1)
-
- #@title Segmentation rendering
- #功能:将渲染图按照部分来渲染出来
- # update renderer to render segmentation
- renderer.enable_segmentation_rendering()
-
- # reset the scene
- renderer.update_scene(data)
-
- seg = renderer.render()
-
- # Display the contents of the first channel, which contains object
- # IDs. The second channel, seg[:, :, 1], contains object types.
- geom_ids = seg[:, :, 0]
- # Infinity is mapped to -1
- geom_ids = geom_ids.astype(np.float64) + 1
- # Scale to [0, 1]
- geom_ids = geom_ids / geom_ids.max()
- pixels = 255*geom_ids
-

在仿真时,我们同样可以设置相机内参矩阵。
关于相机内参矩阵的定义,请参考这篇维基百科:
当我们拿到了相机内参矩阵时,我们便可以建立起来三维世界坐标和二维照片坐标之间的联系。
到最后,我们还可以通过Mujoto.scene向视图中添加任意几何体。
因为个人能力原因,相机内参和任意几何体部分对于原理和使用尚存疑惑没看明白。
至此,基础的Mujoco仿真已经学习结束。
首先,我上手在python脚本里面复刻代码。但发现好像不能用mediapy中的命令来进行图像输出。于是我决定换个计策。我选用了scikit-image中的功能包。该功能包能够直接保存图片。
测试代码如下:
- import mujoco
- import os
- import subprocess
- # import mediapy as media
- from skimage import io
-
- xml = """
- <mujoco>
- <worldbody>
- <light name="top" pos="0 0 1"/>
- <geom name="red_box" type="box" size=".2 .2 .2" rgba="1 0 0 1"/>
- <geom name="green_sphere" pos=".2 .2 .2" size=".1" rgba="0 1 0 1"/>
- </worldbody>
- </mujoco>
- """
- model = mujoco.MjModel.from_xml_string(xml)
- data = mujoco.MjData(model)
- renderer = mujoco.Renderer(model)
-
- mujoco.mj_forward(model, data)
- renderer.update_scene(data)
- io.imshow(renderer.render())
- io.imsave('data1.jpg',renderer.render())
-

图像可以用这个功能包解决,但输出的短视频应该如何解决呢?
经过查找资料,短视频输出就用cv2功能包来做就可以。主要设置:输出大小、输出帧率、输出编码格式,然后按帧输出就可以了。
测试代码如下:
- import mujoco
- import os
- import subprocess
- # import mediapy as media
- # from skimage import io
- import cv2
-
- xml = """
- <mujoco>
- <worldbody>
- <light name="top" pos="0 0 1"/>
- <body name="box_and_sphere" euler="0 0 -30">
- <joint name="swing" type="hinge" axis="1 -1 0" pos="-.2 -.2 -.2"/>
- <geom name="red_box" type="box" size=".2 .2 .2" rgba="1 0 0 1"/>
- <geom name="green_sphere" pos=".2 .2 .2" size=".1" rgba="0 1 0 1"/>
- </body>
- </worldbody>
- </mujoco>
- """
- model = mujoco.MjModel.from_xml_string(xml)
- data = mujoco.MjData(model)
- renderer = mujoco.Renderer(model)
-
- # enable joint visualization option:
- scene_option = mujoco.MjvOption()
- scene_option.flags[mujoco.mjtVisFlag.mjVIS_JOINT] = True
-
- duration = 3.8 # (seconds)
- framerate = 60 # (Hz)
-
- frames = []
-
- # media.show_video(frames, fps=framerate)
- fourcc = cv2.VideoWriter_fourcc(*'XVID')
- #cap = cv2.VideoCapture(frames)
- size = (320,240)
-
- out = cv2.VideoWriter('out.avi',fourcc,framerate,size)
-
- mujoco.mj_resetData(model, data)
- while data.time < duration:
- mujoco.mj_step(model, data)
- if len(frames) < data.time * framerate:
- renderer.update_scene(data, scene_option=scene_option)
- pixels = renderer.render()
- frames.append(pixels)
- out.write(pixels)
-
- # Simulate and display video.
-

把输出的问题解决了,接下来就可以开始考虑硬件了。
什么是倒立摆?维基百科上是这样定义的:
https://zh.wikipedia.org/zh-cn/%E5%80%92%E5%96%AE%E6%93%BA
本质上,倒立摆是一个底部运动、上侧随动的结构。它能够通过二者的连接关节来测试倒立摆是否稳定。但我们只需要在mujoco里面搭建一个底部能动的模型,所以就差不多了。
大致结构如下:地面 - 滑动关节 - 横向移动模块 - 转动关节 - 立杆
因此,首先我需要写出来一个xml格式的结构。
- <mujoco>
- <worldbody>
- <light name="top" pos="0 0 2"/>
- <body name="inverted_pendulum">
- <body name="bottom">
- <joint name="swing" type="slide" axis="0 1 0" limited="true" range="-0.5 0.5" />
- <geom name="bottom" type="box" condim="1" size="0.2 0.2 0.2" rgba="0.4 0.4 0.4 1" />
- </body>
- <body name="upper">
- <joint name="rotate" type="hinge" axis="1 0 0" pos="0 0 0.2" />
- <geom name="upper" type="capsule" size="0.05 0.5" pos="0 0 0.7" rgba="0.75 0.75 0.5 1" />
- </body>
- </body>
- </worldbody>
- </mujoco>
-

设计之后,形状如下:
接下来,给它添加actuator和initial,再将其转成动画。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。