From 0b3fa10abfe7e3399c8d0898bc79f637d98df14a Mon Sep 17 00:00:00 2001 From: Brent Yi Date: Thu, 4 May 2023 14:29:44 -0700 Subject: [PATCH] Sync docs --- docs/source/examples/11_colmap_visualizer.rst | 161 +++++++++++------- 1 file changed, 96 insertions(+), 65 deletions(-) diff --git a/docs/source/examples/11_colmap_visualizer.rst b/docs/source/examples/11_colmap_visualizer.rst index e218af251..d4f717725 100644 --- a/docs/source/examples/11_colmap_visualizer.rst +++ b/docs/source/examples/11_colmap_visualizer.rst @@ -35,18 +35,13 @@ Visualize COLMAP sparse reconstruction outputs. To get demo data, see ``./assets colmap_path: Path = Path(__file__).parent / "assets/colmap_garden/sparse/0", images_path: Path = Path(__file__).parent / "assets/colmap_garden/images_8", downsample_factor: int = 2, - max_points: Optional[int] = 100000, - max_frames: Optional[int] = 100, - point_size: float = 0.01, ) -> None: """Visualize COLMAP sparse reconstruction outputs. Args: colmap_path: Path to the COLMAP reconstruction directory. + images_path: Path to the COLMAP images directory. downsample_factor: Downsample factor for the images. - max_points: Maximum number of points to visualize. - max_frames: Maximum number of frames to visualize. - point_size: Size of the points. """ server = viser.ViserServer() @@ -54,72 +49,108 @@ Visualize COLMAP sparse reconstruction outputs. To get demo data, see ``./assets cameras = read_cameras_binary(colmap_path / "cameras.bin") images = read_images_binary(colmap_path / "images.bin") points3d = read_points3d_binary(colmap_path / "points3D.bin") + gui_reset_up = server.add_gui_button("Reset up direction") - # Set a world rotation to make the scene upright. - server.add_frame( - "/colmap", - wxyz=tf.SO3.exp(onp.array([-onp.pi / 2.0, 0.0, 0.0])).wxyz, - position=(0, 0, 0), - show_axes=False, - ) + @gui_reset_up.on_click + def _(_) -> None: + for client in server.get_clients().values(): + client.camera.up_direction = tf.SO3(client.camera.wxyz) @ onp.array( + [0.0, -1.0, 0.0] + ) - # Set the point cloud. - points = onp.array([points3d[p_id].xyz for p_id in points3d]) - colors = onp.array([points3d[p_id].rgb for p_id in points3d]) - if max_points is not None: - onp.random.shuffle(points) - onp.random.shuffle(colors) - points = points[:max_points] - colors = colors[:max_points] - server.add_point_cloud( - name="/colmap/pcd", points=points, colors=colors, point_size=point_size + gui_points = server.add_gui_slider( + "Max points", min=10, max=len(points3d), step=1, initial_value=50_000 ) - - # Interpret the images and cameras. - img_ids = [im.id for im in images.values()] - if max_frames is not None: - onp.random.shuffle(img_ids) - img_ids = sorted(img_ids[:max_frames]) - - for img_id in tqdm(img_ids): - img = images[img_id] - cam = cameras[img.camera_id] - - # Skip images that don't exist. - image_filename = images_path / img.name - if not image_filename.exists(): - continue - - T_world_camera = tf.SE3.from_rotation_and_translation( - tf.SO3(img.qvec), img.tvec - ).inverse() - server.add_frame(f"/colmap/frames/t{img_id}", show_axes=False) - server.add_frame( - f"/colmap/frames/t{img_id}/camera", - wxyz=T_world_camera.rotation().wxyz, - position=T_world_camera.translation(), - axes_length=0.1, - axes_radius=0.005, + gui_frames = server.add_gui_slider( + "Max frames", min=10, max=len(images), step=1, initial_value=100 + ) + gui_point_size = server.add_gui_number("Point size", initial_value=0.05) + + def visualize_colmap() -> None: + """Send all COLMAP elements to viser for visualization. This could be optimized + a ton!""" + # Set the point cloud. + points = onp.array([points3d[p_id].xyz for p_id in points3d]) + colors = onp.array([points3d[p_id].rgb for p_id in points3d]) + points_selection = onp.random.choice( + points.shape[0], gui_points.value, replace=False ) - - # For pinhole cameras, cam.params will be (fx, fy, cx, cy). - if cam.model != "PINHOLE": - print(f"Expected pinhole camera, but got {cam.model}") - - H, W = cam.height, cam.width - fy = cam.params[1] - image = iio.imread(image_filename) - image = image[::downsample_factor, ::downsample_factor] - server.add_camera_frustum( - f"/colmap/frames/t{img_id}/camera/frustum", - fov=2 * onp.arctan2(H / 2, fy), - aspect=W / H, - scale=0.15, - image=image, + points = points[points_selection] + colors = colors[points_selection] + + server.add_point_cloud( + name="/colmap/pcd", + points=points, + colors=colors, + point_size=gui_point_size.value, ) + # Interpret the images and cameras. + img_ids = [im.id for im in images.values()] + onp.random.shuffle(img_ids) + img_ids = sorted(img_ids[: gui_frames.value]) + + for img_id in tqdm(img_ids): + img = images[img_id] + cam = cameras[img.camera_id] + + # Skip images that don't exist. + image_filename = images_path / img.name + if not image_filename.exists(): + continue + + T_world_camera = tf.SE3.from_rotation_and_translation( + tf.SO3(img.qvec), img.tvec + ).inverse() + server.add_frame( + f"/colmap/frame_{img_id}", + wxyz=T_world_camera.rotation().wxyz, + position=T_world_camera.translation(), + axes_length=0.1, + axes_radius=0.005, + ) + + # For pinhole cameras, cam.params will be (fx, fy, cx, cy). + if cam.model != "PINHOLE": + print(f"Expected pinhole camera, but got {cam.model}") + + H, W = cam.height, cam.width + fy = cam.params[1] + image = iio.imread(image_filename) + image = image[::downsample_factor, ::downsample_factor] + server.add_camera_frustum( + f"/colmap/frame_{img_id}/frustum", + fov=2 * onp.arctan2(H / 2, fy), + aspect=W / H, + scale=0.15, + image=image, + ) + + need_update = True + + @gui_points.on_update + def _(_) -> None: + nonlocal need_update + need_update = True + + @gui_frames.on_update + def _(_) -> None: + nonlocal need_update + need_update = True + + @gui_point_size.on_update + def _(_) -> None: + nonlocal need_update + need_update = True + while True: - time.sleep(10.0) + if need_update: + need_update = False + + server.reset_scene() + visualize_colmap() + + time.sleep(1e-3) if __name__ == "__main__":