Skip to content

Commit

Permalink
Sync docs
Browse files Browse the repository at this point in the history
  • Loading branch information
brentyi committed May 4, 2023
1 parent c8bb281 commit 0b3fa10
Showing 1 changed file with 96 additions and 65 deletions.
161 changes: 96 additions & 65 deletions docs/source/examples/11_colmap_visualizer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,91 +35,122 @@ 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()
# Load the colmap info.
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__":
Expand Down

0 comments on commit 0b3fa10

Please sign in to comment.