def reconstruction_from_json(obj): """ Read a reconstruction from a json object """ reconstruction = types.Reconstruction() # Extract cameras for key, value in iteritems(obj['cameras']): camera = camera_from_json(key, value) reconstruction.add_camera(camera) # Extract shots for key, value in iteritems(obj['shots']): shot = shot_from_json(key, value, reconstruction.cameras) reconstruction.add_shot(shot) # Extract points if 'points' in obj: for key, value in iteritems(obj['points']): point = point_from_json(key, value) reconstruction.add_point(point) # Extract pano_shots if 'pano_shots' in obj: reconstruction.pano_shots = {} for key, value in iteritems(obj['pano_shots']): shot = shot_from_json(key, value, reconstruction.cameras) reconstruction.pano_shots[shot.id] = shot # Extract main and unit shots if 'main_shot' in obj: reconstruction.main_shot = obj['main_shot'] if 'unit_shot' in obj: reconstruction.unit_shot = obj['unit_shot'] return reconstruction
def compute_and_save_undistorted_reconstruction( reconstruction, tracks_manager, data, udata ): image_format = data.config["undistorted_image_format"] urec = types.Reconstruction() utracks_manager = pymap.TracksManager() undistorted_shots = [] for shot in reconstruction.shots.values(): if shot.camera.projection_type == "perspective": ucamera = osfm_u.perspective_camera_from_perspective(shot.camera) elif shot.camera.projection_type == "brown": ucamera = osfm_u.perspective_camera_from_brown(shot.camera) elif shot.camera.projection_type == "fisheye": ucamera = osfm_u.perspective_camera_from_fisheye(shot.camera) else: raise ValueError urec.add_camera(ucamera) ushot = osfm_u.get_shot_with_different_camera(urec, shot, ucamera, image_format) if tracks_manager: osfm_u.add_subshot_tracks(tracks_manager, utracks_manager, shot, ushot) undistorted_shots.append(ushot) image = data.load_image(shot.id, unchanged=True, anydepth=True) if image is not None: max_size = data.config["undistorted_image_max_size"] undistorted = osfm_u.undistort_image( shot, undistorted_shots, image, cv2.INTER_AREA, max_size ) for k, v in undistorted.items(): udata.save_undistorted_image(k, v) udata.save_undistorted_reconstruction([urec]) if tracks_manager: udata.save_undistorted_tracks_manager(utracks_manager) return urec
def undistort_reconstruction(self, graph, reconstruction, data): urec = types.Reconstruction() urec.points = reconstruction.points ugraph = nx.Graph() logger.debug('Undistorting the reconstruction') undistorted_shots = {} for shot in reconstruction.shots.values(): if shot.camera.projection_type == 'perspective': camera = perspective_camera_from_perspective(shot.camera) subshots = [get_shot_with_different_camera(shot, camera)] elif shot.camera.projection_type == 'brown': camera = perspective_camera_from_brown(shot.camera) subshots = [get_shot_with_different_camera(shot, camera)] elif shot.camera.projection_type == 'fisheye': camera = perspective_camera_from_fisheye(shot.camera) subshots = [get_shot_with_different_camera(shot, camera)] elif shot.camera.projection_type in ['equirectangular', 'spherical']: subshot_width = int(data.config['depthmap_resolution']) subshots = perspective_views_of_a_panorama(shot, subshot_width) for subshot in subshots: urec.add_camera(subshot.camera) urec.add_shot(subshot) add_subshot_tracks(graph, ugraph, shot, subshot) undistorted_shots[shot.id] = subshots data.save_undistorted_reconstruction([urec]) data.save_undistorted_tracks_graph(ugraph) arguments = [] for shot in reconstruction.shots.values(): arguments.append((shot, undistorted_shots[shot.id], data)) processes = data.config['processes'] parallel_map(undistort_image_and_masks, arguments, processes)
def resect_annotated_single_images(reconstruction, gcps, camera_models, data): """Resect images that do not belong to reconstruction but have enough GCPs annotated. Returns: A reconstruction with all the resected images. """ not_in_rec = set() for gcp in gcps: for obs in gcp.observations: im = obs.shot_id if im not in reconstruction.shots and im in data.images(): not_in_rec.add(im) resected = types.Reconstruction() resected.reference = reconstruction.reference for im in not_in_rec: exif = data.load_exif(im) camera = camera_models[exif["camera"]] resect_image(im, camera, gcps, reconstruction, data, resected) logger.info( f"Resected: {len(resected.shots)} shots and {len(resected.cameras)} cameras" ) return resected
def test_bundle_alignment_prior() -> None: """Test that cameras are aligned to have the Y axis pointing down.""" camera = pygeometry.Camera.create_perspective(1.0, 0.0, 0.0) camera.id = "camera1" r = types.Reconstruction() r.add_camera(camera) shot = r.create_shot("1", camera.id, pygeometry.Pose(np.random.rand(3), np.random.rand(3))) # pyre-fixme[8]: Attribute has type `ndarray`; used as `List[int]`. shot.metadata.gps_position.value = [0, 0, 0] shot.metadata.gps_accuracy.value = 1 camera_priors = {camera.id: camera} rig_priors = dict(r.rig_cameras.items()) gcp = [] myconfig = config.default_config() reconstruction.bundle(r, camera_priors, rig_priors, gcp, myconfig) shot = r.shots[shot.id] assert np.allclose(shot.pose.translation, np.zeros(3)) # up vector in camera coordinates is (0, -1, 0) assert np.allclose(shot.pose.transform([0, 0, 1]), [0, -1, 0], atol=1e-7)
def test_bundle_void_gps_ignored(): """Test that void gps values are ignored.""" camera = pygeometry.Camera.create_perspective(1.0, 0.0, 0.0) camera.id = "camera1" r = types.Reconstruction() r.add_camera(camera) shot = r.create_shot("1", camera.id, pygeometry.Pose(np.random.rand(3), np.random.rand(3))) camera_priors = {camera.id: camera} gcp = [] myconfig = config.default_config() # Missing position shot.metadata.gps_position.value = np.zeros(3) shot.metadata.gps_accuracy.value = 1 shot.metadata.gps_position.reset() shot.pose.set_origin(np.ones(3)) reconstruction.bundle(r, camera_priors, {}, gcp, myconfig) assert np.allclose(shot.pose.get_origin(), np.ones(3)) # Missing accuracy shot.metadata.gps_position.value = np.zeros(3) shot.metadata.gps_accuracy.value = 1 shot.metadata.gps_accuracy.reset() shot.pose.set_origin(np.ones(3)) reconstruction.bundle(r, camera_priors, {}, gcp, myconfig) assert np.allclose(shot.pose.get_origin(), np.ones(3)) # Valid gps position and accuracy shot.metadata.gps_position.value = np.zeros(3) shot.metadata.gps_accuracy.value = 1 shot.pose.set_origin(np.ones(3)) reconstruction.bundle(r, camera_priors, {}, gcp, myconfig) assert np.allclose(shot.pose.get_origin(), np.zeros(3))
def bootstrap_reconstruction(data, graph, im1, im2, p1, p2): """Start a reconstruction using two shots.""" logger.info("Starting reconstruction with {} and {}".format(im1, im2)) report = { 'image_pair': (im1, im2), 'common_tracks': len(p1), } cameras = data.load_camera_models() camera1 = cameras[data.load_exif(im1)['camera']] camera2 = cameras[data.load_exif(im2)['camera']] threshold = data.config['five_point_algo_threshold'] min_inliers = data.config['five_point_algo_min_inliers'] R, t, inliers, report['two_view_reconstruction'] = \ two_view_reconstruction_general(p1, p2, camera1, camera2, threshold) logger.info("Two-view reconstruction inliers: {} / {}".format( len(inliers), len(p1))) if len(inliers) <= 5: report['decision'] = "Could not find initial motion" logger.info(report['decision']) return None, None, report reconstruction = types.Reconstruction() reconstruction.reference = data.load_reference() reconstruction.cameras = cameras shot1 = types.Shot() shot1.id = im1 shot1.camera = camera1 shot1.pose = types.Pose() shot1.metadata = get_image_metadata(data, im1) reconstruction.add_shot(shot1) shot2 = types.Shot() shot2.id = im2 shot2.camera = camera2 shot2.pose = types.Pose(R, t) shot2.metadata = get_image_metadata(data, im2) reconstruction.add_shot(shot2) graph_inliers = nx.Graph() triangulate_shot_features(graph, graph_inliers, reconstruction, im1, data.config) logger.info("Triangulated: {}".format(len(reconstruction.points))) report['triangulated_points'] = len(reconstruction.points) if len(reconstruction.points) < min_inliers: report['decision'] = "Initial motion did not generate enough points" logger.info(report['decision']) return None, None, report bundle_single_view(graph_inliers, reconstruction, im2, data.config) retriangulate(graph, graph_inliers, reconstruction, data.config) bundle_single_view(graph_inliers, reconstruction, im2, data.config) report['decision'] = 'Success' report['memory_usage'] = current_memory_usage() return reconstruction, graph_inliers, report
def import_bundler(data_path, bundle_file, list_file, track_file, reconstruction_file=None): """ Reconstruction and tracks graph from Bundler's output """ # Init OpenSfM working folder. mkdir_p(data_path) # Copy image list. list_dir = os.path.dirname(list_file) with open_rt(list_file) as fin: lines = fin.read().splitlines() ordered_shots = [] image_list = [] for line in lines: image_path = os.path.join(list_dir, line.split()[0]) rel_to_data = os.path.relpath(image_path, data_path) image_list.append(rel_to_data) ordered_shots.append(os.path.basename(image_path)) with open_wt(os.path.join(data_path, "image_list.txt")) as fout: fout.write("\n".join(image_list) + "\n") # Check for bundle_file if not bundle_file or not os.path.isfile(bundle_file): return None with open_rt(bundle_file) as fin: lines = fin.readlines() offset = 1 if "#" in lines[0] else 0 # header num_shot, num_point = map(int, lines[offset].split(" ")) offset += 1 # initialization reconstruction = types.Reconstruction() # cameras for i in range(num_shot): # Creating a model for each shot. shot_key = ordered_shots[i] focal, k1, k2 = map(float, lines[offset].rstrip("\n").split(" ")) if focal > 0: im = imread(os.path.join(data_path, image_list[i])) height, width = im.shape[0:2] camera = pygeometry.Camera.create_perspective( focal / max(width, height), k1, k2) camera.id = "camera_" + str(i) camera.width = width camera.height = height reconstruction.add_camera(camera) # Shots rline = [] for k in range(3): rline += lines[offset + 1 + k].rstrip("\n").split(" ") R = " ".join(rline) t = lines[offset + 4].rstrip("\n").split(" ") R = np.array(list(map(float, R.split()))).reshape(3, 3) t = np.array(list(map(float, t))) R[1], R[2] = -R[1], -R[2] # Reverse y and z t[1], t[2] = -t[1], -t[2] pose = pygeometry.Pose() pose.set_rotation_matrix(R) pose.translation = t reconstruction.create_shot(shot_key, camera.id, pose) else: logger.warning("ignoring failed image {}".format(shot_key)) offset += 5 # tracks track_lines = [] for i in range(num_point): coordinates = lines[offset].rstrip("\n").split(" ") color = lines[offset + 1].rstrip("\n").split(" ") point = reconstruction.create_point(i, list(map(float, coordinates))) point.color = list(map(int, color)) view_line = lines[offset + 2].rstrip("\n").split(" ") num_view, view_list = int(view_line[0]), view_line[1:] for k in range(num_view): shot_key = ordered_shots[int(view_list[4 * k])] if shot_key in reconstruction.shots: camera = reconstruction.shots[shot_key].camera scale = max(camera.width, camera.height) v = "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}".format( shot_key, i, view_list[4 * k + 1], float(view_list[4 * k + 2]) / scale, -float(view_list[4 * k + 3]) / scale, point.color[0], point.color[1], point.color[2], ) track_lines.append(v) offset += 3 # save track file with open_wt(track_file) as fout: fout.writelines("\n".join(track_lines)) # save reconstruction if reconstruction_file is not None: with open_wt(reconstruction_file) as fout: obj = reconstructions_to_json([reconstruction]) json_dump(obj, fout) return reconstruction
def bootstrap_reconstruction( data: DataSetBase, tracks_manager: pymap.TracksManager, im1: str, im2: str, p1: np.ndarray, p2: np.ndarray, ) -> Tuple[Optional[types.Reconstruction], Dict[str, Any]]: """Start a reconstruction using two shots.""" logger.info("Starting reconstruction with {} and {}".format(im1, im2)) report: Dict[str, Any] = { "image_pair": (im1, im2), "common_tracks": len(p1), } camera_priors = data.load_camera_models() camera1 = camera_priors[data.load_exif(im1)["camera"]] camera2 = camera_priors[data.load_exif(im2)["camera"]] threshold = data.config["five_point_algo_threshold"] min_inliers = data.config["five_point_algo_min_inliers"] iterations = data.config["five_point_refine_rec_iterations"] R, t, inliers, report[ "two_view_reconstruction"] = two_view_reconstruction_general( p1, p2, camera1, camera2, threshold, iterations) logger.info("Two-view reconstruction inliers: {} / {}".format( len(inliers), len(p1))) if len(inliers) <= 5: report["decision"] = "Could not find initial motion" logger.info(report["decision"]) return None, report rig_camera_priors = data.load_rig_cameras() rig_assignments = data.load_rig_assignments_per_image() reconstruction = types.Reconstruction() reconstruction.reference = data.load_reference() reconstruction.cameras = camera_priors reconstruction.rig_cameras = rig_camera_priors new_shots = add_shot(data, reconstruction, rig_assignments, im1, pygeometry.Pose()) if im2 not in new_shots: new_shots |= add_shot(data, reconstruction, rig_assignments, im2, pygeometry.Pose(R, t)) align_reconstruction(reconstruction, None, data.config) triangulate_shot_features(tracks_manager, reconstruction, new_shots, data.config) logger.info("Triangulated: {}".format(len(reconstruction.points))) report["triangulated_points"] = len(reconstruction.points) if len(reconstruction.points) < min_inliers: report["decision"] = "Initial motion did not generate enough points" logger.info(report["decision"]) return None, report to_adjust = {s for s in new_shots if s != im1} bundle_shot_poses(reconstruction, to_adjust, camera_priors, rig_camera_priors, data.config) retriangulate(tracks_manager, reconstruction, data.config) if len(reconstruction.points) < min_inliers: report[ "decision"] = "Re-triangulation after initial motion did not generate enough points" logger.info(report["decision"]) return None, report bundle_shot_poses(reconstruction, to_adjust, camera_priors, rig_camera_priors, data.config) report["decision"] = "Success" report["memory_usage"] = current_memory_usage() return reconstruction, report
def _create_reconstruction( n_cameras=0, n_shots_cam=None, n_pano_shots_cam=None, n_points=0, dist_to_shots=False, dist_to_pano_shots=False, ): """Creates a reconstruction with n_cameras random cameras and shots, where n_shots_cam is a dictionary, containing the camera_id and the number of shots. Example: shot_cams = {"0": 50, "1": 30} _create_reconstruction(2, shot_cams) Will create a reconstruction with two cameras and 80 shots, 50 are associated with cam "0" and 30 with cam "1". n_points_in_shots is the number of points to create. If dist_to_shots, then observations are created and randomly distributed to all shots. We pick with the repeat option, thus if we have three shots the distribution could be something like: [1,2,2], [0,1,2]. We avoid things like [3,3,3] """ if n_shots_cam is None: n_shots_cam = {} if n_pano_shots_cam is None: n_pano_shots_cam = {} rec = types.Reconstruction() if n_cameras > 0: for i in range(n_cameras): focal, k1, k2 = np.random.rand(3) cam = pygeometry.Camera.create_perspective(focal, k1, k2) cam.id = str(i) rec.add_camera(cam) shot_id = 0 for cam_id, n_shots in n_shots_cam.items(): for _ in range(n_shots): rec.create_shot(str(shot_id), cam_id) shot_id += 1 shot_id = 0 for cam_id, n_shots in n_pano_shots_cam.items(): for _ in range(n_shots): rec.create_pano_shot(str(shot_id), cam_id) shot_id += 1 if n_points > 0: for i in range(n_points): rec.create_point(str(i), np.random.rand(3)) if dist_to_shots: n_shots = len(rec.shots) for pt in rec.points.values(): choice = set(np.random.choice(n_shots, n_shots)) if len(choice) > 1: for ch in choice: # create a new observation obs = pysfm.Observation(100, 200, 0.5, 255, 0, 0, int(pt.id)) shot = rec.shots[str(ch)] rec.add_observation(shot, pt, obs) # TODO: If required, we have to do the same for pano shots return rec
def null_scene() -> types.Reconstruction: reconstruction = types.Reconstruction() return reconstruction
def test_compute_relative_pose() -> None: # 4-cameras rig camera1 = pygeometry.Camera.create_spherical() camera1.id = "camera1" camera2 = pygeometry.Camera.create_spherical() camera2.id = "camera2" camera3 = pygeometry.Camera.create_spherical() camera3.id = "camera3" camera4 = pygeometry.Camera.create_spherical() camera4.id = "camera4" # a bit cumbersome that we need to have some reconstruction rec = types.Reconstruction() rec.add_camera(camera1) rec.add_camera(camera2) rec.add_camera(camera3) rec.add_camera(camera4) # First rig instance rec.create_shot( "shot1", "camera1", pygeometry.Pose(np.array([0, 0, 0]), np.array([-2, -2, 0]))) rec.create_shot( "shot2", "camera2", pygeometry.Pose(np.array([0, 0, 0]), np.array([-3, -3, 0]))) rec.create_shot( "shot3", "camera3", pygeometry.Pose(np.array([0, 0, 0]), np.array([-1, -3, 0]))) rec.create_shot( "shot4", "camera4", pygeometry.Pose(np.array([0, 0, 0]), np.array([-2, -4, 0]))) # Second rig instance (rotated by pi/2 around Z) pose_instance = pygeometry.Pose(np.array([0, 0, -1.5707963])) pose_instance.set_origin(np.array([-6, 0, 0])) rec.create_shot("shot5", "camera1", pose_instance) pose_instance.set_origin(np.array([-7, 1, 0])) rec.create_shot("shot6", "camera2", pose_instance) pose_instance.set_origin(np.array([-7, -1, 0])) rec.create_shot("shot7", "camera3", pose_instance) pose_instance.set_origin(np.array([-8, 0, 0])) rec.create_shot("shot8", "camera4", pose_instance) pose_instances = [ [ ( rec.shots["shot1"], "camera_id_1", ), ( rec.shots["shot2"], "camera_id_2", ), ( rec.shots["shot3"], "camera_id_3", ), ( rec.shots["shot4"], "camera_id_4", ), ], [ ( rec.shots["shot5"], "camera_id_1", ), ( rec.shots["shot6"], "camera_id_2", ), ( rec.shots["shot7"], "camera_id_3", ), ( rec.shots["shot8"], "camera_id_4", ), ], ] # Compute rig cameras poses rig_cameras = rig.compute_relative_pose(pose_instances) assert np.allclose([0, -1, 0], rig_cameras["camera_id_1"].pose.get_origin(), atol=1e-7) assert np.allclose([1, 0, 0], rig_cameras["camera_id_2"].pose.get_origin(), atol=1e-7) assert np.allclose([-1, 0, 0], rig_cameras["camera_id_3"].pose.get_origin(), atol=1e-7) assert np.allclose([0, 1, 0], rig_cameras["camera_id_4"].pose.get_origin(), atol=1e-7)
def undistort_reconstruction(self, graph, reconstruction, data, image_format = "jpg", image_scale = 1): urec = types.Reconstruction() urec.points = reconstruction.points logger.debug('Undistorting the reconstruction') undistorted_shots = {} for shot in reconstruction.shots.values(): if shot.camera.projection_type == 'perspective': urec.add_camera(shot.camera) urec.add_shot(shot) undistorted_shots[shot.id] = [shot] elif shot.camera.projection_type == 'brown': ushot = types.Shot() ushot.id = shot.id ushot.camera = perspective_camera_from_brown(shot.camera) ushot.pose = shot.pose ushot.metadata = shot.metadata urec.add_camera(ushot.camera) urec.add_shot(ushot) undistorted_shots[shot.id] = [ushot] elif shot.camera.projection_type == 'fisheye': ushot = types.Shot() ushot.id = shot.id ushot.camera = perspective_camera_from_fisheye(shot.camera) ushot.pose = shot.pose ushot.metadata = shot.metadata urec.add_camera(ushot.camera) urec.add_shot(ushot) undistorted_shots[shot.id] = [ushot] elif shot.camera.projection_type in ['equirectangular', 'spherical']: subshot_width = int(data.config['depthmap_resolution']) subshots = perspective_views_of_a_panorama(shot, subshot_width) for subshot in subshots: urec.add_camera(subshot.camera) urec.add_shot(subshot) add_subshot_tracks(graph, shot, subshot) undistorted_shots[shot.id] = subshots data.save_undistorted_reconstruction([urec]) arguments = [] for shot in reconstruction.shots.values(): arguments.append((shot, undistorted_shots[shot.id], data, 'load_image', 'save_undistorted_image', cv2.INTER_AREA, image_format, image_scale)) arguments.append((shot, undistorted_shots[shot.id], data, 'load_mask', 'save_undistorted_mask', cv2.INTER_NEAREST, image_format, image_scale)) arguments.append((shot, undistorted_shots[shot.id], data, 'load_segmentation', 'save_undistorted_segmentation', cv2.INTER_NEAREST, image_format, image_scale)) processes = data.config['processes'] parallel_map(undistort_image, arguments, processes)
def build_reconstruction(opensfm_path, log_file, dataset_path): if not opensfm_path in sys.path: sys.path.insert(1, opensfm_path) from opensfm import dataset, matching, reconstruction, types, io from opensfm.reconstruction import TrackTriangulator # from opensfm import learners # from opensfm import log global types Rs, ts, subsampled_images = parse_log_file(log_file) recon = types.Reconstruction() camera = build_camera() recon.add_camera(camera) offset = None pose0 = types.Pose() pose0_recon = types.Pose() for i, _ in enumerate(Rs): pose = types.Pose() pose.rotation = Rs[i] # pose.set_rotation_matrix(Rs[i]) # print pose.get_rotation_matrix() pose.set_rotation_matrix(pose.get_rotation_matrix().T) pose.set_origin(np.array(ts[i])) if False and i == 0: print subsampled_images[i] # pose0 = types.Pose() # pose0_recon = types.Pose() pose0.rotation = pose.rotation pose0.translation = pose.translation pose0_recon.rotation = [ 1.46327114203856, 0.6520934519442041, -0.7289951219890223 ] pose0_recon.translation = [ -151.62008675764042, 7.551077656334444, 32.03538718382186 ] if False: R_ = np.matrix(pose.get_rotation_matrix()) * np.matrix( pose0.get_rotation_matrix()).T * np.matrix( pose0_recon.get_rotation_matrix()) pose.set_rotation_matrix(R_) # Bad cases if subsampled_images[i] == '1476939075123622.jpg': print '-' * 100 print '{} : {}'.format(subsampled_images[i], pose.rotation) pose.rotation[2] = math.pi + pose.rotation[2] # Good cases if subsampled_images[i] == '1476939074934112.jpg': print '+' * 100 print '{} : {}'.format(subsampled_images[i], pose.rotation) # if offset is None: # offset = pose.get_origin() - pose0_recon.get_origin() # pose.set_origin(pose.get_origin() - offset) # pose.translation = pose.translation * 0.1 # pose.translation = np.array(ts[i]) # print pose.get_origin() # print pose.get_rotation_matrix() # print pose.get_rotation_matrix() # print pose.get_origin() # sys.exit(1) # t = pose.translation # t[0] = -t[0] # t[1] = -t[1] # t[2] = -t[2] # t[1],t[2] = t[2],t[1] # pose.translation = t # print pose.rotation # print pose.translation # print '#'*100 # sys.exit(1) # R = pose.get_rotation_matrix() # R[:,1], R[:,2] = R[:,2], R[:,1] # pose.set_rotation_matrix(R) # R = pose.get_rotation_matrix() # R[:,1], R[:,2] = R[:,2], R[:,1] # pose.set_rotation_matrix(R.T) # t = pose.translation # t = pose.get_rotation_matrix() * np.matrix(pose.translation.reshape((3,1))) # print pose.translation pose.translation = 20.0 * pose.translation # print pose.translation # sys.exit(1) shot = types.Shot() shot.camera = camera shot.pose = pose shot.id = subsampled_images[i] sm = types.ShotMetadata() sm.orientation = 1 sm.gps_position = [0.0, 0.0, 0.0] sm.gps_dop = 999999.0 # sm.capture_time = 0.0 shot.metadata = sm # add shot to reconstruction recon.add_shot(shot) data = dataset.DataSet(dataset_path) data.save_reconstruction([recon], 'reconstruction_gt.json')
def main(): parser = argparse.ArgumentParser( description='Convert COLMAP database to OpenSfM dataset') parser.add_argument('database', help='path to the database to be processed') parser.add_argument('images', help='path to the images') args = parser.parse_args() logger.info(f"Converting {args.database} to COLMAP format") p_db = Path(args.database) assert p_db.is_file() export_folder = p_db.parent / EXPORT_DIR_NAME export_folder.mkdir(exist_ok=True) images_path = export_folder / 'images' if not images_path.exists(): os.symlink(os.path.abspath(args.images), images_path, target_is_directory=True) # Copy the config if this is an colmap export of an opensfm export if p_db.parent.name == 'colmap_export' and not (export_folder/'config.yaml').exists(): os.symlink(p_db.parent.parent / 'config.yaml', export_folder / 'config.yaml') data = dataset.DataSet(export_folder) db = sqlite3.connect(p_db.as_posix()) camera_map, image_map = import_cameras_images(db, data) # Create image_list.txt with open(export_folder / 'image_list.txt', 'w') as f: for _, (filename, _) in image_map.items(): f.write('images/' + filename + '\n') data.load_image_list() keypoints = import_features(db, data, image_map, camera_map) import_matches(db, data, image_map) rec_cameras = p_db.parent / 'cameras.bin' rec_points = p_db.parent / 'points3D.bin' rec_images = p_db.parent / 'images.bin' if rec_cameras.exists() and rec_images.exists() and rec_points.exists(): reconstruction = types.Reconstruction() import_cameras_reconstruction(rec_cameras, reconstruction) import_points_reconstruction(rec_points, reconstruction) tracks_manager, _ = import_images_reconstruction(rec_images, keypoints, reconstruction) data.save_reconstruction([reconstruction]) data.save_tracks_manager(tracks_manager) # Save undistorted reconstruction as well udata = dataset.UndistortedDataSet(data, 'undistorted') urec = compute_and_save_undistorted_reconstruction(reconstruction, tracks_manager, data, udata) # Project colmap's fused pointcloud to save depths in opensfm format path_ply = p_db.parent / 'dense/fused.ply' if path_ply.is_file(): rec_cameras = p_db.parent / 'dense/sparse/cameras.bin' rec_images = p_db.parent / 'dense/sparse/images.bin' rec_points = p_db.parent / 'points3D.bin' reconstruction = types.Reconstruction() import_cameras_reconstruction(rec_cameras, reconstruction) import_points_reconstruction(rec_points, reconstruction) _, image_ix_to_shot_id = import_images_reconstruction(rec_images, keypoints, reconstruction) logger.info(f"Projecting {path_ply} to depth images") import_depthmaps_from_fused_pointcloud(udata, urec, image_ix_to_shot_id, path_ply) else: logger.info("Not importing dense reconstruction: Didn't find {}".format(path_ply)) else: logger.info("Didn't find some of the reconstruction files at {}".format(p_db.parent)) db.close()
def import_bundler(data_path, bundle_file, list_file, track_file, reconstruction_file=None): """ Reconstruction and tracks graph from Bundler's output """ # Init OpenSfM working folder. mkdir_p(data_path) # Copy image list. list_dir = os.path.dirname(list_file) with open(list_file, 'rb') as fin: lines = fin.read().splitlines() ordered_shots = [] image_list = [] for line in lines: image_path = os.path.join(list_dir, line.split()[0]) rel_to_data = os.path.relpath(image_path, data_path) image_list.append(rel_to_data) ordered_shots.append(os.path.basename(image_path)) with open(os.path.join(data_path, 'image_list.txt'), 'w') as fout: fout.write('\n'.join(image_list) + '\n') # Check for bundle_file if not bundle_file or not os.path.isfile(bundle_file): return None with open(bundle_file, 'rb') as fin: lines = fin.readlines() offset = 1 if '#' in lines[0] else 0 # header num_shot, num_point = map(int, lines[offset].split(' ')) offset += 1 # initialization reconstruction = types.Reconstruction() # cameras for i in xrange(num_shot): # Creating a model for each shot. shot_key = ordered_shots[i] focal, k1, k2 = map(float, lines[offset].rstrip('\n').split(' ')) if focal > 0: im = imread(os.path.join(data_path, image_list[i])) height, width = im.shape[0:2] camera = types.PerspectiveCamera() camera.id = 'camera_' + str(i) camera.width = width camera.height = height camera.focal = focal / max(width, height) camera.k1 = k1 camera.k2 = k2 reconstruction.add_camera(camera) # Shots rline = [] for k in xrange(3): rline += lines[offset + 1 + k].rstrip('\n').split(' ') R = ' '.join(rline) t = lines[offset + 4].rstrip('\n').split(' ') R = np.array(map(float, R.split())).reshape(3, 3) t = np.array(map(float, t)) R[1], R[2] = -R[1], -R[2] # Reverse y and z t[1], t[2] = -t[1], -t[2] shot = types.Shot() shot.id = shot_key shot.camera = camera shot.pose = types.Pose() shot.pose.set_rotation_matrix(R) shot.pose.translation = t reconstruction.add_shot(shot) else: print 'ignore failed image', shot_key offset += 5 # tracks track_lines = [] for i in xrange(num_point): coordinates = lines[offset].rstrip('\n').split(' ') color = lines[offset + 1].rstrip('\n').split(' ') point = types.Point() point.id = i point.coordinates = map(float, coordinates) point.color = map(int, color) reconstruction.add_point(point) view_line = lines[offset + 2].rstrip('\n').split(' ') num_view, view_list = int(view_line[0]), view_line[1:] for k in xrange(num_view): shot_key = ordered_shots[int(view_list[4 * k])] if shot_key in reconstruction.shots: camera = reconstruction.shots[shot_key].camera scale = max(camera.width, camera.height) v = '{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}'.format( shot_key, i, view_list[4 * k + 1], float(view_list[4 * k + 2]) / scale, -float(view_list[4 * k + 3]) / scale, point.color[0], point.color[1], point.color[2] ) track_lines.append(v) offset += 3 # save track file with open(track_file, 'wb') as fout: fout.writelines('\n'.join(track_lines)) # save reconstruction if reconstruction_file is not None: with open(reconstruction_file, 'wb') as fout: obj = reconstructions_to_json([reconstruction]) json_dump(obj, fout) return reconstruction
def bootstrap_reconstruction( problem, data, graph, im1, im2, hint_forward=False): """ Build 3D reconstruction based on two images `im1` and `im2`. See `custom_two_view_reconstruction` for the meaning of `hint_forward`. Returns the `Reconstruction` object, or None if reconstruction failed. """ print "----------------" print "bootstrap_reconstruction({}, {}, hint_forward={})".format( im1, im2, hint_forward) camera1 = problem.image2camera[im1] camera2 = problem.image2camera[im2] cameras = {camera1.id: camera1, camera2.id: camera2} tracks, p1, p2 = matching.common_tracks(graph, im1, im2) print "Common tracks: {}".format(len(tracks)) thresh = data.config.get('five_point_algo_threshold', 0.006) min_inliers = data.config.get('five_point_algo_min_inliers', 50) R, t, inliers = custom_two_view_reconstruction( p1, p2, camera1, camera2, thresh, hint_forward) print "bootstrap: R={} t={} len(inliers)={}".format(R, t, len(inliers)) if len(inliers) <= 5: # FIXME: put const in config print "bootstrap failed: not enough points in initial reconstruction" return # Reconstruction is up to scale; set translation to 1. # (This will be corrected later in the bundle adjustment step.) t /= np.linalg.norm(t) reco = types.Reconstruction() reco.cameras = cameras shot1 = types.Shot() shot1.id = im1 shot1.camera = camera1 shot1.pose = types.Pose() shot1.metadata = get_empty_metadata() reco.add_shot(shot1) shot2 = types.Shot() shot2.id = im2 shot2.camera = camera2 shot2.pose = types.Pose(R, t) shot2.metadata = get_empty_metadata() reco.add_shot(shot2) reconstruction.triangulate_shot_features( graph, reco, im1, data.config.get('triangulation_threshold', 0.004), data.config.get('triangulation_min_ray_angle', 2.0)) if len(reco.points) < min_inliers: print "bootstrap failed: not enough points after triangulation" return reconstruction.bundle_single_view(graph, reco, im2, data.config) reconstruction.retriangulate(graph, reco, data.config) reconstruction.bundle_single_view(graph, reco, im2, data.config) debug = partial(_debug_short, graph, reco, im1, im2) debug("bootstraped reconstruction") return reco