Skip to content

Commit a84bdd9

Browse files
committed
Fix point cloud generation from depth map.
1 parent ae58b2c commit a84bdd9

File tree

2 files changed

+80
-12
lines changed

2 files changed

+80
-12
lines changed

genesis/vis/camera.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -471,21 +471,23 @@ def get_segmentation_idx_dict(self):
471471
@gs.assert_built
472472
def render_pointcloud(self, world_frame=True):
473473
"""
474-
Render a partial point cloud from the camera view. Returns a (res[0], res[1], 3) numpy array representing the point cloud in each pixel.
474+
Render a partial point cloud from the camera view.
475+
475476
Parameters
476477
----------
477478
world_frame : bool, optional
478479
Whether the point cloud is on camera frame or world frame.
480+
479481
Returns
480482
-------
481483
pc : np.ndarray
482-
the point cloud
484+
Numpy array of shape (res[0], res[1], 3) representing the point cloud in each pixel.
483485
mask_arr : np.ndarray
484486
The valid depth mask.
485487
"""
486488
self._rasterizer.update_scene()
487489
rgb_arr, depth_arr, seg_idxc_arr, normal_arr = self._rasterizer.render_camera(
488-
self, False, True, False, normal=False
490+
self, rgb=False, depth=True, segmentation=False, normal=False
489491
)
490492

491493
def opengl_projection_matrix_to_intrinsics(P: np.ndarray, width: int, height: int):
@@ -536,16 +538,11 @@ def backproject_depth_to_pointcloud(K: np.ndarray, depth: np.ndarray, pose, worl
536538
world_y = normalized_y * depth_grid / _fy
537539
world_z = depth_grid
538540

539-
pc = np.stack((world_x, world_y, world_z), axis=1)
540-
541-
point_cloud_h = np.concatenate((pc, np.ones((len(pc), 1), dtype=np.float32)), axis=1)
541+
point_cloud = np.stack((world_x, world_y, world_z, np.ones((depth.size,), dtype=np.float32)), axis=-1)
542542
if world:
543-
point_cloud_world = point_cloud_h @ pose.T
544-
point_cloud_world = point_cloud_world[:, :3].reshape((*depth.shape, 3))
545-
return point_cloud_world, mask
546-
else:
547-
point_cloud = point_cloud_h[:, :3].reshape((*depth.shape, 3))
548-
return point_cloud, mask
543+
point_cloud = point_cloud @ pose.T
544+
point_cloud = point_cloud[:, :3].reshape((*depth.shape, 3))
545+
return point_cloud, mask
549546

550547
intrinsic_K = opengl_projection_matrix_to_intrinsics(
551548
self._rasterizer._camera_nodes[self.uid].camera.get_projection_matrix(),

tests/test_render.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,77 @@ def test_batched_offscreen_rendering(show_viewer, tol):
271271
assert_allclose(steps_rgb_arrays[0][i], steps_rgb_arrays[1][i], tol=tol)
272272

273273

274+
@pytest.mark.required
275+
def test_point_cloud(show_viewer):
276+
CAMERA_DIST = 8.0
277+
OBJ_OFFSET = 10.0
278+
BOX_HALFSIZE = 1.0
279+
SPHERE_RADIUS = 1.0
280+
281+
scene = gs.Scene(
282+
show_viewer=show_viewer,
283+
show_FPS=False,
284+
)
285+
scene.add_entity(
286+
morph=gs.morphs.Sphere(
287+
pos=(0.0, OBJ_OFFSET, 0.0),
288+
radius=SPHERE_RADIUS,
289+
fixed=True,
290+
),
291+
)
292+
camera_sphere = scene.add_camera(
293+
pos=(0.0, OBJ_OFFSET, CAMERA_DIST),
294+
lookat=(0.0, OBJ_OFFSET, 0.0),
295+
GUI=show_viewer,
296+
)
297+
scene.add_entity(
298+
morph=gs.morphs.Box(
299+
pos=(0.0, -OBJ_OFFSET, 0.0),
300+
size=(2.0 * BOX_HALFSIZE, 2.0 * BOX_HALFSIZE, 2.0 * BOX_HALFSIZE),
301+
fixed=True,
302+
)
303+
)
304+
camera_box_1 = scene.add_camera(
305+
pos=(0.0, -OBJ_OFFSET, CAMERA_DIST),
306+
lookat=(0.0, -OBJ_OFFSET, 0.0),
307+
GUI=show_viewer,
308+
)
309+
camera_box_2 = scene.add_camera(
310+
pos=np.array((CAMERA_DIST, CAMERA_DIST - OBJ_OFFSET, CAMERA_DIST)),
311+
lookat=(0.0, -OBJ_OFFSET, 0.0),
312+
GUI=show_viewer,
313+
)
314+
for camera in scene.visualizer.cameras:
315+
camera._near = 1.0
316+
scene.build()
317+
318+
if show_viewer:
319+
for camera in scene.visualizer.cameras:
320+
camera.render(rgb=True, depth=True)
321+
322+
point_cloud, mask = camera_box_1.render_pointcloud(world_frame=False)
323+
point_cloud = point_cloud[mask]
324+
assert_allclose(CAMERA_DIST - point_cloud[:, 2], BOX_HALFSIZE, atol=1e-4)
325+
assert np.all(-BOX_HALFSIZE <= point_cloud[:, :2].min(axis=0))
326+
assert np.all(point_cloud[:, :2].max(axis=0) <= BOX_HALFSIZE)
327+
328+
point_cloud, mask = camera_box_2.render_pointcloud(world_frame=False)
329+
point_cloud = point_cloud[mask]
330+
point_cloud = point_cloud @ gu.z_up_to_R(np.array((1.0, 1.0, 1.0)), np.array((0.0, 0.0, 1.0))).T
331+
point_cloud -= np.array((CAMERA_DIST, CAMERA_DIST, CAMERA_DIST))
332+
assert_allclose(np.linalg.norm(point_cloud, ord=float("inf"), axis=-1), BOX_HALFSIZE, atol=1e-4)
333+
334+
point_cloud, mask = camera_box_2.render_pointcloud(world_frame=True)
335+
point_cloud = point_cloud[mask]
336+
point_cloud += np.array((0.0, OBJ_OFFSET, 0.0))
337+
assert_allclose(np.linalg.norm(point_cloud, ord=float("inf"), axis=-1), BOX_HALFSIZE, atol=1e-4)
338+
339+
# It is not possible to get higher accuracy because of tesselation
340+
point_cloud, mask = camera_sphere.render_pointcloud(world_frame=False)
341+
point_cloud = point_cloud[mask]
342+
assert_allclose(np.linalg.norm((0.0, 0.0, CAMERA_DIST) - point_cloud, axis=-1), SPHERE_RADIUS, atol=1e-2)
343+
344+
274345
@pytest.mark.required
275346
def test_batched_mounted_camera_rendering(show_viewer, tol):
276347
scene = gs.Scene(

0 commit comments

Comments
 (0)