保存渲染图像和 3D 重投影#
通过 scripts/tutorials/04_sensors/run_usd_camera.py 保存相机图像,并将深度反投影为三维点。
run_usd_camera.py 的代码
1# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2# All rights reserved.
3#
4# SPDX-License-Identifier: BSD-3-Clause
5
6"""
7This script shows how to use the camera sensor from the Isaac Lab framework.
8
9Instead of using the simulator or OpenGL convention for the camera, we use the robotics or ROS convention.
10
11.. code-block:: bash
12
13 # Usage with GUI
14 uv run python scripts/tutorials/04_sensors/run_usd_camera.py --viz kit
15
16 # Usage with no visualizer
17 uv run python scripts/tutorials/04_sensors/run_usd_camera.py
18
19"""
20
21"""Parse the command-line arguments first."""
22
23import argparse
24from typing import TYPE_CHECKING
25
26from isaaclab.app import add_launcher_args, launch_simulation
27
28# add argparse arguments
29parser = argparse.ArgumentParser(description="This script demonstrates how to use the camera sensor.")
30parser.add_argument(
31 "--draw",
32 action="store_true",
33 default=False,
34 help="Draw the pointcloud from camera at index specified by ``--camera_id``.",
35)
36parser.add_argument(
37 "--save",
38 action="store_true",
39 default=False,
40 help="Save the data from camera at index specified by ``--camera_id``.",
41)
42parser.add_argument(
43 "--camera_id",
44 type=int,
45 choices={0, 1},
46 default=0,
47 help=(
48 "The camera ID to use for displaying points or saving the camera data. Default is 0."
49 " The viewport will always initialize with the perspective of camera 0."
50 ),
51)
52# append simulation launcher cli args
53add_launcher_args(parser)
54# parse the arguments
55args_cli = parser.parse_args()
56# Camera sensors require the rendering extensions in headless and viewport-free launches.
57args_cli.enable_cameras = True
58
59"""Rest everything follows."""
60
61import os
62import random
63
64import numpy as np
65import torch
66from isaaclab_physx.renderers import IsaacRtxRendererCfg
67
68import isaaclab.sim as sim_utils
69from isaaclab.assets import RigidObject, RigidObjectCfg
70from isaaclab.markers import VisualizationMarkers
71from isaaclab.markers.config import RAY_CASTER_MARKER_CFG
72from isaaclab.sensors.camera import CameraCfg
73from isaaclab.sensors.camera.utils import create_pointcloud_from_depth, save_images_to_file
74from isaaclab.utils import instantiate, replace
75
76if TYPE_CHECKING:
77 from isaaclab.sensors.camera import Camera
78
79
80def define_sensor() -> "Camera":
81 """Defines the camera sensor to add to the scene."""
82 # Setup camera sensor
83 # In contrast to the ray-cast camera, we spawn the prim at these locations.
84 # This means the camera sensor will be attached to these prims.
85 sim_utils.create_prim("/World/Origin_00", "Xform")
86 sim_utils.create_prim("/World/Origin_01", "Xform")
87 camera_cfg = CameraCfg(
88 prim_path="/World/Origin_[^/]+/CameraSensor",
89 update_period=0,
90 height=480,
91 width=640,
92 data_types=[
93 "rgb",
94 "distance_to_image_plane",
95 "normals",
96 "semantic_segmentation",
97 "instance_segmentation",
98 "instance_id_segmentation_fast",
99 ],
100 renderer_cfg=IsaacRtxRendererCfg(
101 colorize_semantic_segmentation=True,
102 colorize_instance_id_segmentation=True,
103 colorize_instance_segmentation=True,
104 ),
105 spawn=sim_utils.PinholeCameraCfg(
106 focal_length=24.0, focus_distance=400.0, horizontal_aperture=20.955, clipping_range=(0.1, 1.0e5)
107 ),
108 )
109 # Create camera
110 camera = instantiate(camera_cfg)
111
112 return camera
113
114
115def design_scene() -> dict:
116 """Design the scene."""
117 # Populate scene
118 # -- Ground-plane
119 cfg = sim_utils.GroundPlaneCfg()
120 cfg.func("/World/defaultGroundPlane", cfg)
121 # -- Lights
122 cfg = sim_utils.DistantLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
123 cfg.func("/World/Light", cfg)
124
125 # Create a dictionary for the scene entities
126 scene_entities = {}
127
128 # Xform to hold objects
129 sim_utils.create_prim("/World/Objects", "Xform")
130 # Random objects
131 for i in range(8):
132 # sample random position
133 position = np.random.rand(3) - np.asarray([0.05, 0.05, -1.0])
134 position *= np.asarray([1.5, 1.5, 0.5])
135 # sample random color
136 color = (random.random(), random.random(), random.random())
137 # choose random prim type
138 prim_type = random.choice(["Cube", "Cone", "Cylinder"])
139 common_properties = {
140 "rigid_props": sim_utils.UsdPhysicsRigidBodyCfg(),
141 "mass_props": sim_utils.MassCfg(mass=5.0),
142 "collision_props": sim_utils.UsdPhysicsCollisionCfg(),
143 "visual_material": sim_utils.PreviewSurfaceCfg(diffuse_color=color, metallic=0.5),
144 "semantic_tags": [("class", prim_type)],
145 }
146 if prim_type == "Cube":
147 shape_cfg = sim_utils.CuboidCfg(size=(0.25, 0.25, 0.25), **common_properties)
148 elif prim_type == "Cone":
149 shape_cfg = sim_utils.ConeCfg(radius=0.1, height=0.25, **common_properties)
150 elif prim_type == "Cylinder":
151 shape_cfg = sim_utils.CylinderCfg(radius=0.25, height=0.25, **common_properties)
152 # Rigid Object
153 obj_cfg = RigidObjectCfg(
154 prim_path=f"/World/Objects/Obj_{i:02d}",
155 spawn=shape_cfg,
156 init_state=RigidObjectCfg.InitialStateCfg(pos=position),
157 )
158 scene_entities[f"rigid_object{i}"] = RigidObject(cfg=obj_cfg)
159
160 # Sensors
161 camera = define_sensor()
162
163 # return the scene information
164 scene_entities["camera"] = camera
165 return scene_entities
166
167
168def run_simulator(sim: sim_utils.SimulationContext, scene_entities: dict):
169 """Run the simulator."""
170 # extract entities for simplified notation
171 camera: Camera = scene_entities["camera"]
172
173 # Create the output directory
174 output_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "camera")
175 os.makedirs(output_dir, exist_ok=True)
176
177 # Camera positions, targets, orientations
178 camera_positions = torch.tensor([[2.5, 2.5, 2.5], [-2.5, -2.5, 2.5]], device=sim.device)
179 camera_targets = torch.tensor([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], device=sim.device)
180 # These orientations are in ROS-convention, and will position the cameras to view the origin
181 camera_orientations = torch.tensor( # noqa: F841
182 [[-0.1759, 0.3399, 0.8205, -0.4247], [-0.4247, 0.8205, -0.3399, 0.1759]], device=sim.device
183 )
184
185 # Set pose: There are two ways to set the pose of the camera.
186 # -- Option-1: Set pose using view
187 camera.set_world_poses_from_view(camera_positions, camera_targets)
188 # -- Option-2: Set pose using ROS
189 # camera.set_world_poses(camera_positions, camera_orientations, convention="ros")
190
191 # Index of the camera to use for visualization and saving
192 camera_index = args_cli.camera_id
193
194 # Create the markers for the --draw option outside of the simulation loop
195 if sim.get_setting("/isaaclab/has_gui") and args_cli.draw:
196 cfg = replace(RAY_CASTER_MARKER_CFG, prim_path="/Visuals/CameraPointCloud")
197 cfg.markers["hit"].radius = 0.002
198 pc_markers = VisualizationMarkers(cfg)
199
200 # Simulate physics
201 while sim.is_running():
202 # Step simulation
203 sim.step()
204 # Update camera data
205 camera.update(dt=sim.get_physics_dt())
206
207 # Print camera info
208 print(camera)
209 if "rgb" in camera.data.output.keys():
210 print("Received shape of rgb image : ", camera.data.output["rgb"].shape)
211 if "distance_to_image_plane" in camera.data.output.keys():
212 print("Received shape of depth image : ", camera.data.output["distance_to_image_plane"].shape)
213 if "normals" in camera.data.output.keys():
214 print("Received shape of normals : ", camera.data.output["normals"].shape)
215 if "semantic_segmentation" in camera.data.output.keys():
216 print("Received shape of semantic segm. : ", camera.data.output["semantic_segmentation"].shape)
217 if "instance_segmentation" in camera.data.output.keys():
218 print("Received shape of instance segm. : ", camera.data.output["instance_segmentation"].shape)
219 if "instance_id_segmentation_fast" in camera.data.output.keys():
220 print("Received shape of instance id segm.: ", camera.data.output["instance_id_segmentation_fast"].shape)
221 print("-------------------------------")
222
223 # Extract camera data
224 if args_cli.save:
225 # Save the camera outputs at camera_index: 8-bit color images as PNG, other data (depth, normals) as NumPy
226 for key, data in camera.data.output.items():
227 file_stem = os.path.join(output_dir, f"{key}_{camera.frame[camera_index]}")
228 if data.torch.dtype == torch.uint8:
229 save_images_to_file(data.torch[camera_index : camera_index + 1].float() / 255.0, f"{file_stem}.png")
230 else:
231 np.save(f"{file_stem}.npy", data.torch[camera_index].cpu().numpy())
232
233 # Draw pointcloud if there is a GUI and --draw has been passed
234 if (
235 sim.get_setting("/isaaclab/has_gui")
236 and args_cli.draw
237 and "distance_to_image_plane" in camera.data.output.keys()
238 ):
239 # Derive pointcloud from camera at camera_index
240 pointcloud = create_pointcloud_from_depth(
241 intrinsic_matrix=camera.data.intrinsic_matrices[camera_index],
242 depth=camera.data.output["distance_to_image_plane"][camera_index],
243 position=camera.data.pos_w[camera_index],
244 orientation=camera.data.quat_w_ros[camera_index],
245 device=sim.device,
246 )
247
248 # In the first few steps, things are still being instanced and Camera.data
249 # can be empty. If we attempt to visualize an empty pointcloud it will crash
250 # the sim, so we check that the pointcloud is not empty.
251 if pointcloud.size()[0] > 0:
252 pc_markers.visualize(translations=pointcloud)
253
254
255def main():
256 """Main function."""
257 # Configure the simulation
258 sim_cfg = sim_utils.SimulationCfg(device=args_cli.device)
259 # Launch the simulator runtime that the configuration needs
260 with launch_simulation(sim_cfg, args_cli):
261 # Initialize the simulation context
262 sim = sim_utils.SimulationContext(sim_cfg)
263 # Set main camera
264 sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0])
265 # Design scene
266 scene_entities = design_scene()
267 # Play simulator
268 sim.reset()
269 # Now we are ready!
270 print("[INFO]: Setup complete...")
271 # Run simulator
272 run_simulator(sim, scene_entities)
273
274
275if __name__ == "__main__":
276 # run the main function
277 main()
将图像保存到文件#
使用 save_images_to_file() 工具保存相机输出。它将一批图像写入 PNG 文件,不依赖渲染后端。脚本在仿真循环开始前一次性创建输出目录:
# Create the output directory
output_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "camera")
os.makedirs(output_dir, exist_ok=True)
推进仿真器时,对 camera_index 指定的相机,按每种数据类型、每帧保存一次输出:8 位彩色输出(RGB 图像和彩色分割图)保存为 PNG;深度和法线等浮点输出保存为 NumPy .npy 文件。
# Save the camera outputs at camera_index: 8-bit color images as PNG, other data (depth, normals) as NumPy
for key, data in camera.data.output.items():
file_stem = os.path.join(output_dir, f"{key}_{camera.frame[camera_index]}")
if data.torch.dtype == torch.uint8:
save_images_to_file(data.torch[camera_index : camera_index + 1].float() / 255.0, f"{file_stem}.png")
else:
np.save(f"{file_stem}.npy", data.torch[camera_index].cpu().numpy())
投影到 3D 空间#
我们包含实用程序来将深度图像投影到 3D 空间。重新投影操作使用 PyTorch 操作来进行,这可以加快计算速度。
from isaaclab.utils.math import transform_points, unproject_depth
# Pointcloud in world frame
points_3d_cam = unproject_depth(
camera.data.output["distance_to_image_plane"], camera.data.intrinsic_matrices
)
points_3d_world = transform_points(points_3d_cam, camera.data.pos_w, camera.data.quat_w_ros)
或者,我们可以使用 isaaclab.sensors.camera.utils.create_pointcloud_from_depth() 函数从深度图像创建点云,并将其转换为世界框架。
# Derive pointcloud from camera at camera_index
pointcloud = create_pointcloud_from_depth(
intrinsic_matrix=camera.data.intrinsic_matrices[camera_index],
depth=camera.data.output["distance_to_image_plane"][camera_index],
position=camera.data.pos_w[camera_index],
orientation=camera.data.quat_w_ros[camera_index],
device=sim.device,
)
生成的点云可通过 Isaac Lab 的 VisualizationMarkers 显示。配套教程使用 RAY_CASTER_MARKER_CFG,并调用 visualize() 在视口中绘制点。
# In the first few steps, things are still being instanced and Camera.data
# can be empty. If we attempt to visualize an empty pointcloud it will crash
# the sim, so we check that the pointcloud is not empty.
if pointcloud.size()[0] > 0:
pc_markers.visualize(translations=pointcloud)
执行脚本#
要运行附带的脚本,请执行以下命令:
# Usage with saving and drawing
uv run python scripts/tutorials/04_sensors/run_usd_camera.py --save --draw --viz kit
# Usage with saving only (no visualizer)
uv run python scripts/tutorials/04_sensors/run_usd_camera.py --save
仿真开始后,可以观察不同物体下落。脚本会在 IsaacLab/scripts/tutorials/04_sensors 目录下创建输出文件夹,将图像保存为 PNG。同时,视口中应显示绘制在三维空间中的点云。
要停止仿真,关闭窗口,或在终端中使用 Ctrl+C 。