Skip to content

Commit 90f8917

Browse files
Pau Gargallofacebook-github-bot
authored andcommitted
make reconstruction comparisons handle different geo references
Summary: When comparing reconstructions, we were assuming that they were in the same reference frame, but this in not the case even for the synthetically generated ones. We add a method to change the refenece frame of reconstruction and use it before comparing a reconstruction to another Differential Revision: D75475832 Privacy Context Container: L1137435 fbshipit-source-id: c41bb75a7778baee614314159d31ceb76624ed0a
1 parent b761d31 commit 90f8917

File tree

4 files changed

+81
-19
lines changed

4 files changed

+81
-19
lines changed

opensfm/synthetic_data/synthetic_metrics.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# pyre-unsafe
2+
import copy
23
from typing import Dict, List, Tuple
34

45
import cv2
56
import numpy as np
67
import opensfm.transformations as tf
7-
from opensfm import align, multiview, pymap, types
8+
from opensfm import align, geo, multiview, pymap, types
89

910

1011
def points_errors(
@@ -125,20 +126,34 @@ def aligned_to_reference(
125126
coords2.append(shot2.pose.get_origin())
126127

127128
s, A, b = find_alignment(coords1, coords2)
128-
aligned = _copy_reconstruction(reconstruction)
129+
aligned = copy.deepcopy(reconstruction)
129130
align.apply_similarity(aligned, s, A, b)
130131
return aligned
131132

132133

133-
def _copy_reconstruction(reconstruction: types.Reconstruction) -> types.Reconstruction:
134-
copy = types.Reconstruction()
135-
for camera in reconstruction.cameras.values():
136-
copy.add_camera(camera)
137-
for shot in reconstruction.shots.values():
138-
copy.add_shot(shot)
139-
for point in reconstruction.points.values():
140-
copy.add_point(point)
141-
return copy
134+
def change_geo_reference(
135+
reconstruction: types.Reconstruction,
136+
latitude: float,
137+
longitude: float,
138+
altitude: float,
139+
) -> types.Reconstruction:
140+
"""Change the geo reference of a reconstruction.
141+
142+
This assumes that the new lla is close enough that rotation differences
143+
between references can be ignored.
144+
"""
145+
t_old_new = reconstruction.reference.to_topocentric(latitude, longitude, altitude)
146+
147+
s = 1.0
148+
A = np.eye(3)
149+
b = -np.array(t_old_new)
150+
aligned = copy.deepcopy(reconstruction)
151+
aligned.reference = geo.TopocentricConverter(latitude, longitude, altitude)
152+
align.apply_similarity(aligned, s, A, b)
153+
for shot in aligned.shots.values():
154+
if shot.metadata.gps_position.has_value:
155+
shot.metadata.gps_position.value = shot.metadata.gps_position.value + b
156+
return aligned
142157

143158

144159
def rmse(errors: np.ndarray) -> float:

opensfm/synthetic_data/synthetic_scene.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import opensfm.synthetic_data.synthetic_generator as sg
99
import opensfm.synthetic_data.synthetic_metrics as sm
1010
from opensfm import geo, pygeometry, pymap, types
11+
from opensfm.reconstruction_helpers import exif_to_metadata
1112

1213

1314
def get_camera(
@@ -452,6 +453,11 @@ def __init__(
452453
causal_gps_noise=causal_gps_noise,
453454
)
454455

456+
for shot in self.reconstruction.shots.values():
457+
shot.metadata = exif_to_metadata(
458+
self.exifs[shot.id], False, self.reconstruction.reference
459+
)
460+
455461
if generate_projections:
456462
(self.features, self.tracks_manager, self.gcps) = sg.generate_track_data(
457463
reconstruction,
@@ -473,15 +479,18 @@ def compare(
473479
reconstruction: types.Reconstruction,
474480
) -> Dict[str, float]:
475481
"""Compare a reconstruction with reference groundtruth."""
482+
geo = reference.reference
483+
476484
completeness = sm.completeness_errors(reference, reconstruction)
477485

478-
absolute_position = sm.position_errors(reference, reconstruction)
479-
absolute_rotation = sm.rotation_errors(reference, reconstruction)
480-
absolute_points = sm.points_errors(reference, reconstruction)
481-
absolute_gps = sm.gps_errors(reconstruction)
482-
absolute_gcp = sm.gcp_errors(reconstruction, gcps)
486+
geo_referenced = sm.change_geo_reference(reconstruction, geo.lat, geo.lon, geo.alt)
487+
absolute_position = sm.position_errors(reference, geo_referenced)
488+
absolute_rotation = sm.rotation_errors(reference, geo_referenced)
489+
absolute_points = sm.points_errors(reference, geo_referenced)
490+
absolute_gps = sm.gps_errors(geo_referenced)
491+
absolute_gcp = sm.gcp_errors(geo_referenced, gcps)
483492

484-
aligned = sm.aligned_to_reference(reference, reconstruction)
493+
aligned = sm.aligned_to_reference(reference, geo_referenced)
485494
aligned_position = sm.position_errors(reference, aligned)
486495
aligned_rotation = sm.rotation_errors(reference, aligned)
487496
aligned_points = sm.points_errors(reference, aligned)

opensfm/test/test_stats.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def test_reconstruction_statistics_normal(
130130
)
131131

132132
assert reconstruction_statistics["components"] == 1
133-
assert not reconstruction_statistics["has_gps"]
133+
assert reconstruction_statistics["has_gps"]
134134
assert not reconstruction_statistics["has_gcp"]
135135
assert 4900 < reconstruction_statistics["initial_points_count"] < 5000
136136
assert reconstruction_statistics["initial_shots_count"] == 20
@@ -257,7 +257,9 @@ def test_gps_errors_normal(
257257
) -> None:
258258
reference = scene_synthetic.reconstruction
259259
gps_errors = stats.gps_errors([reference])
260-
assert gps_errors == {}
260+
assert set(gps_errors.keys()) == {"average_error", "error", "mean", "std"}
261+
# scene_synthetic generated GPS noise is 5 meters
262+
assert 3.0 < gps_errors["average_error"] < 7.0
261263

262264

263265
def test_gps_errors_null(
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# pyre-strict
2+
import numpy as np
3+
from opensfm.synthetic_data import synthetic_metrics, synthetic_scene
4+
5+
6+
def test_change_geo_reference(
7+
scene_synthetic: synthetic_scene.SyntheticInputData,
8+
) -> None:
9+
original = scene_synthetic.reconstruction
10+
lat = original.reference.lat + 0.001 # about 111 m
11+
lon = original.reference.lon + 0.002
12+
alt = original.reference.alt + 2.34
13+
14+
aligned = synthetic_metrics.change_geo_reference(original, lat, lon, alt)
15+
16+
for shot_id in original.shots:
17+
original_position = original.shots[shot_id].pose.get_origin()
18+
aligned_position = aligned.shots[shot_id].pose.get_origin()
19+
aligned_lla = aligned.reference.to_lla(*aligned_position)
20+
aligned_in_original = original.reference.to_topocentric(*aligned_lla)
21+
assert np.allclose(original_position, aligned_in_original, atol=0.01)
22+
23+
assert original.shots[shot_id].metadata.gps_position.has_value
24+
assert aligned.shots[shot_id].metadata.gps_position.has_value
25+
original_gps_prior = original.shots[shot_id].metadata.gps_position.value
26+
aligned_gps_prior = aligned.shots[shot_id].metadata.gps_position.value
27+
aligned_gps_lla = aligned.reference.to_lla(*aligned_gps_prior)
28+
aligned_gps_in_original = original.reference.to_topocentric(*aligned_gps_lla)
29+
assert np.allclose(original_gps_prior, aligned_gps_in_original, atol=0.01)
30+
31+
for point_id in original.points:
32+
original_position = original.points[point_id].coordinates
33+
aligned_position = aligned.points[point_id].coordinates
34+
aligned_lla = aligned.reference.to_lla(*aligned_position)
35+
aligned_in_original = original.reference.to_topocentric(*aligned_lla)
36+
assert np.allclose(original_position, aligned_in_original, atol=0.01)

0 commit comments

Comments
 (0)