Skip to content

Commit

Permalink
Add 3D viz and Sacre Coeur demo
Browse files Browse the repository at this point in the history
  • Loading branch information
sarlinpe committed Jan 4, 2022
1 parent d3c4272 commit 79ff732
Show file tree
Hide file tree
Showing 15 changed files with 8,790 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ __pycache__
.ipynb_checkpoints
outputs/
third_party/netvlad
datasets/*
!datasets/sacre_coeur/
4 changes: 0 additions & 4 deletions datasets/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +0,0 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
3 changes: 3 additions & 0 deletions datasets/sacre_coeur/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Sacre Coeur demo

We provide here a subset of images depicting the Sacre Coeur. These images were obtained from the [Image Matching Challenge 2021](https://www.cs.ubc.ca/research/image-matching-challenge/2021/data/) and were originally collected by the [Yahoo Flickr Creative Commons 100M (YFCC) dataset](https://multimediacommons.wordpress.com/yfcc100m-core-dataset/).
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8,614 changes: 8,614 additions & 0 deletions demo.ipynb

Large diffs are not rendered by default.

171 changes: 171 additions & 0 deletions hloc/utils/viz_3d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
"""
3D visualization based on plotly.
Works for a small number of points and cameras, might be slow otherwise.
1) Initialize a figure with `init_figure`
2) Add 3D points, camera frustums, or both as a pycolmap.Reconstruction
Written by Paul-Edouard Sarlin and Philipp Lindenberger.
"""

from typing import Optional
import numpy as np
import pycolmap
import plotly.graph_objects as go


def to_homogeneous(points):
pad = np.ones((points.shape[:-1]+(1,)), dtype=points.dtype)
return np.concatenate([points, pad], axis=-1)


def init_figure(height: int = 800) -> go.Figure:
"""Initialize a 3D figure."""
fig = go.Figure()
axes = dict(
visible=False,
showbackground=False,
showgrid=False,
showline=False,
showticklabels=True,
autorange=True,
)
fig.update_layout(
template="plotly_dark",
height=height,
scene_camera=dict(
eye=dict(x=0., y=-.1, z=-2),
up=dict(x=0, y=-1., z=0),
projection=dict(type="orthographic")),
scene=dict(
xaxis=axes,
yaxis=axes,
zaxis=axes,
aspectmode='data',
dragmode='orbit',
),
margin=dict(l=0, r=0, b=0, t=0, pad=0),
legend=dict(
orientation="h",
yanchor="top",
y=0.99,
xanchor="left",
x=0.1
),
)
return fig


def plot_points(
fig: go.Figure,
pts: np.ndarray,
color: str = 'rgba(255, 0, 0, 1)',
ps: int = 2,
colorscale: Optional[str] = None,
name: Optional[str] = None):
"""Plot a set of 3D points."""
x, y, z = pts.T
tr = go.Scatter3d(
x=x, y=y, z=z, mode='markers', name=name, legendgroup=name,
marker=dict(
size=ps, color=color, line_width=0.0, colorscale=colorscale))
fig.add_trace(tr)


def plot_camera(
fig: go.Figure,
R: np.ndarray,
t: np.ndarray,
K: np.ndarray,
color: str = 'rgb(0, 0, 255)',
name: Optional[str] = None,
legendgroup: Optional[str] = None,
size: float = 1.0):
"""Plot a camera frustum from pose and intrinsic matrix."""
W, H = K[0, 2]*2, K[1, 2]*2
corners = np.array([[0, 0], [W, 0], [W, H], [0, H], [0, 0]])
if size is not None:
image_extent = max(size * W / 1024.0, size * H / 1024.0)
world_extent = max(W, H) / (K[0, 0] + K[1, 1]) / 0.5
scale = 0.5 * image_extent / world_extent
else:
scale = 1.0
corners = to_homogeneous(corners) @ np.linalg.inv(K).T
corners = (corners / 2 * scale) @ R.T + t

x, y, z = corners.T
rect = go.Scatter3d(
x=x, y=y, z=z, line=dict(color=color), legendgroup=legendgroup,
name=name, marker=dict(size=0.0001), showlegend=False)
fig.add_trace(rect)

x, y, z = np.concatenate(([t], corners)).T
i = [0, 0, 0, 0]
j = [1, 2, 3, 4]
k = [2, 3, 4, 1]

pyramid = go.Mesh3d(
x=x, y=y, z=z, color=color, i=i, j=j, k=k,
legendgroup=legendgroup, name=name, showlegend=False)
fig.add_trace(pyramid)
triangles = np.vstack((i, j, k)).T
vertices = np.concatenate(([t], corners))
tri_points = np.array([
vertices[i] for i in triangles.reshape(-1)
])

x, y, z = tri_points.T
pyramid = go.Scatter3d(
x=x, y=y, z=z, mode='lines', legendgroup=legendgroup,
name=name, line=dict(color=color, width=1), showlegend=False)
fig.add_trace(pyramid)


def plot_camera_colmap(
fig: go.Figure,
image: pycolmap.Image,
camera: pycolmap.Camera,
name: Optional[str] = None,
**kwargs):
"""Plot a camera frustum from PyCOLMAP objects"""
plot_camera(
fig,
image.rotmat().T,
image.projection_center(),
camera.calibration_matrix(),
name=name or str(image.image_id),
**kwargs)


def plot_cameras(
fig: go.Figure,
reconstruction: pycolmap.Reconstruction,
**kwargs):
"""Plot a camera as a cone with camera frustum."""
for image_id, image in reconstruction.images.items():
plot_camera_colmap(
fig, image, reconstruction.cameras[image.camera_id], **kwargs)


def plot_reconstruction(
fig: go.Figure,
rec: pycolmap.Reconstruction,
max_reproj_error: float = 6.0,
color: str = 'rgb(0, 0, 255)',
name: Optional[str] = None,
min_track_length: int = 2,
points: bool = True,
cameras: bool = True,
cs: float = 1.0):
# Filter outliers
bbs = rec.compute_bounding_box(0.001, 0.999)
# Filter points, use original reproj error here
xyzs = [p3D.xyz for _, p3D in rec.points3D.items() if (
(p3D.xyz >= bbs[0]).all() and
(p3D.xyz <= bbs[1]).all() and
p3D.error <= max_reproj_error and
p3D.track.length() >= min_track_length)]
if points:
plot_points(fig, np.array(xyzs), color=color, ps=1, name=name)
if cameras:
plot_cameras(fig, rec, color=color, legendgroup=name, size=cs)

0 comments on commit 79ff732

Please sign in to comment.