opensfm/synthetic_data/synthetic_metrics.py (115 lines of code) (raw):
from typing import Tuple, List, Dict
import cv2
import numpy as np
import opensfm.transformations as tf
from opensfm import align, types, pymap, multiview
def points_errors(
reference: types.Reconstruction, candidate: types.Reconstruction
) -> np.ndarray:
common_points = set(reference.points.keys()).intersection(
set(candidate.points.keys())
)
return np.array(
[
reference.points[p].coordinates - candidate.points[p].coordinates
for p in common_points
]
)
def completeness_errors(
reference: types.Reconstruction, candidate: types.Reconstruction
) -> Tuple[float, float]:
return (
float(len(candidate.shots)) / float(len(reference.shots)),
float(len(candidate.points)) / float(len(reference.points)),
)
def gps_errors(candidate: types.Reconstruction) -> np.ndarray:
errors = []
for shot in candidate.shots.values():
bias = candidate.biases[shot.camera.id]
pose1 = bias.transform(shot.metadata.gps_position.value)
pose2 = shot.pose.get_origin()
errors.append(pose1 - pose2)
return np.array(errors)
def gcp_errors(
candidate: types.Reconstruction, gcps: Dict[str, pymap.GroundControlPoint]
) -> np.ndarray:
errors = []
for gcp in gcps.values():
if not gcp.lla:
continue
triangulated = multiview.triangulate_gcp(gcp, candidate.shots, 1.0, 0.1)
if triangulated is None:
continue
gcp_enu = candidate.reference.to_topocentric(*gcp.lla_vec)
errors.append(triangulated - gcp_enu)
return np.array(errors)
def position_errors(
reference: types.Reconstruction, candidate: types.Reconstruction
) -> np.ndarray:
common_shots = set(reference.shots.keys()).intersection(set(candidate.shots.keys()))
errors = []
for s in common_shots:
pose1 = reference.shots[s].pose.get_origin()
pose2 = candidate.shots[s].pose.get_origin()
errors.append(pose1 - pose2)
return np.array(errors)
def rotation_errors(
reference: types.Reconstruction, candidate: types.Reconstruction
) -> np.ndarray:
common_shots = set(reference.shots.keys()).intersection(set(candidate.shots.keys()))
errors = []
for s in common_shots:
pose1 = reference.shots[s].pose.get_rotation_matrix()
pose2 = candidate.shots[s].pose.get_rotation_matrix()
difference = np.transpose(pose1).dot(pose2)
rodrigues = cv2.Rodrigues(difference)[0].ravel()
angle = np.linalg.norm(rodrigues)
errors.append(angle)
return np.array(errors)
def find_alignment(
points0: List[np.ndarray], points1: List[np.ndarray]
) -> Tuple[float, np.ndarray, np.ndarray]:
"""Compute similarity transform between point sets.
Returns (s, A, b) such that ``points1 = s * A * points0 + b``
"""
v0, v1 = [], []
for p0, p1 in zip(points0, points1):
if p0 is not None and p1 is not None:
v0.append(p0)
v1.append(p1)
v0 = np.array(v0).T
v1 = np.array(v1).T
M = tf.affine_matrix_from_points(v0, v1, shear=False)
s = np.linalg.det(M[:3, :3]) ** (1.0 / 3.0)
A = M[:3, :3] / s
b = M[:3, 3]
return s, A, b
def aligned_to_reference(
reference: types.Reconstruction, reconstruction: types.Reconstruction
) -> types.Reconstruction:
"""Align a reconstruction to a reference."""
coords1, coords2 = [], []
for point1 in reconstruction.points.values():
point2 = reference.points.get(point1.id)
if point2 is not None:
coords1.append(point1.coordinates)
coords2.append(point2.coordinates)
if len(coords1) == 0 or len(coords2) == 0:
for shot1 in reconstruction.shots.values():
shot2 = reference.shots.get(shot1.id)
if shot2 is not None:
coords1.append(shot1.pose.get_origin())
coords2.append(shot2.pose.get_origin())
s, A, b = find_alignment(coords1, coords2)
aligned = _copy_reconstruction(reconstruction)
align.apply_similarity(aligned, s, A, b)
return aligned
def _copy_reconstruction(reconstruction: types.Reconstruction) -> types.Reconstruction:
copy = types.Reconstruction()
for camera in reconstruction.cameras.values():
copy.add_camera(camera)
for shot in reconstruction.shots.values():
copy.add_shot(shot)
for point in reconstruction.points.values():
copy.add_point(point)
return copy
def rmse(errors: np.ndarray) -> float:
return np.sqrt(np.mean(errors ** 2))
def mad(errors: np.ndarray) -> float:
return np.median(np.absolute(errors - np.median(errors)))