logo Mr.Robota 具身智能学习手册 EN
第十章:具身智能其他仿真工具及仿真前沿

📸 可视化与渲染

Genesis的可视化系统由刚创建的场景的visualizer管理(即scene.visualizer)。有两种方式来可视化场景:1)使用在单独线程中运行的交互式查看器,2)手动向场景添加相机并使用相机渲染图像。

查看器

如果连接了显示器,可以使用交互式查看器来可视化场景。Genesis使用不同的options组来配置场景中的不同组件。要配置查看器,可以在创建场景时更改viewer_options中的参数。此外,我们使用vis_options来指定与可视化相关的属性,这些属性将由查看器和相机共享(我们很快会添加相机)。 创建一个具有更详细查看器和可视化设置的场景(这看起来有点复杂,但只是为了说明目的):

pythonscene = gs.Scene(
    show_viewer    = True,
    viewer_options = gs.options.ViewerOptions(
        res           = (1280, 960),
        camera_pos    = (3.5, 0.0, 2.5),
        camera_lookat = (0.0, 0.0, 0.5),
        camera_fov    = 40,
        max_FPS       = 60,
    ),
    vis_options = gs.options.VisOptions(
        show_world_frame = True, # 可视化`world`在其原点的坐标系
        world_frame_size = 1.0, # 世界坐标系的长度(米)
        show_link_frame  = False, # 不可视化实体链接的坐标系
        show_cameras     = False, # 不可视化添加的相机的网格和视锥
        plane_reflection = True, # 打开平面反射
        ambient_light    = (0.1, 0.1, 0.1), # 环境光设置
    ),
    renderer = gs.renderers.Rasterizer(), # 使用光栅化器进行相机渲染
)

在这里我们可以指定查看器相机的姿态和视场角。如果max_FPS设置为None,查看器将尽可能快地运行。如果res设置为None,Genesis将自动创建一个4:3的窗口,高度设置为显示器高度的一半。还要注意,在上述设置中,我们设置使用光栅化后端进行相机渲染。Genesis提供了两种渲染后端:gs.renderers.Rasterizer()和gs.renderers.RayTracer()。查看器始终使用光栅化器。默认情况下,相机也使用光栅化器。 一旦场景创建完成,可以通过scene.visualizer.viewer或简写scene.viewer访问查看器对象。可以查询或设置查看器相机姿态:

pythoncam_pose = scene.viewer.camera_pose()
scene.viewer.set_camera_pose(cam_pose)

相机与无头渲染

现在让我们手动向场景添加一个相机对象。相机不连接到查看器或显示器,仅在需要时返回渲染的图像。因此,相机在无头模式下工作。

pythoncam = scene.add_camera(
    res    = (1280, 960),
    pos    = (3.5, 0.0, 2.5),
    lookat = (0, 0, 0.5),
    fov    = 30,
    GUI    = False
)

如果GUI=True,每个相机将创建一个opencv窗口以动态显示渲染的图像。请注意,这与查看器GUI不同。 然后,一旦我们构建场景,我们可以使用相机渲染图像。我们的相机支持渲染rgb图像、深度图、分割掩码和表面法线。默认情况下,仅渲染rgb,可以通过在调用camera.render()时设置参数来打开其他模式:

pythonscene.build()
# 渲染rgb、深度、分割掩码和法线图
rgb, depth, segmentation, normal = cam.render(depth=True, segmentation=True, normal=True)

如果使用了GUI=True并连接了显示器,现在应该能看到4个窗口。(有时opencv窗口会有额外的延迟,所以如果窗口是黑色的,可以调用额外的cv2.waitKey(1),或者简单地再次调用render()来刷新窗口。)


使用相机录制视频 现在,让我们仅渲染rgb图像,并移动相机并录制视频。Genesis提供了一个方便的工具来录制视频:

python# 开始相机录制。一旦开始,所有渲染的rgb图像将被内部记录
cam.start_recording()
import numpy as np
for i in range(120):
    scene.step()
    # 改变相机位置
    cam.set_pose(
        pos    = (3.0 * np.sin(i / 60), 3.0 * np.cos(i / 60), 2.5),
        lookat = (0, 0, 0.5),
    )

    cam.render()
# 停止录制并保存视频。如果未指定`filename`,将使用调用文件名自动生成名称。
cam.stop_recording(save_to_filename='video.mp4', fps=60)

将视频保存到video.mp4:

import genesis as gs gs.init(backend=gs.cpu) scene = gs.Scene( show_viewer = True, viewer_options = gs.options.ViewerOptions( res = (1280, 960), camera_pos = (3.5, 0.0, 2.5), camera_lookat = (0.0, 0.0, 0.5), camera_fov = 40, max_FPS = 60, ), vis_options = gs.options.VisOptions( show_world_frame = True, world_frame_size = 1.0, show_link_frame = False, show_cameras = False, plane_reflection = True, ambient_light = (0.1, 0.1, 0.1), ), renderer=gs.renderers.Rasterizer(), ) plane = scene.add_entity( gs.morphs.Plane(), ) franka = scene.add_entity( gs.morphs.MJCF(file=‘xml/franka_emika_panda/panda.xml’), ) cam = scene.add_camera( res = (640, 480), pos = (3.5, 0.0, 2.5), lookat = (0, 0, 0.5), fov = 30, GUI = False, ) scene.build()

渲染rgb、深度、分割掩码和法线图

rgb, depth, segmentation, normal = cam.render(rgb=True, depth=True, segmentation=True, normal=True)

cam.start_recording() import numpy as np for i in range(120): scene.step() cam.set_pose( pos = (3.0 * np.sin(i / 60), 3.0 * np.cos(i / 60), 2.5), lookat = (0, 0, 0.5), ) cam.render() cam.stop_recording(save_to_filename=‘video.mp4’, fps=60)

并行仿真

pythonimport torch
import genesis as gs

gs.init(backend=gs.gpu)

scene = gs.Scene(
    show_viewer   = False,
    rigid_options = gs.options.RigidOptions(
        dt                = 0.01,
    ),
)

plane = scene.add_entity(
    gs.morphs.Plane(),
)

franka = scene.add_entity(
    gs.morphs.MJCF(file='xml/franka_emika_panda/panda.xml'),
)

scene.build(n_envs=30000)

# 控制所有机器人
franka.control_dofs_position(
    torch.tile(
        torch.tensor([0, 0, 0, -1.0, 0, 0, 0, 0.02, 0.02], device=gs.device), (30000, 1)
    ),
)

for i in range(1000):
    scene.step()

test_genesis_bottle.py

pyimport argparse
import time

import genesis as gs
import numpy as np
import torch

parser = argparse.ArgumentParser()
parser.add_argument("-B", type=int, default=1) # batch size
parser.add_argument("-v", action="store_true", default=False) # visualize
parser.add_argument("-r", action="store_true", default=False) # random action
                    
args = parser.parse_args()

########################## init ##########################
gs.init(backend=gs.gpu)

########################## create a scene ##########################
scene = gs.Scene(
    show_viewer=args.v,
    rigid_options=gs.options.RigidOptions(
        dt=0.01,
        constraint_solver=gs.constraint_solver.Newton,
        enable_self_collision=True,
    ),
)

########################## entities ##########################
plane = scene.add_entity(
    gs.morphs.Plane(),
)

obj = scene.add_entity(
    morph=gs.morphs.URDF(
        file="../assets/urdf/bottle/bottle.urdf",
        scale=0.09,
        pos=(0.65, 0.0, 0.036),
        euler=(0, 90, 0),
    ),
)
franka = scene.add_entity(
    gs.morphs.MJCF(file="../assets/xml/franka_emika_panda/panda.xml")
)

########################## build ##########################
n_envs = args.B
scene.build(n_envs=n_envs)

# set control gains (official value taken from mujoco franka xml)
franka.set_dofs_kp(
    np.array([4500, 4500, 3500, 3500, 2000, 2000, 2000, 100, 100]),
)
franka.set_dofs_kv(
    np.array([450, 450, 350, 350, 200, 200, 200, 10, 10]),
)
franka.set_dofs_force_range(
    np.array([-87, -87, -87, -87, -12, -12, -12, -100, -100]),
    np.array([87, 87, 87, 87, 12, 12, 12, 100, 100]),
)

motors_dof = np.arange(7)
fingers_dof = np.arange(7, 9)

grasp_qpos = np.array([-0.9937,  1.4588,  1.3058, -1.6924, -1.4882,  1.8461,  1.4577,  0.04, 0.04])
lift_qpos = np.array([-1.0411,  1.2861,  1.5200, -1.7065, -1.2946,  1.6572,  1.4315,  0.04, 0.04])
franka.set_dofs_position(grasp_qpos)

# grasp
franka.control_dofs_position(grasp_qpos[:-2], motors_dof)
franka.control_dofs_force(np.array([-4, -4]), fingers_dof)


for i in range(100):
    scene.step()

# lift
franka.control_dofs_position(lift_qpos[:-2], motors_dof)
for i in range(50):
    scene.step()

ref_pos = torch.tile(torch.tensor(lift_qpos[:7]), [n_envs, 1]).cuda()

t0 = time.perf_counter()
for i in range(500):
    if args.r and i % 2 == 0: # match maniskill's 50hz control freq
        # match maniskill's random action
        franka.control_dofs_position(ref_pos + torch.rand((n_envs, 7), device='cuda')*0.05 - 0.025, motors_dof)
    scene.step()
t1 = time.perf_counter()

print(f'per env: {500 / (t1 - t0):,.2f} FPS')
print(f'total  : {500 / (t1 - t0) * n_envs:,.2f} FPS')

03test_genesis_cube.py

pyimport argparse
import time

import genesis as gs
import numpy as np
import torch

parser = argparse.ArgumentParser()
parser.add_argument("-B", type=int, default=1) # batch size
parser.add_argument("-v", action="store_true", default=False) # visualize
parser.add_argument("-r", action="store_true", default=False) # random action
parser.add_argument("-m", action="store_true", default=False) # move along a traj
parser.add_argument("--mjcf", action="store_true", default=False) # use mjcf
                    
args = parser.parse_args()

########################## init ##########################
gs.init(backend=gs.gpu)

########################## create a scene ##########################
scene = gs.Scene(
    show_viewer=args.v,
    rigid_options=gs.options.RigidOptions(
        dt=0.01,
        constraint_solver=gs.constraint_solver.Newton,
        enable_self_collision=True,
    ),
)

########################## entities ##########################
plane = scene.add_entity(
    gs.morphs.Plane(),
)

cube = scene.add_entity(
    gs.morphs.Box(
        size=(0.04, 0.04, 0.04),
        pos=(0.65, 0.0, 0.02),
    ),
)
franka = scene.add_entity(
    gs.morphs.MJCF(file="../assets/xml/franka_emika_panda/panda.xml") if args.mjcf else gs.morphs.URDF(file="../assets/urdf/franka_description/robots/franka_panda.urdf", fixed=True),
)

########################## build ##########################
n_envs = args.B
scene.build(n_envs=n_envs)

# set control gains (official value taken from mujoco franka xml)
franka.set_dofs_kp(
    np.array([4500, 4500, 3500, 3500, 2000, 2000, 2000, 100, 100]),
)
franka.set_dofs_kv(
    np.array([450, 450, 350, 350, 200, 200, 200, 10, 10]),
)
franka.set_dofs_force_range(
    np.array([-87, -87, -87, -87, -12, -12, -12, -100, -100]),
    np.array([87, 87, 87, 87, 12, 12, 12, 100, 100]),
)

motors_dof = np.arange(7)
fingers_dof = np.arange(7, 9)

grasp_qpos = np.array([-1.0104,  1.5623,  1.3601, -1.6840, -1.5863,  1.7810,  1.4598,  0.0400, 0.0400])
lift_qpos = np.array([-1.0426,  1.4028,  1.5634, -1.7114, -1.4055,  1.6015,  1.4510,  0., 0.])
franka.set_dofs_position(grasp_qpos)

# grasp
franka.control_dofs_position(grasp_qpos[:-2], motors_dof)
franka.control_dofs_force(np.array([-0.5, -0.5]), fingers_dof)


for i in range(100):
    scene.step()

# lift
franka.control_dofs_position(lift_qpos[:-2], motors_dof)
for i in range(50):
    scene.step()

ref_pos = torch.tile(torch.tensor(lift_qpos[:7]), [n_envs, 1]).cuda()

t0 = time.perf_counter()
if args.m:
    init_qpos = torch.tile(torch.tensor([-1.0426,  1.4028,  1.5634, -1.7114, -1.4055,  1.6015,  1.4510]), [n_envs, 1]).cuda()
    target_qpos = torch.tile(torch.tensor([1.0426,  -1.4028,  1.5634, -1.7114, -1.4055,  1.6015,  1.4510]), [n_envs, 1]).cuda()
    for i in range(500):
        if i % 2 == 0: # match maniskill's 50hz control freq
            franka.control_dofs_position(init_qpos + (target_qpos - init_qpos) * i / 500, motors_dof)
        scene.step()
else:
    for i in range(500):
        if args.r and i % 2 == 0: # match maniskill's 50hz control freq
            # match maniskill's random action
            franka.control_dofs_position(ref_pos + torch.rand((n_envs, 7), device='cuda')*0.05 - 0.025, motors_dof)
        scene.step()
t1 = time.perf_counter()

print(f'per env: {500 / (t1 - t0):,.2f} FPS')
print(f'total  : {500 / (t1 - t0) * n_envs:,.2f} FPS')

逆运动学

py