opensfm/mesh.py (140 lines of code) (raw):

#!/usr/bin/env python3 import itertools import logging from typing import Any, Tuple, List import numpy as np import scipy.spatial from opensfm import pygeometry, pymap, types logger = logging.getLogger(__name__) def triangle_mesh( shot_id: str, r: types.Reconstruction, tracks_manager: pymap.TracksManager ): """ Create triangle meshes in a list """ if shot_id not in r.shots or shot_id not in tracks_manager.get_shot_ids(): return [], [] shot = r.shots[shot_id] if shot.camera.projection_type in [ "perspective", "brown", "radial", "simple_radial", ]: return triangle_mesh_perspective(shot_id, r, tracks_manager) elif shot.camera.projection_type in [ "fisheye", "fisheye_opencv", "fisheye62", "fisheye624", "dual", ]: return triangle_mesh_fisheye(shot_id, r, tracks_manager) elif pygeometry.Camera.is_panorama(shot.camera.projection_type): return triangle_mesh_spherical(shot_id, r, tracks_manager) else: raise NotImplementedError( f"triangle_mesh not implemented for projection type {shot.camera.projection_type}" ) def triangle_mesh_perspective( shot_id: str, r: types.Reconstruction, tracks_manager: pymap.TracksManager ) -> Tuple[List[Any], List[Any]]: shot = r.shots[shot_id] cam = shot.camera dx = float(cam.width) / 2 / max(cam.width, cam.height) dy = float(cam.height) / 2 / max(cam.width, cam.height) pixels = [[-dx, -dy], [-dx, dy], [dx, dy], [dx, -dy]] vertices = [None for i in range(4)] for track_id in tracks_manager.get_shot_observations(shot_id): if track_id in r.points: point = r.points[track_id] pixel = shot.project(point.coordinates) nonans = not np.isnan(pixel).any() if nonans and -dx <= pixel[0] <= dx and -dy <= pixel[1] <= dy: vertices.append(point.coordinates) pixels.append(pixel.tolist()) try: tri = scipy.spatial.Delaunay(pixels) except Exception as e: logger.error("Delaunay triangulation failed for input: {}".format(repr(pixels))) raise e sums = [0.0, 0.0, 0.0, 0.0] depths = [0.0, 0.0, 0.0, 0.0] for t in tri.simplices: for i in range(4): if i in t: for j in t: if j >= 4: depths[i] += shot.pose.transform(vertices[j])[2] sums[i] += 1 for i in range(4): if sums[i] > 0: d = depths[i] / sums[i] else: d = 50.0 vertices[i] = back_project_no_distortion(shot, pixels[i], d).tolist() faces = tri.simplices.tolist() return vertices, faces def back_project_no_distortion( shot: pymap.Shot, pixel: List[float], depth: float ) -> np.ndarray: """ Back-project a pixel of a perspective camera ignoring its radial distortion """ K = shot.camera.get_K() K1 = np.linalg.inv(K) p = np.dot(K1, [pixel[0], pixel[1], 1]) p *= depth / p[2] return shot.pose.transform_inverse(p) def triangle_mesh_fisheye( shot_id: str, r: types.Reconstruction, tracks_manager: pymap.TracksManager ) -> Tuple[List[Any], List[Any]]: shot = r.shots[shot_id] bearings = [] vertices = [] # Add boundary vertices num_circle_points = 20 for i in range(num_circle_points): a = 2 * np.pi * float(i) / num_circle_points point = 30 * np.array([np.cos(a), np.sin(a), 0]) bearing = point / np.linalg.norm(point) point = shot.pose.transform_inverse(point) vertices.append(point.tolist()) bearings.append(bearing) # Add a single vertex in front of the camera point = 30 * np.array([0, 0, 1]) bearing = 0.3 * point / np.linalg.norm(point) point = shot.pose.transform_inverse(point) vertices.append(point.tolist()) bearings.append(bearing) # Add reconstructed points for track_id in tracks_manager.get_shot_observations(shot_id): if track_id in r.points: point = r.points[track_id].coordinates direction = shot.pose.transform(point) pixel = direction / np.linalg.norm(direction) if not np.isnan(pixel).any(): vertices.append(point) bearings.append(pixel.tolist()) # Triangulate tri = scipy.spatial.ConvexHull(bearings) faces = tri.simplices.tolist() # Remove faces having only boundary vertices def good_face(face): return ( face[0] >= num_circle_points or face[1] >= num_circle_points or face[2] >= num_circle_points ) faces = list(filter(good_face, faces)) return vertices, faces def triangle_mesh_spherical( shot_id: str, r: types.Reconstruction, tracks_manager: pymap.TracksManager ) -> Tuple[List[Any], List[Any]]: shot = r.shots[shot_id] bearings = [] vertices = [] # Add vertices to ensure that the camera is inside the convex hull # of the points for point in itertools.product([-1, 1], repeat=3): # vertices of a cube bearing = 0.3 * np.array(point) / np.linalg.norm(point) bearings.append(bearing) point = shot.pose.transform_inverse(bearing) vertices.append(point.tolist()) for track_id in tracks_manager.get_shot_observations(shot_id): if track_id in r.points: point = r.points[track_id].coordinates direction = shot.pose.transform(point) pixel = direction / np.linalg.norm(direction) if not np.isnan(pixel).any(): vertices.append(point) bearings.append(pixel.tolist()) tri = scipy.spatial.ConvexHull(bearings) faces = tri.simplices.tolist() return vertices, faces