MuJoCo 入门教程(五)Python 绑定

系列文章目录

 


前言

        本笔记本提供了使用本地 Python 绑定的 MuJoCo 物理入门教程。

版权声明
        DeepMind Technologies Limited 2022 年版权所有。

        根据 Apache License 2.0 版(以下简称 "许可协议")授权;除非遵守许可协议,否则不得使用本文件。您可从 http://www.apache.org/licenses/LICENSE-2.0 获取许可证副本。

        除非适用法律要求或书面同意,否则根据许可协议分发的软件均按 "原样 "分发,不附带任何明示或暗示的保证或条件。请参阅许可协议,了解有关许可协议下的权限和限制的具体语言。

        本教程所有代码编辑器为 Jupyter Notebook 


 

一、All imports

!pip install mujoco# Set up GPU rendering.
from google.colab import files
import distutils.util
import os
import subprocess
if subprocess.run('nvidia-smi').returncode:raise RuntimeError('Cannot communicate with GPU. ''Make sure you are using a GPU Colab runtime. ''Go to the Runtime menu and select Choose runtime type.')# Add an ICD config so that glvnd can pick up the Nvidia EGL driver.
# This is usually installed as part of an Nvidia driver package, but the Colab
# kernel doesn't install its driver via APT, and as a result the ICD is missing.
# (https://github.com/NVIDIA/libglvnd/blob/master/src/EGL/icd_enumeration.md)
NVIDIA_ICD_CONFIG_PATH = '/usr/share/glvnd/egl_vendor.d/10_nvidia.json'
if not os.path.exists(NVIDIA_ICD_CONFIG_PATH):with open(NVIDIA_ICD_CONFIG_PATH, 'w') as f:f.write("""{"file_format_version" : "1.0.0","ICD" : {"library_path" : "libEGL_nvidia.so.0"}
}
""")# Configure MuJoCo to use the EGL rendering backend (requires GPU)
print('Setting environment variable to use GPU rendering:')
%env MUJOCO_GL=egl# Check if installation was succesful.
try:print('Checking that the installation succeeded:')import mujocomujoco.MjModel.from_xml_string('<mujoco/>')
except Exception as e:raise e from RuntimeError('Something went wrong during installation. Check the shell output above ''for more information.\n''If using a hosted Colab runtime, make sure you enable GPU acceleration ''by going to the Runtime menu and selecting "Choose runtime type".')print('Installation successful.')# Other imports and helper functions
import time
import itertools
import numpy as np# Graphics and plotting.
print('Installing mediapy:')
!command -v ffmpeg >/dev/null || (apt update && apt install -y ffmpeg)
!pip install -q mediapy
import mediapy as media
import matplotlib.pyplot as plt# More legible printing from numpy.
np.set_printoptions(precision=3, suppress=True, linewidth=100)from IPython.display import clear_output
clear_output()

 二、MuJoCo 基础知识

        我们首先定义并加载一个简单的模型:

import 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)

        xml 字符串是用 MuJoCo 的 MJCF 编写的,这是一种基于 XML 的建模语言。

  • 唯一需要的元素是 <mujoco>。最小的有效 MJCF 模型是 <mujoco/>,它是一个完全空的模型。
  • 所有物理元素都位于 <worldbody> 中,而 <worldbody> 始终是顶层主体,并构成笛卡尔坐标中的全局原点。
  • 我们在世界中定义了两个几何体,分别命名为 red_box 和 green_sphere。
  • 问题 red_box 没有位置,green_sphere 没有类型,这是为什么?
    • 答:MJCF 属性有默认值: MJCF 属性有默认值。默认位置为 0 0 0,默认几何类型为球体。MJCF 语言在文档的 XML 参考章节中有描述。

        from_xml_string() 方法调用模型编译器,创建二进制 mjModel 实例。

 2.1 mjModel

        MuJoCo 的 mjModel 包含模型描述,即所有不随时间变化的量。mjModel 的完整描述可在头文件 mjmodel.h 的末尾找到。请注意,头文件包含简短、有用的内联注释,描述了每个字段。

        在 mjModel 中可以找到的量的例子有 ngeom(场景中的 geoms 数量)和 geom_rgba(它们各自的颜色):

model.ngeom
model.geom_rgba

2.1.1 命名访问

        MuJoCo Python 绑定提供了使用名称的便捷访问器。在没有名称字符串的情况下调用 model.geom() 访问器会产生一个方便的错误,告诉我们有效的名称是什么。

try:model.geom()
except KeyError as e:print(e)

         在不指定属性的情况下调用已命名的访问器,会告诉我们所有有效的属性是什么:

model.geom('green_sphere')

         让我们读取 green_sphere 的 rgba 值:

model.geom('green_sphere').rgba

         该功能是 MuJoCo 的 mj_name2id 函数的快捷方式:

id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_GEOM, 'green_sphere')
model.geom_rgba[id, :]

         同样,只读 id 和 name 属性可用于将 id 转换为 name,然后再转换回来:

print('id of "green_sphere": ', model.geom('green_sphere').id)
print('name of geom 1: ', model.geom(1).name)
print('name of body 0: ', model.body(0).name)

        请注意,第 0 个机构始终是世界。它不能重命名。

        id 和 name 属性在 Python 解析中很有用:        

[model.geom(i).name for i in range(model.ngeom)]

2.2 mjData

        mjData 包含状态和与之相关的量。状态由时间、广义位置和广义速度组成。它们分别是 data.time、data.qpos 和 data.qvel。要创建一个新的 mjData,我们只需要我们的 mjModel

data = mujoco.MjData(model)

         mjData 还包含状态的函数,例如物体在世界坐标系中的笛卡尔位置。我们两个几何体的(x、y、z)位置在 data.geom_xpos 中:

print(data.geom_xpos)

        等等,为什么我们的两个几何体都在原点?我们不是偏移了绿球吗?答案是 mjData 中的派生量需要显式传播(见下文)。在我们的例子中,所需的最小函数是 mj_kinematics,它可以计算所有对象(不包括摄像机和灯光)的全局笛卡尔姿态。

mujoco.mj_kinematics(model, data)
print('raw access:\n', data.geom_xpos)# MjData also supports named access:
print('\nnamed access:\n', data.geom('green_sphere').xpos)

2.3 基本渲染、仿真和动画

        为了进行渲染,我们需要实例化一个渲染器对象并调用其渲染方法。

        我们还将重新加载模型,使 colab 的各个部分相互独立。

import mediapy as media
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>
"""
# Make model and data
model = mujoco.MjModel.from_xml_string(xml)
data = mujoco.MjData(model)# Make renderer, render and show the pixels
renderer = mujoco.Renderer(model)
media.show_image(renderer.render())

         嗯?为什么是黑色像素?

        答案是 原因同上,我们首先需要传播 mjData 中的值。这一次,我们将调用 mj_forward,它调用了计算加速度之前的整个流水线,即计算 eq?%5Cdot%7Bx%7D%3Df%28x%29,其中 eq?x 是状态。这个函数所做的比我们实际需要的要多,但除非我们想节省计算时间,否则调用 mj_forward 是个不错的做法,因为这样我们就知道没有遗漏任何东西。

        我们还需要更新 mjvScene,这是一个由呈现器持有的描述视觉场景的对象。我们稍后会看到,场景可以包括不属于物理模型的视觉对象。

mujoco.mj_forward(model, data)
renderer.update_scene(data)media.show_image(renderer.render())

 虽然成功了,但图像有点暗。让我们添加一束光,然后重新渲染。

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)media.show_image(renderer.render())

         好多了!

        请注意,mjModel 实例中的所有值都是可写的。虽然一般不建议这样做,而是修改 XML 中的值,因为这样很容易创建一个无效的模型,但有些值是可以安全写入的,例如颜色:

# Run this cell multiple times for different colors
model.geom('red_box').rgba[:3] = np.random.rand(3)
renderer.update_scene(data)
media.show_image(renderer.render())

2.4 仿真

现在我们来仿真并制作一段视频。我们将使用 MuJoCo 的主要高级函数 mj_step,它将状态步进为 eq?x_%7Bt&plus;h%7D%3Df%28x_t%29.

请注意,在下面的代码块中,每次调用 mj_step 之后,我们都不会进行渲染。这是因为默认的时间步长是 2ms,而我们想要的是 60fps 的视频,而不是 500fps。

duration = 3.8  # (seconds)
framerate = 60  # (Hz)# Simulate and display video.
frames = []
mujoco.mj_resetData(model, data)  # Reset state and time.
while data.time < duration:mujoco.mj_step(model, data)if len(frames) < data.time * framerate:renderer.update_scene(data)pixels = renderer.render()frames.append(pixels)
media.show_video(frames, fps=framerate)

        嗯,视频在播放,但什么都没动,这是为什么?

        这是因为这个模型没有自由度(DoF)。可以移动的物体(具有惯性)称为体。我们通过为体添加关节来增加自由度,指定体如何相对于其父体移动。让我们创建一个包含几何体的新体,添加一个铰链关节并重新渲染,同时使用可视化选项对象 MjvOption 可视化关节轴。

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] = Trueduration = 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)

 请注意,我们使用指令 euler="0 0 -30 "将 box_and_sphere 主体绕 Z 轴(垂直轴)旋转了 30°。这样做是为了强调运动学树中元素的姿势始终是相对于其父体而言的,因此我们的两个几何体也通过这种变换进行了旋转。

物理选项位于 mjModel.opt 中,例如时间步长:

model.opt.timestep

让我们翻转重力,重新渲染:

print('default gravity', model.opt.gravity)
model.opt.gravity = (0, 0, 10)
print('flipped gravity', model.opt.gravity)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)media.show_video(frames, fps=60)

我们也可以使用 XML 中的顶级 <option> 元素来实现这一功能:

<mujoco><option gravity="0 0 10"/>...
</mujoco>

2.5 了解自由度

        在现实世界中,所有刚性物体都有 6 个自由度:3 个平移和 3 个旋转。现实世界中的关节就像约束一样,消除了由关节连接的物体的相对自由度。一些物理仿真软件使用这种被称为 "笛卡尔 "或 "减法 "表示法,但其效率很低。MuJoCo 使用的是一种称为 "拉格朗日"、"广义 "或 "加法 "的表示法,根据这种表示法,除非使用关节明确添加,否则物体没有自由度。

        我们的模型只有一个铰链关节,只有一个自由度,整个状态由该关节的角度和角速度定义。这就是系统的广义位置和速度。

print('Total number of DoFs in the model:', model.nv)
print('Generalized positions:', data.qpos)
print('Generalized velocities:', data.qvel)

        MuJoCo 使用广义坐标的原因是,在渲染或读取对象的全局姿态之前,需要调用一个函数(例如 mj_forward)--笛卡尔位置是从广义位置导出的,需要明确计算。

三、举例说明: 使用自反 "尖顶 "仿真自由体

        自由体是指具有 6 个 DoFs(即 3 个平移和 3 个旋转)的自由关节的物体。我们可以给我们的 "盒子与球体 "一个自由关节,然后看着它坠落,但让我们看看更有趣的东西。小陀螺 "是一种会自己翻转的旋转玩具(视频,维基百科)。我们对其进行如下建模:

tippe_top = """"""
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())

        请注意该模型定义的几个新特征:

  1. 使用 <freejoint/> 子句添加了一个 6-DoF 自由度关节。
  2. 我们使用 <option/> 子句将积分器设置为 4 阶 Runge Kutta。与默认的欧拉积分器相比,Runge-Kutta 具有更高的收敛速度,在许多情况下可以提高给定时间步长下的精度。
  3. 我们在 <asset/> 子句中定义了地板的网格材料,并在 "floor "geom 中对其进行引用。
  4. 我们使用一个名为 "ballast "的不可见、不碰撞的盒状几何体来降低顶部的质量中心。低质量中心是发生翻转行为的必要条件(与直觉相反)。
  5. 我们将初始旋转状态保存为一个关键帧。它绕 Z 轴的旋转速度很高,但与世界的方向并不完全一致,这就引入了翻转所需的对称性破坏。
  6. 我们在模型中定义了一个 <camera>(摄像机),然后使用 update_scene() 的摄像机参数对其进行渲染。让我们检查一下状态:
print('positions', data.qpos)
print('velocities', data.qvel)

        速度很容易解释,6 个零,每个自由度一个。那么长度 7 的位置呢?我们可以看到身体最初的 2 厘米高度;随后的 4 个数字是三维方向,由单位四元数定义。三维方向用 4 个数字表示,而角速度用 3 个数字表示。更多信息,请参阅维基百科上关于四元数和空间旋转的文章。

        让我们来制作一段视频:

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)

3.1 测量 mjData 中的值

        如上所述,mjData 结构包含仿真产生的动态变量和中间结果,预计在每个时间步上都会发生变化。下面我们仿真 2000 个时间步,并绘制出茎顶和茎高的角速度与时间的函数关系图。

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')

3.2 举例说明: 混沌摆

        下面是一个混沌摆的模型,与旧金山探索馆的这个模型类似。

chaotic_pendulum = """"""
model = mujoco.MjModel.from_xml_string(chaotic_pendulum)
renderer = mujoco.Renderer(model, 480, 640)
data = mujoco.MjData(model)
mujoco.mj_forward(model, data)
renderer.update_scene(data, camera="fixed")
media.show_image(renderer.render())

3.2.1  计时

        让我们来观看一段视频,看看它是如何工作的:

# setup
n_seconds = 6
framerate = 30  # Hz
n_frames = int(n_seconds * framerate)
frames = []
renderer = mujoco.Renderer(model, 240, 320)# set initial state
mujoco.mj_resetData(model, data)
data.joint('root').qvel = 10# simulate and record frames
frame = 0
sim_time = 0
render_time = 0
n_steps = 0
for i in range(n_frames):while data.time * framerate < i:tic = time.time()mujoco.mj_step(model, data)sim_time += time.time() - ticn_steps += 1tic = time.time()renderer.update_scene(data, "fixed")frame = renderer.render()render_time += time.time() - ticframes.append(frame)# print timing and play video
step_time = 1e6*sim_time/n_steps
step_fps = n_steps/sim_time
print(f'simulation: {step_time:5.3g} μs/step  ({step_fps:5.0f}Hz)')
frame_time = 1e6*render_time/n_frames
frame_fps = n_frames/render_time
print(f'rendering:  {frame_time:5.3g} μs/frame ({frame_fps:5.0f}Hz)')
print('\n')# show video
media.show_video(frames, fps=framerate)

        请注意,渲染比仿真物理要慢得多。

3.2.2 混沌

        这是一个混沌系统(初始条件中的微小扰动会迅速累积):

PERTURBATION = 1e-7
SIM_DURATION = 10 # seconds
NUM_REPEATS = 8# preallocate
n_steps = int(SIM_DURATION / model.opt.timestep)
sim_time = np.zeros(n_steps)
angle = np.zeros(n_steps)
energy = np.zeros(n_steps)# prepare plotting axes
_, ax = plt.subplots(2, 1, figsize=(8, 6), sharex=True)# simulate NUM_REPEATS times with slightly different initial conditions
for _ in range(NUM_REPEATS):# initializemujoco.mj_resetData(model, data)data.qvel[0] = 10 # root joint velocity# perturb initial velocitiesdata.qvel[:] += PERTURBATION * np.random.randn(model.nv)# simulatefor i in range(n_steps):mujoco.mj_step(model, data)sim_time[i] = data.timeangle[i] = data.joint('root').qposenergy[i] = data.energy[0] + data.energy[1]# plotax[0].plot(sim_time, angle)ax[1].plot(sim_time, energy)# finalize plot
ax[0].set_title('root angle')
ax[0].set_ylabel('radian')
ax[1].set_title('total energy')
ax[1].set_ylabel('Joule')
ax[1].set_xlabel('second')
plt.tight_layout()

3.2.3 时间表和准确性

        问题 为什么能量会发生变化?没有摩擦或阻尼,这个系统应该节省能量。

        答:因为时间离散化: 因为时间离散化。

        如果我们减小时间步长,就能获得更高的精度和更好的能量守恒:

SIM_DURATION = 10 # (seconds)
TIMESTEPS = np.power(10, np.linspace(-2, -4, 5))# prepare plotting axes
_, ax = plt.subplots(1, 1)for dt in TIMESTEPS:# set timestep, printmodel.opt.timestep = dt# allocaten_steps = int(SIM_DURATION / model.opt.timestep)sim_time = np.zeros(n_steps)energy = np.zeros(n_steps)# initializemujoco.mj_resetData(model, data)data.qvel[0] = 9 # root joint velocity# simulateprint('{} steps at dt = {:2.2g}ms'.format(n_steps, 1000*dt))for i in range(n_steps):mujoco.mj_step(model, data)sim_time[i] = data.timeenergy[i] = data.energy[0] + data.energy[1]# plotax.plot(sim_time, energy, label='timestep = {:2.2g}ms'.format(1000*dt))# finalize plot
ax.set_title('energy')
ax.set_ylabel('Joule')
ax.set_xlabel('second')
ax.legend(frameon=True);
plt.tight_layout()

3.2.4 时间步长和发散

        当我们增加时间步长时,仿真会迅速发散:

SIM_DURATION = 10 # (seconds)
TIMESTEPS = np.power(10, np.linspace(-2, -1.5, 7))# get plotting axes
ax = plt.gca()for dt in TIMESTEPS:# set timestepmodel.opt.timestep = dt# allocaten_steps = int(SIM_DURATION / model.opt.timestep)sim_time = np.zeros(n_steps)energy = np.zeros(n_steps) * np.nanspeed = np.zeros(n_steps) * np.nan# initializemujoco.mj_resetData(model, data)data.qvel[0] = 11 # set root joint velocity# simulateprint('simulating {} steps at dt = {:2.2g}ms'.format(n_steps, 1000*dt))for i in range(n_steps):mujoco.mj_step(model, data)if data.warning.number.any():warning_index = np.nonzero(data.warning.number)[0][0]warning = mujoco.mjtWarning(warning_index).nameprint(f'stopped due to divergence ({warning}) at timestep {i}.\n')breaksim_time[i] = data.timeenergy[i] = sum(abs(data.qvel))speed[i] = np.linalg.norm(data.qvel)# plotax.plot(sim_time, energy, label='timestep = {:2.2g}ms'.format(1000*dt))ax.set_yscale('log')# finalize plot
ax.set_ybound(1, 1e3)
ax.set_title('energy')
ax.set_ylabel('Joule')
ax.set_xlabel('second')
ax.legend(frameon=True, loc='lower right');
plt.tight_layout()

3.2.5 接触

        让我们回到 "方框和球体 "的示例,让它自由连接:

free_body_MJCF = """"""
model = mujoco.MjModel.from_xml_string(free_body_MJCF)
renderer = mujoco.Renderer(model, 400, 600)
data = mujoco.MjData(model)
mujoco.mj_forward(model, data)
renderer.update_scene(data, "fixed")
media.show_image(renderer.render())

        让这个体在地板上慢速滚动,同时想象接触点和力量:

n_frames = 200
height = 240
width = 320
frames = []
renderer = mujoco.Renderer(model, height, width)# 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# tweak scales of contact visualization elements
model.vis.scale.contactwidth = 0.1
model.vis.scale.contactheight = 0.03
model.vis.scale.forcewidth = 0.05
model.vis.map.force = 0.3# random initial rotational velocity:
mujoco.mj_resetData(model, data)
data.qvel[3:6] = 5*np.random.randn(3)# simulate and render
for i in range(n_frames):while data.time < i/120.0: #1/4x real timemujoco.mj_step(model, data)renderer.update_scene(data, "track", options)frame = renderer.render()frames.append(frame)# show video
media.show_video(frames, fps=30)

3.2.6 接触力分析

        让我们重新运行上述仿真(使用不同的随机初始条件),并绘制一些与接触力相关的值

n_steps = 499# allocate
sim_time = np.zeros(n_steps)
ncon = np.zeros(n_steps)
force = np.zeros((n_steps,3))
velocity = np.zeros((n_steps, model.nv))
penetration = np.zeros(n_steps)
acceleration = np.zeros((n_steps, model.nv))
forcetorque = np.zeros(6)# random initial rotational velocity:
mujoco.mj_resetData(model, data)
data.qvel[3:6] = 2*np.random.randn(3)# simulate and save data
for i in range(n_steps):mujoco.mj_step(model, data)sim_time[i] = data.timencon[i] = data.nconvelocity[i] = data.qvel[:]acceleration[i] = data.qacc[:]# iterate over active contacts, save force and distancefor j,c in enumerate(data.contact):mujoco.mj_contactForce(model, data, j, forcetorque)force[i] += forcetorque[0:3]penetration[i] = min(penetration[i], c.dist)# we could also do# force[i] += data.qfrc_constraint[0:3]# do you see why?# plot
_, ax = plt.subplots(3, 2, sharex=True, figsize=(10, 10))lines = ax[0,0].plot(sim_time, force)
ax[0,0].set_title('contact force')
ax[0,0].set_ylabel('Newton')
ax[0,0].legend(iter(lines), ('normal z', 'friction x', 'friction y'));ax[1,0].plot(sim_time, acceleration)
ax[1,0].set_title('acceleration')
ax[1,0].set_ylabel('(meter,radian)/s/s')ax[2,0].plot(sim_time, velocity)
ax[2,0].set_title('velocity')
ax[2,0].set_ylabel('(meter,radian)/s')
ax[2,0].set_xlabel('second')ax[0,1].plot(sim_time, ncon)
ax[0,1].set_title('number of contacts')
ax[0,1].set_yticks(range(6))ax[1,1].plot(sim_time, force[:,0])
ax[1,1].set_yscale('log')
ax[1,1].set_title('normal (z) force - log scale')
ax[1,1].set_ylabel('Newton')
z_gravity = -model.opt.gravity[2]
mg = model.body("box_and_sphere").mass[0] * z_gravity
mg_line = ax[1,1].plot(sim_time, np.ones(n_steps)*mg, label='m*g', linewidth=1)
ax[1,1].legend()ax[2,1].plot(sim_time, 1000*penetration)
ax[2,1].set_title('penetration depth')
ax[2,1].set_ylabel('millimeter')
ax[2,1].set_xlabel('second')plt.tight_layout()

3.2.7 摩擦力

        让我们看看改变摩擦力值的效果

MJCF = """"""
n_frames = 60
height = 300
width = 300
frames = []# load
model = mujoco.MjModel.from_xml_string(MJCF)
data = mujoco.MjData(model)
renderer = mujoco.Renderer(model, height, width)# simulate and render
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)

四、肌腱、致动器和传感器

MJCF = """"""
model = mujoco.MjModel.from_xml_string(MJCF)
renderer = mujoco.Renderer(model, 480, 480)
data = mujoco.MjData(model)
mujoco.mj_forward(model, data)
renderer.update_scene(data, "fixed")
media.show_image(renderer.render())

驱动蝙蝠和被动 "皮纳塔":

n_frames = 180
height = 240
width = 320
frames = []
fps = 60.0
times = []
sensordata = []renderer = mujoco.Renderer(model, height, width)# constant actuator signal
mujoco.mj_resetData(model, data)
data.ctrl = 20# simulate and render
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)

让我们绘制加速度传感器测得的数值:

ax = plt.gca()ax.plot(np.asarray(times), np.asarray(sensordata), label='timestep = {:2.2g}ms'.format(1000*dt))# finalize plot
ax.set_title('Accelerometer values')
ax.set_ylabel('meter/second^2')
ax.set_xlabel('second')
ax.legend(frameon=True, loc='lower right');
plt.tight_layout()

请注意,从加速度计的测量结果中可以清楚地看到身体被球棒击中的瞬间。

五、高级渲染

        与关节可视化一样,其他渲染选项也作为参数显示在渲染方法中。

让我们回到第一个模型:

xml = """"""
model = mujoco.MjModel.from_xml_string(xml)
renderer = mujoco.Renderer(model)
data = mujoco.MjData(model)mujoco.mj_forward(model, data)
renderer.update_scene(data)
media.show_image(renderer.render())
#@title Enable transparency and frame visualization {vertical-output: true}scene_option.frame = mujoco.mjtFrame.mjFRAME_GEOM
scene_option.flags[mujoco.mjtVisFlag.mjVIS_TRANSPARENT] = True
renderer.update_scene(data, scene_option=scene_option)
frame = renderer.render()
media.show_image(frame)
#@title Depth rendering {vertical-output: true}# 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)media.show_image(pixels.astype(np.uint8))renderer.disable_depth_rendering()
#@title Segmentation rendering {vertical-output: true}# 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
media.show_image(pixels.astype(np.uint8))renderer.disable_segmentation_rendering()

5.1 摄像机矩阵
有关摄像机矩阵的描述,请参阅维基百科上的 "摄像机矩阵 "一文。

def compute_camera_matrix(renderer, data):"""Returns the 3x4 camera matrix."""# If the camera is a 'free' camera, we get its position and orientation# from the scene data structure. It is a stereo camera, so we average over# the left and right channels. Note: we call `self.update()` in order to# ensure that the contents of `scene.camera` are correct.renderer.update_scene(data)pos = np.mean([camera.pos for camera in renderer.scene.camera], axis=0)z = -np.mean([camera.forward for camera in renderer.scene.camera], axis=0)y = np.mean([camera.up for camera in renderer.scene.camera], axis=0)rot = np.vstack((np.cross(y, z), y, z))fov = model.vis.global_.fovy# Translation matrix (4x4).translation = np.eye(4)translation[0:3, 3] = -pos# Rotation matrix (4x4).rotation = np.eye(4)rotation[0:3, 0:3] = rot# Focal transformation matrix (3x4).focal_scaling = (1./np.tan(np.deg2rad(fov)/2)) * renderer.height / 2.0focal = np.diag([-focal_scaling, focal_scaling, 1.0, 0])[0:3, :]# Image matrix (3x3).image = np.eye(3)image[0, 2] = (renderer.width - 1) / 2.0image[1, 2] = (renderer.height - 1) / 2.0return image @ focal @ rotation @ translation
#@title Project from world to camera coordinates {vertical-output: true}
# reset the scene
renderer.update_scene(data)# Get the world coordinates of the box corners
box_pos = data.geom_xpos[model.geom('red_box').id]
box_mat = data.geom_xmat[model.geom('red_box').id].reshape(3, 3)
box_size = model.geom_size[model.geom('red_box').id]
offsets = np.array([-1, 1]) * box_size[:, None]
xyz_local = np.stack(list(itertools.product(*offsets))).T
xyz_global = box_pos[:, None] + box_mat @ xyz_local# Camera matrices multiply homogenous [x, y, z, 1] vectors.
corners_homogeneous = np.ones((4, xyz_global.shape[1]), dtype=float)
corners_homogeneous[:3, :] = xyz_global# Get the camera matrix.
m = compute_camera_matrix(renderer, data)# Project world coordinates into pixel space. See:
# https://en.wikipedia.org/wiki/3D_projection#Mathematical_formula
xs, ys, s = m @ corners_homogeneous
# x and y are in the pixel coordinate system.
x = xs / s
y = ys / s# Render the camera view and overlay the projected corner coordinates.
pixels = renderer.render()
fig, ax = plt.subplots(1, 1)
ax.imshow(pixels)
ax.plot(x, y, '+', c='w')
ax.set_axis_off()

5.2 修改场景
让我们在 mjvScene 中添加一些任意几何体。

def get_geom_speed(model, data, geom_name):"""Returns the speed of a geom."""geom_vel = np.zeros(6)geom_type = mujoco.mjtObj.mjOBJ_GEOMgeom_id = data.geom(geom_name).idmujoco.mj_objectVelocity(model, data, geom_type, geom_id, geom_vel, 0)return np.linalg.norm(geom_vel)def add_visual_capsule(scene, point1, point2, radius, rgba):"""Adds one capsule to an mjvScene."""if scene.ngeom >= scene.maxgeom:returnscene.ngeom += 1  # increment ngeom# initialise a new capsule, add it to the scene using mjv_makeConnectormujoco.mjv_initGeom(scene.geoms[scene.ngeom-1],mujoco.mjtGeom.mjGEOM_CAPSULE, np.zeros(3),np.zeros(3), np.zeros(9), rgba.astype(np.float32))mujoco.mjv_makeConnector(scene.geoms[scene.ngeom-1],mujoco.mjtGeom.mjGEOM_CAPSULE, radius,point1[0], point1[1], point1[2],point2[0], point2[1], point2[2])# traces of time, position and speed
times = []
positions = []
speeds = []
offset = model.jnt_axis[0]/16  # offset along the joint axisdef modify_scene(scn):"""Draw position trace, speed modifies width and colors."""if len(positions) > 1:for i in range(len(positions)-1):rgba=np.array((np.clip(speeds[i]/10, 0, 1),np.clip(1-speeds[i]/10, 0, 1),.5, 1.))radius=.003*(1+speeds[i])point1 = positions[i] + offset*times[i]point2 = positions[i+1] + offset*times[i+1]add_visual_capsule(scn, point1, point2, radius, rgba)duration = 6    # (seconds)
framerate = 30  # (Hz)# Simulate and display video.
frames = []# Reset state and time.
mujoco.mj_resetData(model, data)
mujoco.mj_forward(model, data)while data.time < duration:# append data to the tracespositions.append(data.geom_xpos[data.geom("green_sphere").id].copy())times.append(data.time)speeds.append(get_geom_speed(model, data, "green_sphere"))mujoco.mj_step(model, data)if len(frames) < data.time * framerate:renderer.update_scene(data)modify_scene(renderer.scene)pixels = renderer.render()frames.append(pixels)
media.show_video(frames, fps=framerate)

5.3 摄像机控制
摄像机可以动态控制,以实现电影效果。运行下面的三个单元格,就能看到静态摄像机和移动摄像机渲染效果的不同。

摄像机控制代码在两个轨迹之间平滑转换,一个轨迹围绕一个固定点运行,另一个轨迹跟踪一个移动物体。代码中的参数值是通过快速迭代低分辨率视频获得的。

#@title Load the "dominos" model
dominos_xml = """"""
model = mujoco.MjModel.from_xml_string(dominos_xml)
data = mujoco.MjData(model)
renderer = mujoco.Renderer(model, height=1024, width=1440)
#@title Render from fixed camera
duration = 2.5  # (seconds)
framerate = 60  # (Hz)# Simulate and display video.
frames = []
mujoco.mj_resetData(model, data)  # Reset state and time.
while data.time < duration:mujoco.mj_step(model, data)if len(frames) < data.time * framerate:renderer.update_scene(data, camera='top')pixels = renderer.render()frames.append(pixels)
media.show_video(frames, fps=framerate)
#@title Render from moving camera
duration = 3  # (seconds)# find time when box is thrown (speed > 2cm/s)
throw_time = 0.0
mujoco.mj_resetData(model, data)
while data.time < duration and not throw_time:mujoco.mj_step(model, data)box_speed = np.linalg.norm(data.joint('box').qvel[:3])if box_speed > 0.02:throw_time = data.time
assert throw_time > 0def mix(time, t0=0.0, width=1.0):"""Sigmoidal mixing function."""t = (time - t0) / widths = 1 / (1 + np.exp(-t))return 1 - s, sdef unit_cos(t):"""Unit cosine sigmoid from (0,0) to (1,1)."""return 0.5 - np.cos(np.pi*np.clip(t, 0, 1))/2def orbit_motion(t):"""Return orbit trajectory."""distance = 0.9azimuth = 140 + 100 * unit_cos(t)elevation = -30lookat = data.geom('floor').xpos.copy()return distance, azimuth, elevation, lookatdef track_motion():"""Return box-track trajectory."""distance = 0.08azimuth = 280elevation = -10lookat = data.geom('box').xpos.copy()return distance, azimuth, elevation, lookatdef cam_motion():"""Return sigmoidally-mixed {orbit, box-track} trajectory."""d0, a0, e0, l0 = orbit_motion(data.time / throw_time)d1, a1, e1, l1 = track_motion()mix_time = 0.3w0, w1 = mix(data.time, throw_time, mix_time)return w0*d0+w1*d1, w0*a0+w1*a1, w0*e0+w1*e1, w0*l0+w1*l1# Make a camera.
cam = mujoco.MjvCamera()
mujoco.mjv_defaultCamera(cam)# Simulate and display video.
framerate = 60  # (Hz)
slowdown = 4    # 4x slow-down
mujoco.mj_resetData(model, data)
frames = []
while data.time < duration:mujoco.mj_step(model, data)if len(frames) < data.time * framerate * slowdown:cam.distance, cam.azimuth, cam.elevation, cam.lookat = cam_motion()renderer.update_scene(data, cam)pixels = renderer.render()frames.append(pixels)
media.show_video(frames, fps=framerate)

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/802888.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux文件打开及创建(3.31)

创建一个file1文件。 运行结果&#xff1a;

DataX 数据库同步部分源码解析

在工作中遇到异构数据库同步的问题,从Oracle数据库同步数据到Postgres&#xff0c;其中的很多数据库表超过百万&#xff0c;并且包含空间字段。经过筛选&#xff0c;选择了开源的DataXDataX Web作为基础框架。DataX 是阿里云的开源产品&#xff0c;大厂的产品值得信赖&#xff…

Vue.js组件精讲 组件的通信2:派发与广播——自行实现dispatch和broadcast方法

上一讲的 provide / inject API 主要解决了跨级组件间的通信问题&#xff0c;不过它的使用场景&#xff0c;主要是子组件获取上级组件的状态&#xff0c;跨级组件间建立了一种主动提供与依赖注入的关系。然后有两种场景它不能很好的解决&#xff1a; 父组件向子组件&#xff0…

transformer上手(2) —— 注意力机制

自从 2017 年 Google 发布《Attention is All You Need》之后&#xff0c;各种基于 Transformer 的模型和方法层出不穷。尤其是 2018 年&#xff0c;OpenAI 发布的 GPT 和 Google 发布的 BERT 模型在几乎所有 NLP 任务上都取得了远超先前最强基准的性能&#xff0c;将 Transfor…

Java多路查找树(含面试大厂题和源码)

多路查找树&#xff08;Multiway Search Tree&#xff09;&#xff0c;也称为B树或B树&#xff0c;是一种自平衡的树形数据结构&#xff0c;用于存储大量数据&#xff0c;通常用于数据库和文件系统中。它允许在查找、插入和删除操作中保持数据的有序性&#xff0c;同时优化了磁…

【蓝桥杯每日一题】4.9网络分析(代码详解版)

终于把清明节假期时自己挖的坑给补上了 题目来源&#xff1a; 2069. 网络分析 - AcWing题库 参考&#xff1a; Bear_king的图解 y总代码解读 思路1&#xff1a; 思考&#xff1a; 题目看着&#xff0c;看到“发送信息后&#xff0c;又会发送到相邻的结点上面”这句话&am…

js通过Object.defineProperty实现数据响应式

目录 数据响应式属性描述符propertyResponsive 依赖收集依赖队列寻找依赖 观察器 派发更新Observer完整代码关于数据响应式关于Object.defineProperty的限制 数据响应式 假设我们现在有这么一个页面 <!DOCTYPE html> <html lang"en"><head><m…

Oracle表空间满清理方案汇总分享

目录 前言思考 一、第一种增加表空间的数据文件数量达到总容量的提升 二、第二种解决方案针对system和sysaux的操作 2.1SYSTEM表空间优化 2.2sysaux表空间回收 2.2.1针对sysaux的表空间爆满还有第二套方案维护 三、第三种解决方案使用alter tablespace resize更改表空间的…

深入浅出 -- 系统架构之微服务架构的新挑战

尽管微服务架构有着高度独立的软件模块、单一的业务职责、可灵活调整的技术栈等优势&#xff0c;但也不能忽略它所带来的弊端。本篇文章&#xff0c;我们从网络、性能、运维、组织架构和集成测试五个方面来聊一下设计微服务架构需要考虑哪些问题&#xff0c;对设计有哪些挑战呢…

Webots常用的执行器(Python版)

文章目录 1. RotationalMotor2. LinearMotor3. Brake4. Propeller5. Pen6. LED 1. RotationalMotor # -*- coding: utf-8 -*- """motor_controller controller."""from controller import Robot# 实例化机器人 robot Robot()# 获取基本仿真步长…

ChatGPT/GPT4科研应用与绘图技术及论文写作

2023年随着OpenAI开发者大会的召开&#xff0c;最重磅更新当属GPTs&#xff0c;多模态API&#xff0c;未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚于互联网和个人电脑的问世。360创始人周鸿祎认为未来各行各业如果不能搭上这班车…

2024年第十七届“认证杯”数学中国数学建模网络挑战赛思路

2024年第十七届“认证杯”数学中国数学建模网络挑战赛将于2024年4月举行。 比赛两个阶段统一报名&#xff0c;参赛费为每队100元人民币&#xff08;两个阶段总共&#xff09;。如果需要组委会提供详细的论文评价&#xff0c;需要再支付100元人民币的论文点评费(即每个参赛队支…

c++的学习之路:19、模板

摘要 本章主要是说了一些模板&#xff0c;如非类型模板参数、类模板的特化等等&#xff0c;文章末附上测试代码与导图 目录 摘要 一、非类型模板参数 二、类模板的特化 1、概念 2、函数模板特化 3、类模板特化 三、模板的分离编译 1、什么是分离编译 2、模板的分离编…

2024.4.8力扣每日一题——使数组连续的最少操作数

2024.4.8 题目来源我的题解方法一 去重排序滑动窗口 题目来源 力扣每日一题&#xff1b;题序&#xff1a;2009 我的题解 方法一 去重排序滑动窗口 参考官方题解。 记数组 nums的长度为 n。经过若干次操作后&#xff0c;若数组变为连续的&#xff0c;那么数组的长度不会改变&…

ip地址切换器安卓版,保护隐私,自由上网

在移动互联网时代&#xff0c;随着智能手机和平板电脑的普及&#xff0c;移动设备的网络连接变得愈发重要。为了满足用户在不同网络环境下的需求&#xff0c;IP地址切换器安卓版应运而生。本文将以虎观代理为例&#xff0c;为您详细解析IP地址切换器安卓版的功能、应用以及其所…

UVA1596 Bug Hunt 找Bug 解题报告

题目链接 https://vjudge.net/problem/UVA-1596 题目大意 输入并模拟执行一段程序&#xff0c;输出第一个bug所在的行。每行程序有两种可能&#xff1a; 数组定义&#xff0c;格式为arr[size]。例如a[10]或者b[5]&#xff0c;可用下标分别是0&#xff5e;9和0&#xff5e;4…

Linux压缩打包

压缩文件有时候也叫归档文件&#xff0c;但是归档是将多个文件捆绑成一个文件&#xff0c;并没有压缩&#xff0c;压缩才是将大小压缩的更小。 tar 压缩 tar -zcf 压缩后文件名.tar.gz 需要压缩的文件 [rootlocalhost ~]# tar -zcf ser.tar.gz services压缩多个文件 [rootloca…

克服与新一代人工智能部署相关的数据挑战

随着商界领袖逐渐了解该技术的力量和潜力&#xff0c;人们对 ChatGPT 等生成式人工智能工具的潜力的兴趣正在迅速上升。 这些工具能够创建以前属于人类创造力和智力领域的输出&#xff0c;有潜力改变许多业务流程&#xff0c;并成为每个人&#xff08;从作家和创作者到程序员和…

题目:学习使用按位异或 ^

题目&#xff1a;学习使用按位异或 ^ There is no nutrition in the blog content. After reading it, you will not only suffer from malnutrition, but also impotence. The blog content is all parallel goods. Those who are worried about being cheated should leave q…

蓝桥杯加训

1.两只塔姆沃斯牛&#xff08;模拟&#xff09; 思路&#xff1a;人和牛都记录三个数据&#xff0c;当前坐标和走的方向&#xff0c;如果人和牛的坐标和方向走重复了&#xff0c;那就说明一直在绕圈圈&#xff0c;无解 #include<iostream> using namespace std; const i…