📸 可视化与渲染
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