Skip to content

Commit 3616639

Browse files
paulinusfacebook-github-bot
authored andcommitted
Match undistorted image names with shot ids (mapillary#706)
Summary: Pull Request resolved: mapillary#706 Since we do not know on which format the input images are, we were always adding `.jpg` to the undistorted image file name before saving them. This caused a miss-match between the undistorted images' file names and the shot IDs in the undistorted reconstruction. We fix that by renaming the shot IDs to match the image filename. Additionally, we do not append the file format to the file name if the filename already ends with that extension. This breaks file-level backwards compatibility. We also add the `undistorted_shot_ids.json` file to the dataset that maps original shot IDs to the list of corresponding undistorted shot IDs. Remember that a single shot can have multiple undistorted shots. Reviewed By: YanNoun Differential Revision: D26443052 fbshipit-source-id: 4042c93c434ef120cd2c3a13591087ee715a7640
1 parent 4946478 commit 3616639

File tree

6 files changed

+64
-24
lines changed

6 files changed

+64
-24
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
### Breaking
77
- Main datastructures moved to C++ with Python bindings
88
- Drop Python 2 support. OpenSfM 0.5.x is the latest to support Python2.
9+
- Undistorted image file names only append the image format if it does not match the distorted image source
10+
- Undistorted shot ids now match the undistorted image file names and may not match the source shot ids
11+
12+
### Added
13+
- The file `undistorted/undistorted_shot_ids.json` stores a map from the original shot ids to their corresponding list of undistorted shot ids.
914

1015

1116
## 0.5.1

bin/import_colmap.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
def compute_and_save_undistorted_reconstruction(
4949
reconstruction, tracks_manager, data, udata
5050
):
51+
image_format = data.config["undistorted_image_format"]
5152
urec = types.Reconstruction()
5253
utracks_manager = pysfm.TracksManager()
5354
undistorted_shots = []
@@ -61,7 +62,7 @@ def compute_and_save_undistorted_reconstruction(
6162
else:
6263
raise ValueError
6364
urec.add_camera(ucamera)
64-
ushot = osfm_u.get_shot_with_different_camera(urec, shot, ucamera)
65+
ushot = osfm_u.get_shot_with_different_camera(urec, shot, ucamera, image_format)
6566
if tracks_manager:
6667
osfm_u.add_subshot_tracks(tracks_manager, utracks_manager, shot, ushot)
6768
undistorted_shots.append(ushot)

opensfm/actions/export_openmvs.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ def run_dataset(data, image_list):
2121
export_only[image.strip()] = True
2222

2323
if reconstructions:
24-
export(reconstructions[0], tracks_manager, udata, data, export_only)
24+
export(reconstructions[0], tracks_manager, udata, export_only)
2525

2626

27-
def export(reconstruction, tracks_manager, udata, data, export_only):
27+
def export(reconstruction, tracks_manager, udata, export_only):
2828
exporter = pydense.OpenMVSExporter()
2929
for camera in reconstruction.cameras.values():
3030
if camera.projection_type == "perspective":

opensfm/actions/export_ply.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,17 @@ def run_dataset(data, no_cameras, no_points, depthmaps, point_num_views):
2626
if reconstructions:
2727
data.save_ply(reconstructions[0], tracks_manager, None, no_cameras, no_points, point_num_views)
2828

29-
if depthmaps and reconstructions:
29+
if depthmaps:
3030
udata = dataset.UndistortedDataSet(data)
31-
for id, shot in reconstructions[0].shots.items():
32-
rgb = udata.load_undistorted_image(id)
31+
urec = udata.load_undistorted_reconstruction()[0]
32+
for shot in urec.shots.values():
33+
rgb = udata.load_undistorted_image(shot.id)
3334
for t in ("clean", "raw"):
34-
path_depth = udata.depthmap_file(id, t + ".npz")
35+
path_depth = udata.depthmap_file(shot.id, t + ".npz")
3536
if not os.path.exists(path_depth):
3637
continue
3738
depth = np.load(path_depth)["depth"]
3839
rgb = scale_down_image(rgb, depth.shape[1], depth.shape[0])
3940
ply = depthmap_to_ply(shot, depth, rgb)
40-
with io.open_wt(udata.depthmap_file(id, t + ".ply")) as fout:
41+
with io.open_wt(udata.depthmap_file(shot.id, t + ".ply")) as fout:
4142
fout.write(ply)

opensfm/dataset.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -759,14 +759,23 @@ def __init__(self, base_dataset, undistorted_data_path=None):
759759
else:
760760
self.data_path = os.path.join(self.base.data_path, "undistorted")
761761

762+
def load_undistorted_shot_ids(self):
763+
filename = os.path.join(self.data_path, "undistorted_shot_ids.json")
764+
with io.open_rt(filename) as fin:
765+
return io.json_load(fin)
766+
767+
def save_undistorted_shot_ids(self, ushot_dict):
768+
filename = os.path.join(self.data_path, "undistorted_shot_ids.json")
769+
io.mkdir_p(self.data_path)
770+
with io.open_wt(filename) as fout:
771+
io.json_dump(ushot_dict, fout, minify=False)
772+
762773
def _undistorted_image_path(self):
763774
return os.path.join(self.data_path, "images")
764775

765776
def _undistorted_image_file(self, image):
766777
"""Path of undistorted version of an image."""
767-
image_format = self.config["undistorted_image_format"]
768-
filename = image + "." + image_format
769-
return os.path.join(self._undistorted_image_path(), filename)
778+
return os.path.join(self._undistorted_image_path(), image)
770779

771780
def load_undistorted_image(self, image):
772781
"""Load undistorted image pixels as a numpy array."""

opensfm/undistort.py

+37-13
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616

1717
def undistort_reconstruction(tracks_manager, reconstruction, data, udata):
18+
image_format = data.config["undistorted_image_format"]
1819
urec = types.Reconstruction()
1920
urec.points = reconstruction.points
2021
utracks_manager = pysfm.TracksManager()
@@ -24,18 +25,20 @@ def undistort_reconstruction(tracks_manager, reconstruction, data, udata):
2425
if shot.camera.projection_type == "perspective":
2526
camera = perspective_camera_from_perspective(shot.camera)
2627
urec.add_camera(camera)
27-
subshots = [get_shot_with_different_camera(urec, shot, camera)]
28+
subshots = [get_shot_with_different_camera(urec, shot, camera, image_format)]
2829
elif shot.camera.projection_type == "brown":
2930
camera = perspective_camera_from_brown(shot.camera)
3031
urec.add_camera(camera)
31-
subshots = [get_shot_with_different_camera(urec, shot, camera)]
32+
subshots = [get_shot_with_different_camera(urec, shot, camera, image_format)]
3233
elif shot.camera.projection_type in ["fisheye", "fisheye_opencv"]:
3334
camera = perspective_camera_from_fisheye(shot.camera)
3435
urec.add_camera(camera)
35-
subshots = [get_shot_with_different_camera(urec, shot, camera)]
36+
subshots = [get_shot_with_different_camera(urec, shot, camera, image_format)]
3637
elif pygeometry.Camera.is_panorama(shot.camera.projection_type):
3738
subshot_width = int(data.config["depthmap_resolution"])
38-
subshots = perspective_views_of_a_panorama(shot, subshot_width, urec)
39+
subshots = perspective_views_of_a_panorama(
40+
shot, subshot_width, urec, image_format
41+
)
3942

4043
for subshot in subshots:
4144
if tracks_manager:
@@ -46,6 +49,13 @@ def undistort_reconstruction(tracks_manager, reconstruction, data, udata):
4649
if tracks_manager:
4750
udata.save_undistorted_tracks_manager(utracks_manager)
4851

52+
udata.save_undistorted_shot_ids(
53+
{
54+
shot_id: [ushot.id for ushot in ushots]
55+
for shot_id, ushots in undistorted_shots.items()
56+
}
57+
)
58+
4959
return undistorted_shots
5060

5161

@@ -122,25 +132,26 @@ def undistort_image(shot, undistorted_shots, original, interpolation, max_size):
122132

123133
projection_type = shot.camera.projection_type
124134
if projection_type in ["perspective", "brown", "fisheye", "fisheye_opencv"]:
125-
new_camera = undistorted_shots[0].camera
135+
[undistorted_shot] = undistorted_shots
136+
new_camera = undistorted_shot.camera
126137
height, width = original.shape[:2]
127138
map1, map2 = pygeometry.compute_camera_mapping(
128139
shot.camera, new_camera, width, height
129140
)
130141
undistorted = cv2.remap(original, map1, map2, interpolation)
131-
return {shot.id: scale_image(undistorted, max_size)}
142+
return {undistorted_shot.id: scale_image(undistorted, max_size)}
132143
elif pygeometry.Camera.is_panorama(projection_type):
133144
subshot_width = undistorted_shots[0].camera.width
134145
width = 4 * subshot_width
135146
height = width // 2
136147
image = cv2.resize(original, (width, height), interpolation=interpolation)
137148
mint = cv2.INTER_LINEAR if interpolation == cv2.INTER_AREA else interpolation
138149
res = {}
139-
for subshot in undistorted_shots:
150+
for undistorted_shot in undistorted_shots:
140151
undistorted = render_perspective_view_of_a_panorama(
141-
image, shot, subshot, mint
152+
image, shot, undistorted_shot, mint
142153
)
143-
res[subshot.id] = scale_image(undistorted, max_size)
154+
res[undistorted_shot.id] = scale_image(undistorted, max_size)
144155
return res
145156
else:
146157
raise NotImplementedError(
@@ -161,8 +172,16 @@ def scale_image(image, max_size):
161172
return cv2.resize(image, (width, height), interpolation=cv2.INTER_NEAREST)
162173

163174

164-
def get_shot_with_different_camera(urec, shot, camera):
165-
new_shot = urec.create_shot(shot.id, shot.camera.id, shot.pose)
175+
def add_image_format_extension(shot_id, image_format):
176+
if shot_id.endswith(f".{image_format}"):
177+
return shot_id
178+
else:
179+
return f"{shot_id}.{image_format}"
180+
181+
182+
def get_shot_with_different_camera(urec, shot, camera, image_format):
183+
new_shot_id = add_image_format_extension(shot.id, image_format)
184+
new_shot = urec.create_shot(new_shot_id, shot.camera.id, shot.pose)
166185
new_shot.metadata = shot.metadata
167186
return new_shot
168187

@@ -206,6 +225,7 @@ def perspective_camera_from_fisheye_opencv(fisheye_opencv):
206225
camera.height = fisheye_opencv.height
207226
return camera
208227

228+
209229
def perspective_camera_from_fisheye62(fisheye62):
210230
"""Create a perspective camera from a fisheye extended."""
211231
camera = pygeometry.Camera.create_perspective(
@@ -216,7 +236,8 @@ def perspective_camera_from_fisheye62(fisheye62):
216236
camera.height = fisheye62.height
217237
return camera
218238

219-
def perspective_views_of_a_panorama(spherical_shot, width, reconstruction):
239+
240+
def perspective_views_of_a_panorama(spherical_shot, width, reconstruction, image_format):
220241
"""Create 6 perspective views of a panorama."""
221242
camera = pygeometry.Camera.create_perspective(0.5, 0.0, 0.0)
222243
camera.id = "perspective_panorama_camera"
@@ -240,9 +261,12 @@ def perspective_views_of_a_panorama(spherical_shot, width, reconstruction):
240261
pose = pygeometry.Pose()
241262
pose.set_rotation_matrix(R)
242263
pose.set_origin(o)
264+
shot_id = add_image_format_extension(
265+
f"{spherical_shot.id}_perspective_view_{name}", image_format
266+
)
243267
shots.append(
244268
reconstruction.create_shot(
245-
"{}_perspective_view_{}".format(spherical_shot.id, name),
269+
shot_id,
246270
camera.id,
247271
pose,
248272
)

0 commit comments

Comments
 (0)