向机器人添加传感器#
尽管资产类别允许我们创建和模拟机器人的实体,但传感器有助于获取有关环境的信息。它们通常以比模拟更新频率更低的频率更新,并且有助于获取不同的本体感知和外部感知信息。例如,相机传感器可以用于获取环境的视觉信息,接触传感器可以用于获取机器人与环境的接触信息。
在本教程中,我们将看到如何向机器人添加不同的传感器。我们将在本教程中使用 ANYmal-C 机器人。ANYmal-C 机器人是一个有 12 个自由度的四足机器人。它有 4 条腿,每个腿有 3 个自由度。机器人具有以下传感器:
机器人头部的相机传感器,提供 RGB-D 图像
提供地形高度信息的高度扫描传感器
机器人脚部的接触传感器,提供接触信息
我们将从之前关于 使用交互式场景 的教程中继续本教程,在那里我们了解了 scene.InteractiveScene
类。
代码#
本教程对应于 source/standalone/tutorials/04_sensors
目录中的 add_sensors_on_robot.py
脚本。
add_sensors_on_robot.py 的代码
1# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
2# All rights reserved.
3#
4# SPDX-License-Identifier: BSD-3-Clause
5
6"""
7This script demonstrates how to add and simulate on-board sensors for a robot.
8
9We add the following sensors on the quadruped robot, ANYmal-C (ANYbotics):
10
11* USD-Camera: This is a camera sensor that is attached to the robot's base.
12* Height Scanner: This is a height scanner sensor that is attached to the robot's base.
13* Contact Sensor: This is a contact sensor that is attached to the robot's feet.
14
15.. code-block:: bash
16
17 # Usage
18 ./isaaclab.sh -p source/standalone/tutorials/04_sensors/add_sensors_on_robot.py --enable_cameras
19
20"""
21
22"""Launch Isaac Sim Simulator first."""
23
24import argparse
25
26from omni.isaac.lab.app import AppLauncher
27
28# add argparse arguments
29parser = argparse.ArgumentParser(description="Tutorial on adding sensors on a robot.")
30parser.add_argument("--num_envs", type=int, default=2, help="Number of environments to spawn.")
31# append AppLauncher cli args
32AppLauncher.add_app_launcher_args(parser)
33# parse the arguments
34args_cli = parser.parse_args()
35
36# launch omniverse app
37app_launcher = AppLauncher(args_cli)
38simulation_app = app_launcher.app
39
40"""Rest everything follows."""
41
42import torch
43
44import omni.isaac.lab.sim as sim_utils
45from omni.isaac.lab.assets import ArticulationCfg, AssetBaseCfg
46from omni.isaac.lab.scene import InteractiveScene, InteractiveSceneCfg
47from omni.isaac.lab.sensors import CameraCfg, ContactSensorCfg, RayCasterCfg, patterns
48from omni.isaac.lab.utils import configclass
49
50##
51# Pre-defined configs
52##
53from omni.isaac.lab_assets.anymal import ANYMAL_C_CFG # isort: skip
54
55
56@configclass
57class SensorsSceneCfg(InteractiveSceneCfg):
58 """Design the scene with sensors on the robot."""
59
60 # ground plane
61 ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())
62
63 # lights
64 dome_light = AssetBaseCfg(
65 prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
66 )
67
68 # robot
69 robot: ArticulationCfg = ANYMAL_C_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")
70
71 # sensors
72 camera = CameraCfg(
73 prim_path="{ENV_REGEX_NS}/Robot/base/front_cam",
74 update_period=0.1,
75 height=480,
76 width=640,
77 data_types=["rgb", "distance_to_image_plane"],
78 spawn=sim_utils.PinholeCameraCfg(
79 focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 1.0e5)
80 ),
81 offset=CameraCfg.OffsetCfg(pos=(0.510, 0.0, 0.015), rot=(0.5, -0.5, 0.5, -0.5), convention="ros"),
82 )
83 height_scanner = RayCasterCfg(
84 prim_path="{ENV_REGEX_NS}/Robot/base",
85 update_period=0.02,
86 offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)),
87 attach_yaw_only=True,
88 pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]),
89 debug_vis=True,
90 mesh_prim_paths=["/World/defaultGroundPlane"],
91 )
92 contact_forces = ContactSensorCfg(
93 prim_path="{ENV_REGEX_NS}/Robot/.*_FOOT", update_period=0.0, history_length=6, debug_vis=True
94 )
95
96
97def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene):
98 """Run the simulator."""
99 # Define simulation stepping
100 sim_dt = sim.get_physics_dt()
101 sim_time = 0.0
102 count = 0
103
104 # Simulate physics
105 while simulation_app.is_running():
106 # Reset
107 if count % 500 == 0:
108 # reset counter
109 count = 0
110 # reset the scene entities
111 # root state
112 # we offset the root state by the origin since the states are written in simulation world frame
113 # if this is not done, then the robots will be spawned at the (0, 0, 0) of the simulation world
114 root_state = scene["robot"].data.default_root_state.clone()
115 root_state[:, :3] += scene.env_origins
116 scene["robot"].write_root_state_to_sim(root_state)
117 # set joint positions with some noise
118 joint_pos, joint_vel = (
119 scene["robot"].data.default_joint_pos.clone(),
120 scene["robot"].data.default_joint_vel.clone(),
121 )
122 joint_pos += torch.rand_like(joint_pos) * 0.1
123 scene["robot"].write_joint_state_to_sim(joint_pos, joint_vel)
124 # clear internal buffers
125 scene.reset()
126 print("[INFO]: Resetting robot state...")
127 # Apply default actions to the robot
128 # -- generate actions/commands
129 targets = scene["robot"].data.default_joint_pos
130 # -- apply action to the robot
131 scene["robot"].set_joint_position_target(targets)
132 # -- write data to sim
133 scene.write_data_to_sim()
134 # perform step
135 sim.step()
136 # update sim-time
137 sim_time += sim_dt
138 count += 1
139 # update buffers
140 scene.update(sim_dt)
141
142 # print information from the sensors
143 print("-------------------------------")
144 print(scene["camera"])
145 print("Received shape of rgb image: ", scene["camera"].data.output["rgb"].shape)
146 print("Received shape of depth image: ", scene["camera"].data.output["distance_to_image_plane"].shape)
147 print("-------------------------------")
148 print(scene["height_scanner"])
149 print("Received max height value: ", torch.max(scene["height_scanner"].data.ray_hits_w[..., -1]).item())
150 print("-------------------------------")
151 print(scene["contact_forces"])
152 print("Received max contact force of: ", torch.max(scene["contact_forces"].data.net_forces_w).item())
153
154
155def main():
156 """Main function."""
157
158 # Initialize the simulation context
159 sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device)
160 sim = sim_utils.SimulationContext(sim_cfg)
161 # Set main camera
162 sim.set_camera_view(eye=[3.5, 3.5, 3.5], target=[0.0, 0.0, 0.0])
163 # design scene
164 scene_cfg = SensorsSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0)
165 scene = InteractiveScene(scene_cfg)
166 # Play the simulator
167 sim.reset()
168 # Now we are ready!
169 print("[INFO]: Setup complete...")
170 # Run the simulator
171 run_simulator(sim, scene)
172
173
174if __name__ == "__main__":
175 # run the main function
176 main()
177 # close sim app
178 simulation_app.close()
代码解释#
与之前的教程类似,我们添加资产到场景中时,传感器也是使用场景配置添加到场景中的。所有传感器都继承自 sensors.SensorBase
类,并通过其各自的配置类进行配置。每个传感器实例可以定义自己的更新周期,即传感器更新的频率。更新周期通过 sensors.SensorBaseCfg.update_period
属性以秒为单位指定。
根据指定的路径和传感器类型,传感器将附加到场景中的 prims 上。它们可能有一个关联的 prim 在场景中创建,或者它们可能附加到现有的 prim 上。例如,相机传感器有一个对应的 prim 在场景中创建,而对于接触传感器,激活接触报告是刚体 prim 上的一个属性。
接下来,我们介绍本教程中使用的不同传感器及其配置。有关更多描述,请查看 sensors
模块。
相机传感器#
使用 sensors.CameraCfg
定义相机。它基于 USD 相机传感器,并使用 Omniverse Replicator API 捕获不同的数据类型。由于它在场景中有一个对应的 prim,因此 prims 会在指定的 prim 路径上创建在场景中。
相机传感器的配置包括以下参数:
spawn
: 要创建的 USD 相机类型。这可以是PinholeCameraCfg
或FisheyeCameraCfg
。offset
: 相机传感器相对于父 prim 的偏移量。data_types
: 要捕获的数据类型。这可以是rgb
、distance_to_image_plane
、normals
等 USD 相机传感器支持的其他类型。
为了将 RGB-D 相机传感器附加到机器人的头部,我们指定相对于机器人基准框架的偏移量。偏移量是相对于基准框架的平移和旋转指定的,以及指定偏移量的 convnetion
。
接下来,我们展示本教程中使用的相机传感器的配置。我们将更新周期设置为0.1秒,这意味着相机传感器以10Hz 进行更新。prim 路径表达式设置为 {ENV_REGEX_NS}/Robot/base/front_cam
,其中 {ENV_REGEX_NS}
是环境命名空间, "Robot"
是机器人名称, "base"
是相机附加到的 prim 的名称, "front_cam"
是与相机传感器关联的 prim 的名称。
camera = CameraCfg(
prim_path="{ENV_REGEX_NS}/Robot/base/front_cam",
update_period=0.1,
height=480,
width=640,
data_types=["rgb", "distance_to_image_plane"],
spawn=sim_utils.PinholeCameraCfg(
focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 1.0e5)
),
offset=CameraCfg.OffsetCfg(pos=(0.510, 0.0, 0.015), rot=(0.5, -0.5, 0.5, -0.5), convention="ros"),
)
高度扫描传感器#
高度扫描器被实现为使用 NVIDIA Warp 光线投射内核的虚拟传感器。通过 sensors.RayCasterCfg
,我们可以指定要投射的光线模式和要对其进行投射的网格。由于它们是虚拟传感器,因此在场景中没有为其创建对应的 prim。而是将其附加到场景中的一个 prim 上,该 prim 用于指定传感器的位置。
对于本教程,基于光线集的高度扫描器附加到机器人的基准框架。使用 pattern
属性指定光线的模式。对于均匀网格模式,我们使用 GridPatternCfg
指定模式。由于我们只关心高度信息,因此我们不需要考虑机器人的滚动和俯仰。因此,我们将 attach_yaw_only
设置为 true。
对于高度扫描器,可以将光线击中网格的点可视化。通过设置 debug_vis
属性为 true 实现此功能。
高度扫描器的整体配置如下:
height_scanner = RayCasterCfg(
prim_path="{ENV_REGEX_NS}/Robot/base",
update_period=0.02,
offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)),
attach_yaw_only=True,
pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]),
debug_vis=True,
mesh_prim_paths=["/World/defaultGroundPlane"],
)
接触传感器#
接触传感器包装 PhysX 接触报告 API 以获取机器人与环境的接触信息。由于它依赖于PhysX,因此接触传感器希望启用机器人的刚体上的接触报告 API。可以通过在资产配置中将 activate_contact_sensors
设置为 true 来完成此操作。
通过 sensors.ContactSensorCfg
,可以指定要获取其接触信息的 prims。可以设置附加标志以获取有关接触的更多信息,例如过滤 prims 之间的接触空气时间、接触力等。
在本教程中,我们将接触传感器附加到机器人的脚部。机器人的脚部分别命名为 "LF_FOOT"
、"RF_FOOT"
、"LH_FOOT"
和 "RF_FOOT"
。我们传递一个正则表达式 " .*_FOOT"
来简化 prim 路径的指定。该正则表达式与以 "_FOOT"
结尾的所有 prims 匹配。
将更新周期设置为0以使传感器以模拟相同的频率更新。此外,对于接触传感器,可以指定要存储的接触信息的历史长度。对于本教程,我们将历史长度设置为6,这意味着存储最后6个模拟步骤的接触信息。
接触传感器的整体配置如下:
contact_forces = ContactSensorCfg(
prim_path="{ENV_REGEX_NS}/Robot/.*_FOOT", update_period=0.0, history_length=6, debug_vis=True
)
运行仿真循环#
与使用资产时类似,仅当播放模拟时才会初始化传感器的缓冲区和物理句柄,即在创建场景后调用 sim.reset()
是重要的。
# Play the simulator
sim.reset()
此外,仿真循环类似于以前的教程。传感器作为场景更新的一部分进行更新,并且它们内部处理基于其更新周期的缓冲区的更新。
可以通过它们的 data
属性访问传感器的数据。作为示例,我们展示了如何访问本教程中创建的不同传感器的数据:
# print information from the sensors
print("-------------------------------")
print(scene["camera"])
print("Received shape of rgb image: ", scene["camera"].data.output["rgb"].shape)
print("Received shape of depth image: ", scene["camera"].data.output["distance_to_image_plane"].shape)
print("-------------------------------")
print(scene["height_scanner"])
print("Received max height value: ", torch.max(scene["height_scanner"].data.ray_hits_w[..., -1]).item())
print("-------------------------------")
print(scene["contact_forces"])
print("Received max contact force of: ", torch.max(scene["contact_forces"].data.net_forces_w).item())
代码执行#
既然我们已经查看了代码,让我们运行脚本并查看结果:
./isaaclab.sh -p source/standalone/tutorials/04_sensors/add_sensors_on_robot.py --num_envs 2 --enable_cameras
此命令应该打开一个带有地面平面、灯光和两个四足机器人的场景。在机器人周围,您应该看到指示光线击中网格点的红色球。此外,您可以将视口切换到相机视图,以查看相机传感器捕获的 RGB 图像。请查看 这里 获取有关如何将视口切换到相机视图的更多信息。
要停止仿真,您可以关闭窗口,或在终端中按 Ctrl+C
。
在本教程中,我们讨论了创建和使用不同的传感器, sensors
模块中还有许多其他传感器可用。我们在 source/standalone/tutorials/04_sensors
目录中包含了使用这些传感器的最低限度示例。为了完整起见,可以使用以下命令运行这些脚本:
# Frame Transformer
./isaaclab.sh -p source/standalone/tutorials/04_sensors/run_frame_transformer.py
# Ray Caster
./isaaclab.sh -p source/standalone/tutorials/04_sensors/run_ray_caster.py
# Ray Caster Camera
./isaaclab.sh -p source/standalone/tutorials/04_sensors/run_ray_caster_camera.py
# USD Camera
./isaaclab.sh -p source/standalone/tutorials/04_sensors/run_usd_camera.py