def gcp_errors(data: DataSetBase, reconstructions: List[types.Reconstruction]) -> Dict[str, Any]: all_errors = [] reference = data.load_reference() gcps = data.load_ground_control_points() if not gcps: return {} all_errors = [] for gcp in gcps: if not gcp.lla: continue triangulated = None for rec in reconstructions: triangulated = multiview.triangulate_gcp(gcp, rec.shots, 1.0, 0.1) if triangulated is None: continue else: break if triangulated is None: continue gcp_enu = reference.to_topocentric(*gcp.lla_vec) all_errors.append(triangulated - gcp_enu) return _gps_gcp_errors_stats(np.array(all_errors))
def bootstrap_reconstruction(data: DataSetBase, tracks_manager, camera_priors, im1, im2, p1, p2): """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_id1 = data.load_exif(im1)["camera"] camera_id2 = data.load_exif(im2)["camera"] camera1 = camera_priors[camera_id1] camera2 = camera_priors[camera_id2] 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 reconstruction = types.Reconstruction() reconstruction.reference = data.load_reference() reconstruction.cameras = camera_priors shot1 = reconstruction.create_shot(im1, camera_id1, pygeometry.Pose()) shot1.metadata = get_image_metadata(data, im1) shot2 = reconstruction.create_shot(im2, camera_id2, pygeometry.Pose(R, t)) shot2.metadata = get_image_metadata(data, im2) triangulate_shot_features(tracks_manager, 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, report bundle_single_view(reconstruction, im2, 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_single_view(reconstruction, im2, camera_priors, data.config) report["decision"] = "Success" report["memory_usage"] = current_memory_usage() return reconstruction, report
def get_image_metadata(data: DataSetBase, image: str): """Get image metadata as a ShotMetadata object.""" metadata = pymap.ShotMeasurements() exif = data.load_exif(image) reference = data.load_reference() if "gps" in exif and "latitude" in exif["gps"] and "longitude" in exif[ "gps"]: lat = exif["gps"]["latitude"] lon = exif["gps"]["longitude"] if data.config["use_altitude_tag"]: alt = min( [oexif.maximum_altitude, exif["gps"].get("altitude", 2.0)]) else: alt = 2.0 # Arbitrary value used to align the reconstruction x, y, z = reference.to_topocentric(lat, lon, alt) metadata.gps_position.value = [x, y, z] metadata.gps_accuracy.value = exif["gps"].get("dop", 15.0) if metadata.gps_accuracy.value == 0.0: metadata.gps_accuracy.value = 15.0 else: metadata.gps_position.value = [0.0, 0.0, 0.0] metadata.gps_accuracy.value = 999999.0 metadata.orientation.value = exif.get("orientation", 1) if "accelerometer" in exif: metadata.accelerometer.value = exif["accelerometer"] if "compass" in exif: metadata.compass_angle.value = exif["compass"]["angle"] if "accuracy" in exif["compass"]: metadata.compass_accuracy.value = exif["compass"]["accuracy"] if "capture_time" in exif: metadata.capture_time.value = exif["capture_time"] if "skey" in exif: metadata.sequence_key.value = exif["skey"] return metadata
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 get_image_metadata(data: DataSetBase, image: str) -> pymap.ShotMeasurements: """Get image metadata as a ShotMetadata object.""" exif = data.load_exif(image) reference = data.load_reference() return exif_to_metadata(exif, data.config["use_altitude_tag"], reference)
def match_candidates_from_metadata( images_ref: List[str], images_cand: List[str], exifs: Dict[str, Any], data: DataSetBase, config_override: Dict[str, Any], ) -> Tuple[List[Tuple[str, str]], Dict[str, Any]]: """Compute candidate matching pairs between between images_ref and images_cand Returns a list of pairs (im1, im2) such that (im1 in images_ref) is true. Returned pairs are unique given that (i, j) == (j, i). """ overriden_config = data.config.copy() overriden_config.update(config_override) max_distance = overriden_config["matching_gps_distance"] gps_neighbors = overriden_config["matching_gps_neighbors"] time_neighbors = overriden_config["matching_time_neighbors"] order_neighbors = overriden_config["matching_order_neighbors"] bow_neighbors = overriden_config["matching_bow_neighbors"] bow_gps_distance = overriden_config["matching_bow_gps_distance"] bow_gps_neighbors = overriden_config["matching_bow_gps_neighbors"] bow_other_cameras = overriden_config["matching_bow_other_cameras"] vlad_neighbors = overriden_config["matching_vlad_neighbors"] vlad_gps_distance = overriden_config["matching_vlad_gps_distance"] vlad_gps_neighbors = overriden_config["matching_vlad_gps_neighbors"] vlad_other_cameras = overriden_config["matching_vlad_other_cameras"] if not data.reference_lla_exists(): data.invent_reference_lla() reference = data.load_reference() if not all(map(has_gps_info, exifs.values())): if gps_neighbors != 0: logger.warn("Not all images have GPS info. " "Disabling matching_gps_neighbors.") gps_neighbors = 0 max_distance = 0 images_ref.sort() if (max_distance == gps_neighbors == time_neighbors == order_neighbors == bow_neighbors == vlad_neighbors == 0): # All pair selection strategies deactivated so we match all pairs d = set() t = set() o = set() b = set() v = set() pairs = { sorted_pair(i, j) for i in images_ref for j in images_cand if i != j } else: d = match_candidates_by_distance(images_ref, images_cand, exifs, reference, gps_neighbors, max_distance) t = match_candidates_by_time(images_ref, images_cand, exifs, time_neighbors) o = match_candidates_by_order(images_ref, images_cand, order_neighbors) b = match_candidates_with_bow( data, images_ref, images_cand, exifs, reference, bow_neighbors, bow_gps_distance, bow_gps_neighbors, bow_other_cameras, ) v = match_candidates_with_vlad( data, images_ref, images_cand, exifs, reference, vlad_neighbors, vlad_gps_distance, vlad_gps_neighbors, vlad_other_cameras, {}, ) pairs = d | t | o | set(b) | set(v) pairs = ordered_pairs(pairs, images_ref) report = { "num_pairs_distance": len(d), "num_pairs_time": len(t), "num_pairs_order": len(o), "num_pairs_bow": len(b), "num_pairs_vlad": len(v), } return pairs, report
def gcp_errors(data: DataSetBase, reconstructions: List[types.Reconstruction]) -> Dict[str, Any]: all_errors = [] reference = data.load_reference() gcps = data.load_ground_control_points() if not gcps: return {} all_errors = [] gcp_stats = [] for gcp in gcps: if not gcp.lla: continue triangulated = None for rec in reconstructions: triangulated = multiview.triangulate_gcp(gcp, rec.shots, 1.0, 0.1) if triangulated is None: continue else: break if triangulated is None: continue gcp_enu = reference.to_topocentric(*gcp.lla_vec) e = triangulated - gcp_enu all_errors.append(e) # Begin computation of GCP stats observations = [] for i, obs in enumerate(gcp.observations): if not obs.shot_id in rec.shots: continue shot = rec.shots[obs.shot_id] reprojected = shot.project(gcp_enu) annotated = obs.projection r_pixel = features.denormalized_image_coordinates( np.array([[reprojected[0], reprojected[1]]]), shot.camera.width, shot.camera.height)[0] r_pixel[0] /= shot.camera.width r_pixel[1] /= shot.camera.height a_pixel = features.denormalized_image_coordinates( np.array([[annotated[0], annotated[1]]]), shot.camera.width, shot.camera.height)[0] a_pixel[0] /= shot.camera.width a_pixel[1] /= shot.camera.height observations.append({ 'shot_id': obs.shot_id, 'annotated': list(a_pixel), 'reprojected': list(r_pixel) }) gcp_stats.append({ 'id': gcp.id, 'coordinates': list(gcp_enu), 'observations': observations, 'error': list(e) }) # End computation of GCP stats with open( os.path.join(data.data_path, "stats", "ground_control_points.json"), 'w') as f: f.write(json.dumps(gcp_stats, indent=4)) return _gps_gcp_errors_stats(np.array(all_errors))