【MuJoCo学习笔记】#2 接触动力学 腱系统 执行器 传感器
接触动力学
接触力
# 定义带接触的自由体模型
free_body_MJCF = """
<mujoco><!-- 纹理和材质定义 --><asset><!-- 棋盘格纹理 --><texture name="grid" type="2d" builtin="checker" rgb1=".1 .2 .3"rgb2=".2 .3 .4" width="300" height="300" mark="edge" markrgb=".2 .3 .4"/><material name="grid" texture="grid" texrepeat="2 2" texuniform="true"reflectance=".2"/></asset><worldbody><!-- 跟踪质心的光源 --><light pos="0 0 1" mode="trackcom"/><!-- 地面,带接触参数 --><geom name="ground" type="plane" pos="0 0 -.5" size="2 2 .1" material="grid" solimp=".99 .99 .01" solref=".001 1"/><!-- 自由运动的盒子和球体 --><body name="box_and_sphere" pos="0 0 0"><freejoint/> <!-- 6自由度 --><!-- 红色盒子,带接触参数 --><geom name="red_box" type="box" size=".1 .1 .1" rgba="1 0 0 1" solimp=".99 .99 .01" solref=".001 1"/><!-- 绿色球体 --><geom name="green_sphere" size=".06" pos=".1 .1 .1" rgba="0 1 0 1"/><!-- 固定相机视角 --><camera name="fixed" pos="0 -.6 .3" xyaxes="1 0 0 0 1 2"/><!-- 跟踪相机视角 --><camera name="track" pos="0 -.6 .3" xyaxes="1 0 0 0 1 2" mode="track"/></body></worldbody>
</mujoco>
"""
元素详解
asset
texture
mark/markrgb
:颜色标记,edge
在棋盘格边缘添加标记线
worldbody
light
mode
:光源的行为模式,trackcom
跟踪整个系统的质心位置
geom
solimp
:接触参数,旧版本只有前三个参数,指数默认 2,刚度默认 1dmin, dmax
:产生接触力的距离阈值 dmaxd_{\max}dmax,dmind_{\min}dminwidth
:接触力分布的宽度 width\mathrm{width}widthexponent
:接触力的指数参数 eeestiffness
:接触刚度系数 kkk- 接触力关于穿透深度 ddd 的函数
- F(d)=k⋅(d−dmindmax−dmin)e\displaystyle F(d)=k\cdot\left(\frac{d-d_{\min}}{d_{\max}-d_{\min}}\right)^eF(d)=k⋅(dmax−dmind−dmin)e
camera
mode
:相机行为模式,track
跟随其父物体body
# 加载模型
model = mujoco.MjModel.from_xml_string(free_body_MJCF)
data = mujoco.MjData(model)
height = 400
width = 600# 渲染初始状态
with mujoco.Renderer(model, height, width) as renderer:mujoco.mj_forward(model, data)renderer.update_scene(data, "fixed")media.show_image(renderer.render())
# 慢动作渲染,可视化接触点和接触力
n_frames = 200
height = 240
width = 320
frames = []# 创建可视化选项
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# 调整接触可视化元素的比例
model.vis.scale.contactwidth = 0.1 # 接触点宽度
model.vis.scale.contactheight = 0.03 # 接触点高度
model.vis.scale.forcewidth = 0.05 # 力矢量宽度
model.vis.map.force = 0.3 # 力矢量缩放# 设置随机初始旋转速度
mujoco.mj_resetData(model, data)
data.qvel[3:6] = 5*np.random.randn(3) # 随机角速度# 仿真并录制视频
with mujoco.Renderer(model, height, width) as renderer:for i in range(n_frames):# 1/4倍速仿真while data.time < i/120.0:mujoco.mj_step(model, data)# 使用跟踪相机渲染renderer.update_scene(data, "track", options)frame = renderer.render()frames.append(frame)# 显示慢动作视频
media.show_video(frames, fps=30)
倍速仿真
在遍历总数为 nframe
的视频帧的过程中,原速下每一帧截取的间隔为 1/fps
,在 n
倍速下则应为 n/fps
,故第 i
帧的时刻为 ni/fps
。
访问仿真运动数据
上一篇末尾我们提到如何记录分析仿真运动数据,由于这一步代码冗长、重复度高,且 matplotlib
绘图不是该系列的重点,因此这里仅列出访问仿真过程相应数据属性的方法。
目前涉及到 mjData
的部分属性如下:
time
:时刻- 运动学相关
qpos
:关节的广义位姿。对于单个自由关节(即自由刚体),[0:3]
为位置向量,[3:7]
为姿态四元数。对于更多刚体/关节,我们需要访问mjModel
中的jnt_qposadr[i]
:查询索引为i
的关节在qpos
中的起始索引,位姿表示方式由关节类型决定
xpos
:刚体位置矩阵,第一个索引为刚体 ID,第二个索引为刚体在世界坐标系下的坐标(0=x, 1=y, 2=z
)geom_xpos
:几何体位置矩阵,数据结构与xpos
对应qvel
:刚体和关节的广义速度。对于单个自由刚体,[0:3]
为线速度,[3:6]
为角速度。对于更多刚体/关节,我们需要访问mjModel
中的body_dofadr[i]/jnt_dofadr[i]
:查询索引为i
的刚体/关节在qvel
中的起始索引body_dofnum[i]/jnt_dofnum[i]
:查询索引为i
的刚体/关节的自由度数- 刚体/关节在
qvel
中的终止索引=起始索引+自由度数 - 除了自由刚体,
qvel
中关节的速度通常是在自身的局部坐标系(取决于关节类型)中,而关节连接的刚体速度通常在父刚体的局部坐标系(父刚体的惯性主轴)中 - 还可以通过
mjData.joint('name').qvel
cvel
:刚体的复合速度。只涉及刚体在世界坐标系下的线速度和角速度,按刚体索引排序,即对于索引为i
的刚体,其速度切片为[i*6:i*6+6]
qacc
:刚体和关节的加速度,数据结构与qvel
对应cacc
:刚体的复合加速度,数据结构与cvel
对应energy
:第一个元素为系统动能,第二个元素为系统势能- 高级 API:通过
mjData.body("name")
和mjData.joint("name")
可以直接通过名称而非索引查询来访问刚体和关节的运动学数据
- 动力学相关
ncon
:活动接触数contact
:活动接触点,对于每个索引的接触点对象有如下属性dist
:最小接触距离(负值为最大穿透)pos
:接触点世界坐标系位置frame
:接触点坐标系(3*3 矩阵)frame[2]
:接触法线方向frame[0]
:接触切向方向 1frame[1]
:接触切向方向 2
friction
:摩擦系数solref
:约束求解器参数solimp
:阻抗参数
qfrc_constraint
:约束广义力,包含所有约束产生的广义力/力矩,数据结构与qvel
对应
摩擦力
# 演示不同摩擦系数的效果
MJCF = """
<mujoco><asset><!-- 棋盘格纹理 --><texture name="grid" type="2d" builtin="checker" rgb1=".1 .2 .3"rgb2=".2 .3 .4" width="300" height="300" mark="none"/><material name="grid" texture="grid" texrepeat="6 6"texuniform="true" reflectance=".2"/><material name="wall" rgba='.5 .5 .5 1'/></asset><!-- 默认设置 --><default><geom type="box" size=".05 .05 .05" /><joint type="free"/></default><worldbody><light name="light" pos="-.2 0 1"/><!-- 倾斜的地面,低摩擦 --><geom name="ground" type="plane" size=".5 .5 10" material="grid"zaxis="-.3 0 1" friction=".1"/><!-- 侧视相机 --><camera name="y" pos="-.1 -.6 .3" xyaxes="1 0 0 0 1 2"/><!-- 低摩擦盒子 --><body pos="0 0 .1"><joint/><geom/></body><!-- 高摩擦盒子 --><body pos="0 .2 .1"><joint/><geom friction=".33"/></body></worldbody></mujoco>
"""
元素详解
friction
:摩擦属性,对于各向同性摩擦只需给出一个摩擦系数,而在各向异性摩擦中可以依次给出滑动摩擦(主要方向和次要方向)、扭转摩擦和滚动摩擦(主要方向和次要方向)
n_frames = 60
height = 300
width = 300
frames = []# 加载模型
model = mujoco.MjModel.from_xml_string(MJCF)
data = mujoco.MjData(model)# 仿真并录制视频
with mujoco.Renderer(model, height, width) as renderer:mujoco.mj_resetData(model, data)for i in range(n_frames):while data.time < i/30.0:mujoco.mj_step(model, data)renderer.update_scene(data, "y")frame = renderer.render()frames.append(frame)# 显示摩擦对比
media.show_video(frames, fps=30)
腱系统、执行器和传感器
为了实现更完善的机械系统建模,我们还需要以下元素:
- 腱系统:虚拟的弹性连接,类似于生物肌腱,用于定义和执行复杂驱动和约束的机制。
- 执行器:将控制信号转换为物理力的组件。
- 传感器:从模拟环境中提取各种物理量和数据的接口,相较于直接访问
mjData
可以模拟真实世界传感器的噪声、偏差、延迟、采样频率等。
# 演示腱系统、执行器和传感器
MJCF = """
<mujoco><asset><!-- 棋盘格纹理 --><texture name="grid" type="2d" builtin="checker" rgb1=".1 .2 .3"rgb2=".2 .3 .4" width="300" height="300" mark="none"/><material name="grid" texture="grid" texrepeat="1 1"texuniform="true" reflectance=".2"/></asset><worldbody><light name="light" pos="0 0 1"/><!-- 地面 --><geom name="floor" type="plane" pos="0 0 -.5" size="2 2 .1" material="grid"/><!-- 锚点(腱的固定端) --><site name="anchor" pos="0 0 .3" size=".01"/><!-- 固定相机 --><camera name="fixed" pos="0 -1.3 .5" xyaxes="1 0 0 0 1 2"/><!-- 支柱 --><geom name="pole" type="cylinder" fromto=".3 0 -.5 .3 0 -.1" size=".04"/><!-- 球棒 --><body name="bat" pos=".3 0 -.1"><!-- 铰链关节,带阻尼 --><joint name="swing" type="hinge" damping="1" axis="0 0 1"/><!-- 球棒几何体 --><geom name="bat" type="capsule" fromto="0 0 .04 0 -.3 .04"size=".04" rgba="0 0 1 1"/></body><!-- 目标物体 --><body name="box_and_sphere" pos="0 0 0"><joint name="free" type="free"/><geom name="red_box" type="box" size=".1 .1 .1" rgba="1 0 0 1"/><geom name="green_sphere" size=".06" pos=".1 .1 .1" rgba="0 1 0 1"/><!-- 挂钩点(腱的移动端) --><site name="hook" pos="-.1 -.1 -.1" size=".01"/><!-- IMU传感器位置 --><site name="IMU"/></body></worldbody><!-- 腱系统:连接锚点和挂钩 --><tendon><spatial name="wire" limited="true" range="0 0.35" width="0.003"><site site="anchor"/><site site="hook"/></spatial></tendon><!-- 执行器:驱动球棒 --><actuator><motor name="my_motor" joint="swing" gear="1"/></actuator><!-- 传感器:加速度计 --><sensor><accelerometer name="accelerometer" site="IMU"/></sensor>
</mujoco>
"""
元素详解
site
:站点,用于定义特定位置、标记点或附加点的虚拟实体pos
:位置,若未给出则默认为所属body
的局部坐标系原点或世界坐标系原点size
:视觉尺寸
tendon
:腱系统spatial
:空间肌腱limited
:肌腱长度限制,启用后肌腱长度会被限制在range
指定的范围内width
:肌腱视觉宽度site
:肌腱将以第一个site
为起点,最后一个site
为终点,可以有任意个途径点
actuator
:执行器motor
:电机执行器,直接对关节施加力或扭矩joint
:目标关节gear
:传动比,输出力(扭矩)=传动比*控制输入
sensor
:传感器accelerometer
:加速度计传感器site
:测量点位置
# 加载模型
model = mujoco.MjModel.from_xml_string(MJCF)
data = mujoco.MjData(model)
height = 480
width = 480# 渲染初始状态
with mujoco.Renderer(model, height, width) as renderer:mujoco.mj_forward(model, data)renderer.update_scene(data, "fixed")media.show_image(renderer.render())
通过 mjData
的 ctrl[i]
和 sensor('name').data
可以实现执行器按索引控制和传感器按名称读取:
# 设置仿真参数
n_frames = 180
height = 240
width = 320
frames = []
fps = 60.0
times = [] # 时间序列
sensordata = [] # 传感器数据# 设置恒定的执行器控制信号
mujoco.mj_resetData(model, data)
data.ctrl = 20 # 施加20牛米的力矩# 仿真并录制视频
with mujoco.Renderer(model, height, width) as renderer:for i in range(n_frames):# 仿真到下一帧时间while data.time < i/fps:mujoco.mj_step(model, data)times.append(data.time)# 记录加速度计数据sensordata.append(data.sensor('accelerometer').data.copy())# 渲染帧renderer.update_scene(data, "fixed")frame = renderer.render()frames.append(frame)# 显示球棒击打过程
media.show_video(frames, fps=fps)