def test_bundle_alignment_prior(): """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' shot = types.Shot() shot.id = '1' shot.camera = camera shot.pose = types.Pose(np.random.rand(3), np.random.rand(3)) shot.metadata = types.ShotMetadata() shot.metadata.gps_position = [0, 0, 0] shot.metadata.gps_dop = 1 r = types.Reconstruction() r.add_camera(camera) r.add_shot(shot) graph = nx.Graph() camera_priors = {camera.id: camera} gcp = [] myconfig = config.default_config() reconstruction.bundle(graph, r, camera_priors, gcp, myconfig) 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])
def test_bundle_projection_fixed_internals(scene_synthetic): reference = scene_synthetic[0].get_reconstruction() camera_priors = {c.id: c for c in scene_synthetic[0].cameras} graph = tracking.as_graph(scene_synthetic[5]) # Create the connnections in the reference for point_id in reference.points.keys(): if point_id in graph: for shot_id, g_obs in graph[point_id].items(): color = g_obs["feature_color"] pt = g_obs["feature"] obs = pysfm.Observation( pt[0], pt[1], g_obs["feature_scale"], g_obs["feature_id"], color[0], color[1], color[2], ) reference.map.add_observation(shot_id, point_id, obs) orig_camera = copy.deepcopy(reference.cameras["1"]) custom_config = config.default_config() custom_config["bundle_use_gps"] = False custom_config["optimize_camera_parameters"] = False reconstruction.bundle(reference, camera_priors, [], custom_config) assert _projection_errors_std(reference.points) < 5e-3 assert reference.cameras["1"].focal == orig_camera.focal assert reference.cameras["1"].k1 == orig_camera.k1 assert reference.cameras["1"].k2 == orig_camera.k2
def run(self, args): start = time.time() data = dataset.DataSet(args.dataset) graph = data.load_tracks_graph() reconstructions = data.load_reconstruction(args.input) gcp = data.load_ground_control_points() for reconstruction in reconstructions: orec.bundle(graph, reconstruction, gcp, data.config) end = time.time() with open(data.profile_log(), 'a') as fout: fout.write('bundle: {0}\n'.format(end - start)) data.save_reconstruction(reconstructions, args.output)
def test_bundle_projection_fixed_internals(scene_synthetic): reference = scene_synthetic[0].get_reconstruction() graph = scene_synthetic[5] adjusted = copy.deepcopy(reference) custom_config = config.default_config() custom_config['bundle_use_gps'] = False custom_config['optimize_camera_parameters'] = False reconstruction.bundle(graph, adjusted, {}, custom_config) assert _projection_errors_std(adjusted.points) < 5e-3 assert reference.cameras['1'].focal == adjusted.cameras['1'].focal assert reference.cameras['1'].k1 == adjusted.cameras['1'].k1 assert reference.cameras['1'].k2 == adjusted.cameras['1'].k2
def run(self, args): start = time.time() data = dataset.DataSet(args.dataset) graph = data.load_tracks_graph() reconstructions = data.load_reconstruction(args.input) gcp = None if data.ground_control_points_exist(): gcp = data.load_ground_control_points() for reconstruction in reconstructions: orec.bundle(graph, reconstruction, gcp, data.config) end = time.time() with open(data.profile_log(), 'a') as fout: fout.write('bundle: {0}\n'.format(end - start)) data.save_reconstruction(reconstructions, args.output)
def run_dataset(dataset, input, output): """ Bundle a reconstructions. Args: input: input reconstruction JSON in the dataset output: input reconstruction JSON in the dataset """ reconstructions = dataset.load_reconstruction(input) camera_priors = dataset.load_camera_models() gcp = dataset.load_ground_control_points() for reconstruction in reconstructions: orec.bundle(reconstruction, camera_priors, gcp, dataset.config) dataset.save_reconstruction(reconstructions, output)
def run_dataset(dataset: DataSetBase, input, output): """Bundle a reconstructions. Args: input: input reconstruction JSON in the dataset output: input reconstruction JSON in the dataset """ reconstructions = dataset.load_reconstruction(input) camera_priors = dataset.load_camera_models() gcp = dataset.load_ground_control_points() tracks_manager = dataset.load_tracks_manager() # load the tracks manager and add its observations to the reconstruction # go through all the points and add their shots for reconstruction in reconstructions: reconstruction.add_correspondences_from_tracks_manager(tracks_manager) orec.bundle(reconstruction, camera_priors, gcp, dataset.config) dataset.save_reconstruction(reconstructions, output)
def test_bundle_alignment_prior(): """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))) shot.metadata.gps_position.value = [0, 0, 0] shot.metadata.gps_accuracy.value = 1 camera_priors = {camera.id: camera} gcp = [] myconfig = config.default_config() reconstruction.bundle(r, camera_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])
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() -> None: """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} rig_priors = dict(r.rig_cameras.items()) 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, rig_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, rig_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, rig_priors, gcp, myconfig) assert np.allclose(shot.pose.get_origin(), np.zeros(3))
def main(): args = parse_args() path = args.dataset data = dataset.DataSet(path) for fn in ("reconstruction.json", "ground_control_points.json", "tracks.csv"): if not (os.path.exists(os.path.join(path, fn))): logger.error(f"Missing file: {fn}") return assert args.rec_a != args.rec_b, "rec_a and rec_b should be different" camera_models = data.load_camera_models() tracks_manager = data.load_tracks_manager() gcps = data.load_ground_control_points() fn_resplit = f"reconstruction_gcp_ba_resplit_{args.rec_a}x{args.rec_b}.json" fn_rigid = f"reconstruction_gcp_rigid_{args.rec_a}x{args.rec_b}.json" if args.rec_b: # reconstruction - to - reconstruction annotation if args.fast and os.path.exists(data._reconstruction_file(fn_resplit)): reconstructions = data.load_reconstruction(fn_resplit) else: reconstructions = data.load_reconstruction() reconstructions = [ reconstructions[args.rec_a], reconstructions[args.rec_b] ] coords0 = triangulate_gcps(gcps, reconstructions[0]) coords1 = triangulate_gcps(gcps, reconstructions[1]) s, A, b = find_alignment(coords1, coords0) align.apply_similarity(reconstructions[1], s, A, b) else: # Image - to - reconstruction annotation reconstructions = data.load_reconstruction() base = reconstructions[args.rec_a] resected = resect_annotated_single_images(base, gcps, camera_models, data) for shot in resected.shots.values(): shot.metadata.gps_accuracy.value = 1e12 shot.metadata.gps_position.value = shot.pose.get_origin() reconstructions = [base, resected] data.save_reconstruction(reconstructions, fn_rigid) merged = merge_reconstructions(reconstructions, tracks_manager) # data.save_reconstruction( # [merged], f"reconstruction_merged_{args.rec_a}x{args.rec_b}.json" # ) if not args.fast: data.config["bundle_max_iterations"] = 200 data.config["bundle_use_gcp"] = True print("Running BA ...") orec.bundle(merged, camera_models, gcp=gcps, config=data.config) # rigid rotation to put images on the ground orec.align_reconstruction(merged, None, data.config) # data.save_reconstruction( # [merged], "reconstruction_gcp_ba_{args.rec_a}x{args.rec_b}.json" # ) gcp_reprojections = reproject_gcps(gcps, merged) reprojection_errors = get_all_reprojection_errors(gcp_reprojections) err_values = [t[2] for t in reprojection_errors] max_reprojection_error = np.max(err_values) median_reprojection_error = np.median(err_values) with open( f"{data.data_path}/gcp_reprojections_{args.rec_a}x{args.rec_b}.json", "w") as f: json.dump(gcp_reprojections, f, indent=4, sort_keys=True) gcp_std = compute_gcp_std(gcp_reprojections) logger.info(f"GCP reprojection error STD: {gcp_std}") if not args.fast: resplit = resplit_reconstruction(merged, reconstructions) data.save_reconstruction(resplit, fn_resplit) all_shots_std = [] # We run bundle by fixing one reconstruction. # If we have two reconstructions, we do this twice, fixing each one. _rec_ixs = [(0, 1), (1, 0)] if args.rec_b else [(0, 1)] for rec_ixs in _rec_ixs: print(f"Running BA with fixed images. Fixing rec #{rec_ixs[0]}") fixed_images = set(reconstructions[rec_ixs[0]].shots.keys()) bundle_with_fixed_images( merged, camera_models, gcp=gcps, gcp_std=gcp_std, fixed_images=fixed_images, config=data.config, ) logger.info( f"STD in the position of shots in R#{rec_ixs[1]} w.r.t R#{rec_ixs[0]}" ) for shot in merged.shots.values(): if shot.id in reconstructions[rec_ixs[1]].shots: u, std = decompose_covariance(shot.covariance[3:, 3:]) all_shots_std.append((shot.id, np.linalg.norm(std))) logger.info( f"{shot.id} position std: {np.linalg.norm(std)}") # If the STD of all shots is the same, replace by nan std_values = [x[1] for x in all_shots_std] n_bad_std = sum(std > args.std_threshold for std in std_values) n_good_std = sum(std <= args.std_threshold for std in std_values) if np.allclose(std_values, std_values[0]): all_shots_std = [(x[0], np.nan) for x in all_shots_std] n_bad_std = len(std_values) # Average positional STD median_shot_std = np.median([t[1] for t in all_shots_std]) # Save the shot STD to a file with open(f"{data.data_path}/shots_std_{args.rec_a}x{args.rec_b}.csv", "w") as f: s = sorted(all_shots_std, key=lambda t: -t[-1]) for t in s: line = "{}, {}".format(*t) f.write(line + "\n") max_shot_std = s[0][1] got_shots_std = not np.isnan(all_shots_std[0][1]) else: n_bad_std = -1 n_good_std = -1 median_shot_std = -1 max_shot_std = -1 got_shots_std = False n_bad_gcp_annotations = int( sum(t[2] > args.px_threshold for t in reprojection_errors)) for t in reprojection_errors: if t[2] > args.px_threshold: print(t) metrics = { "n_reconstructions": len(data.load_reconstruction()), "median_shot_std": median_shot_std, "max_shot_std": max_shot_std, "max_reprojection_error": max_reprojection_error, "median_reprojection_error": median_reprojection_error, "n_gcp": len(gcps), "n_bad_gcp_annotations": n_bad_gcp_annotations, "n_bad_position_std": int(n_bad_std), "n_good_position_std": int(n_good_std), "rec_a": args.rec_a, "rec_b": args.rec_b, } logger.info(metrics) p_metrics = f"{data.data_path}/run_ba_metrics_{args.rec_a}x{args.rec_b}.json" with open(p_metrics, "w") as f: json.dump(metrics, f, indent=4, sort_keys=True) logger.info(f"Saved metrics to {p_metrics}") logger.info("========================================") logger.info("=============== Summary ================") logger.info("========================================") if n_bad_std == 0 and n_bad_gcp_annotations == 0: logger.info( f"No issues. All gcp reprojections are under {args.px_threshold}" f" and all frames are localized within {args.std_threshold}m") if n_bad_std == 0 and n_bad_gcp_annotations == 0: logger.info( f"No issues. All gcp reprojections are under {args.px_threshold}" f" and all frames are localized within {args.std_threshold}m") if n_bad_std != 0 or n_bad_gcp_annotations != 0: if args.fast: logger.info( "Positional uncertainty unknown since analysis ran in fast mode." ) elif not got_shots_std: logger.info( "Could not get positional uncertainty. It could be because:" "\na) there are not enough GCPs." "\nb) they are badly distributed in 3D." "\nc) there are some wrong annotations") else: logger.info( f"{n_bad_std} badly localized images (error>{args.std_threshold})." " Use the frame list on each view to find these") logger.info( f"{n_bad_gcp_annotations} annotations with large reprojection error." " Worst offenders:") stats_bad_reprojections = get_number_of_wrong_annotations_per_gcp( gcp_reprojections, args.px_threshold) gcps_sorted = sorted(stats_bad_reprojections, key=lambda k: -stats_bad_reprojections[k])[:5] for ix, gcp_id in enumerate(gcps_sorted): n = stats_bad_reprojections[gcp_id] if n > 0: logger.info(f"#{ix+1} - {gcp_id}: {n} bad annotations")
def align( path: str, rec_a: int, rec_b: int, rigid: bool, covariance: bool, px_threshold: float, std_threshold: float, ): data = dataset.DataSet(path) for fn in ("reconstruction.json", "ground_control_points.json", "tracks.csv"): if not (os.path.exists(os.path.join(path, fn))): logger.error(f"Missing file: {fn}") return camera_models = data.load_camera_models() tracks_manager = data.load_tracks_manager() fix_3d_annotations_in_gcp_file(data) gcps = data.load_ground_control_points() fn_resplit = f"reconstruction_gcp_ba_resplit_{rec_a}x{rec_b}.json" fn_rigid = f"reconstruction_gcp_rigid_{rec_a}x{rec_b}.json" reconstructions = data.load_reconstruction() if len(reconstructions) > 1: if rec_b is not None: # reconstruction - to - reconstruction alignment if rigid and os.path.exists(data._reconstruction_file(fn_resplit)): reconstructions = data.load_reconstruction(fn_resplit) else: reconstructions = data.load_reconstruction() reconstructions = [reconstructions[rec_a], reconstructions[rec_b]] coords0 = triangulate_gcps(gcps, reconstructions[0]) coords1 = triangulate_gcps(gcps, reconstructions[1]) n_valid_0 = sum(c is not None for c in coords0) logger.debug(f"Triangulated {n_valid_0}/{len(gcps)} gcps for rec #{rec_a}") n_valid_1 = sum(c is not None for c in coords1) logger.debug(f"Triangulated {n_valid_1}/{len(gcps)} gcps for rec #{rec_b}") try: s, A, b = find_alignment(coords1, coords0) apply_similarity(reconstructions[1], s, A, b) except ValueError: logger.warning(f"Could not rigidly align rec #{rec_b} to rec #{rec_a}") return logger.info(f"Rigidly aligned rec #{rec_b} to rec #{rec_a}") else: # Image - to - reconstruction annotation reconstructions = data.load_reconstruction() base = reconstructions[rec_a] resected = resect_annotated_single_images(base, gcps, camera_models, data) reconstructions = [base, resected] else: logger.debug( "Only one reconstruction in reconstruction.json. Will only to 3d-3d alignment if any" ) align_external_3d_models_to_reconstruction( data, gcps, reconstructions[0], rec_a ) return logger.debug(f"Aligning annotations, if any, to rec #{rec_a}") align_external_3d_models_to_reconstruction(data, gcps, reconstructions[0], rec_a) # Set the GPS constraint of the moved/resected shots to the manually-aligned position for shot in reconstructions[1].shots.values(): shot.metadata.gps_position.value = shot.pose.get_origin() data.save_reconstruction(reconstructions, fn_rigid) logger.info("Merging reconstructions") merged = merge_reconstructions(reconstructions, tracks_manager) # data.save_reconstruction( # [merged], f"reconstruction_merged_{rec_a}x{rec_b}.json" # ) # Scale the GPS DOP with the number of shots to ensure GCPs are used to align for shot in merged.shots.values(): shot.metadata.gps_accuracy.value = 0.5 * len(merged.shots) gcp_alignment = {"after_rigid": gcp_geopositional_error(gcps, merged)} logger.info( "GCP errors after rigid alignment:\n" + "\n".join( "[{}]: {:.2f} m / {:.2f} m (planar)".format( k, v["error"], v["error_planar"] ) for k, v in gcp_alignment["after_rigid"].items() ) ) if not rigid: data.config["bundle_max_iterations"] = 200 data.config["bundle_use_gcp"] = True logger.info("Running BA on merged reconstructions") # orec.align_reconstruction(merged, None, data.config) orec.bundle(merged, camera_models, {}, gcp=gcps, config=data.config) data.save_reconstruction( [merged], f"reconstruction_gcp_ba_{rec_a}x{rec_b}.json" ) gcp_alignment["after_bundle"] = gcp_geopositional_error(gcps, merged) logger.info( "GCP errors after bundle:\n" + "\n".join( "[{}]: {:.2f} m / {:.2f} m (planar)".format( k, v["error"], v["error_planar"] ) for k, v in gcp_alignment["after_bundle"].items() ) ) with open(f"{data.data_path}/gcp_alignment_{rec_a}x{rec_b}.json", "w") as f: json.dump(gcp_alignment, f, indent=4, sort_keys=True) # Reproject GCPs with a very loose threshold so that we get a point every time # These reprojections are only used for feedback in any case gcp_reprojections = reproject_gcps(gcps, merged, reproj_threshold=10) reprojection_errors = get_sorted_reprojection_errors(gcp_reprojections) err_values = [t[2] for t in reprojection_errors] max_reprojection_error = np.max(err_values) median_reprojection_error = np.median(err_values) with open(f"{data.data_path}/gcp_reprojections_{rec_a}x{rec_b}.json", "w") as f: json.dump(gcp_reprojections, f, indent=4, sort_keys=True) n_bad_gcp_annotations = int(sum(t[2] > px_threshold for t in reprojection_errors)) if n_bad_gcp_annotations > 0: logger.info(f"{n_bad_gcp_annotations} large reprojection errors:") for t in reprojection_errors: if t[2] > px_threshold: logger.info(t) gcp_std = compute_gcp_std(gcp_reprojections) logger.info(f"GCP reprojection error STD: {gcp_std}") resplit = resplit_reconstruction(merged, reconstructions) data.save_reconstruction(resplit, fn_resplit) if covariance: # Re-triangulate to remove badly conditioned points n_points = len(merged.points) logger.info("Re-triangulating...") backup = data.config["triangulation_min_ray_angle"] data.config["triangulation_min_ray_angle"] = 2.0 orec.retriangulate(tracks_manager, merged, data.config) orec.paint_reconstruction(data, tracks_manager, merged) data.config["triangulation_min_ray_angle"] = backup logger.info( f"Re-triangulated. Removed {n_points - len(merged.points)}." f" Kept {int(100*len(merged.points)/n_points)}%" ) data.save_reconstruction( [merged], f"reconstruction_gcp_ba_retriangulated_{rec_a}x{rec_b}.json", ) all_shots_std = [] # We run bundle by fixing one reconstruction. # If we have two reconstructions, we do this twice, fixing each one. _rec_ixs = [(0, 1), (1, 0)] if rec_b is not None else [(0, 1)] for rec_ixs in _rec_ixs: logger.info(f"Running BA with fixed images. Fixing rec #{rec_ixs[0]}") fixed_images = set(reconstructions[rec_ixs[0]].shots.keys()) covariance_estimation_valid = bundle_with_fixed_images( merged, camera_models, gcp=gcps, gcp_std=gcp_std, fixed_images=fixed_images, config=data.config, ) if not covariance_estimation_valid: logger.info( f"Could not get positional uncertainty for pair {rec_ixs} It could be because:" "\na) there are not enough GCPs." "\nb) they are badly distributed in 3D." "\nc) there are some wrong annotations" ) shots_std_this_pair = [ (shot, np.nan) for shot in reconstructions[rec_ixs[1]].shots ] else: shots_std_this_pair = [] for shot in merged.shots.values(): if shot.id in reconstructions[rec_ixs[1]].shots: u, std_v = decompose_covariance(shot.covariance[3:, 3:]) std = np.linalg.norm(std_v) shots_std_this_pair.append((shot.id, std)) logger.debug(f"{shot.id} std: {std}") all_shots_std.extend(shots_std_this_pair) std_values = [x[1] for x in all_shots_std] n_nan_std = sum(np.isnan(std) for std in std_values) n_good_std = sum( std <= std_threshold for std in std_values if not np.isnan(std) ) n_bad_std = len(std_values) - n_good_std - n_nan_std # Average positional STD median_shot_std = np.median(std_values) # Save the shot STD to a file with open(f"{data.data_path}/shots_std_{rec_a}x{rec_b}.csv", "w") as f: s = sorted(all_shots_std, key=lambda t: -t[-1]) for t in s: line = "{}, {}".format(*t) f.write(line + "\n") max_shot_std = s[0][1] else: n_nan_std = -1 n_bad_std = -1 n_good_std = -1 median_shot_std = -1 max_shot_std = -1 std_values = [] metrics = { "n_reconstructions": len(data.load_reconstruction()), "median_shot_std": median_shot_std, "max_shot_std": max_shot_std, "max_reprojection_error": max_reprojection_error, "median_reprojection_error": median_reprojection_error, "n_gcp": len(gcps), "n_bad_gcp_annotations": n_bad_gcp_annotations, "n_bad_position_std": int(n_bad_std), "n_good_position_std": int(n_good_std), "n_nan_position_std": int(n_nan_std), "rec_a": rec_a, "rec_b": rec_b, } logger.info(metrics) p_metrics = f"{data.data_path}/run_ba_metrics_{rec_a}x{rec_b}.json" with open(p_metrics, "w") as f: json.dump(metrics, f, indent=4, sort_keys=True) logger.info(f"Saved metrics to {p_metrics}") logger.info("========================================") logger.info("=============== Summary ================") logger.info("========================================") if n_bad_std == 0 and n_bad_gcp_annotations == 0: logger.info( f"No issues. All gcp reprojections are under {px_threshold}" f" and all frames are localized within {std_threshold}m" ) if n_bad_std != 0 or n_bad_gcp_annotations != 0: if rigid: logger.info("Positional uncertainty was not calculated. (--rigid was set).") elif not covariance: logger.info( "Positional uncertainty was not calculated (--covariance not set)." ) else: logger.info( f"{n_nan_std}/{len(std_values)} images with unknown error." f"\n{n_good_std}/{len(std_values)} well-localized images." f"\n{n_bad_std}/{len(std_values)} badly localized images." ) if n_bad_gcp_annotations > 0: logger.info( f"{n_bad_gcp_annotations} annotations with large reprojection error." " Worst offenders:" ) stats_bad_reprojections = get_number_of_wrong_annotations_per_gcp( gcp_reprojections, px_threshold ) gcps_sorted = sorted( stats_bad_reprojections, key=lambda k: -stats_bad_reprojections[k] ) for ix, gcp_id in enumerate(gcps_sorted[:5]): n = stats_bad_reprojections[gcp_id] if n > 0: logger.info(f"#{ix+1} - {gcp_id}: {n} bad annotations") else: logger.info("No annotations with large reprojection errors")
def grow_reconstruction( problem, data, graph, reco, im1, im2, hint_forward=False): """ Grow the given reconstruction `reco` by adding a new image `im2` to it. The new image `im2` is initially matched against an image `im1`, which must already exist in the reconstruction. See `custom_two_view_reconstruction` for the meaning of `hint_forward`. Updates the reconstruction in-place and returns `True` on success. """ # FIXME: # - Make DRY with bootstrap_reconstruction; they look similar but they're # different in subtle ways. # - Could probably align once at the end, instead of aligning at every # step. # - Sometimes we get "Termination: NO_CONVERGENCE" from Ceres. Check for # that error case and perhaps run it for more iterations. print "----------------" print "grow_reconstruction({}, {}, hint_forward={})".format( im1, im2, hint_forward) reconstruction.bundle(graph, reco, None, data.config) align.align_reconstruction(reco, None, data.config) assert im1 in reco.shots assert im2 not in reco.shots camera1 = problem.image2camera[im1] camera2 = problem.image2camera[im2] 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) R, t, inliers = custom_two_view_reconstruction( p1, p2, camera1, camera2, thresh, hint_forward) print "grow: R={} t={} len(inliers)={}".format(R, t, len(inliers)) if len(inliers) <= 5: print "grow failed: not enough points in initial reconstruction" return False # Reconstruction is up to scale; set translation to 1. # (This will be corrected later in the bundle adjustment step.) t /= np.linalg.norm(t) assert camera1.id in reco.cameras if camera2.id not in reco.cameras: reco.add_camera(camera2) shot1_pose = reco.shots[im1].pose shot2 = types.Shot() shot2.id = im2 shot2.camera = camera2 shot2.pose = types.Pose(R, t).compose(shot1_pose) shot2.metadata = get_empty_metadata() reco.add_shot(shot2) debug = partial(_debug_short, graph, reco, im1, im2) debug("started with") # FIXME: fail here if im2 does not have enough tracks in common with reco pose_before = deepcopy(reco.shots[im2].pose) reconstruction.bundle_single_view(graph, reco, im2, data.config) # It's possible that bundle_single_view caused hint_forward to be violated. # If that's the case, revert to the pose before bundle_single_view, and # hope that after triangulating the points from `im2`, bundle adjustment # will find a better solution. rel_pose_after = reco.shots[im2].pose.compose( reco.shots[im1].pose.inverse()) t_after_norm = rel_pose_after.translation / np.linalg.norm( rel_pose_after.translation) # print "*** t_after_norm={}".format(t_after_norm) if hint_forward and t_after_norm[2] >= -0.75: # FIXME put const in config print "*** hint_forward violated; undoing bundle_single_view" reco.shots[im2].pose = pose_before rel_pose_after = reco.shots[im2].pose.compose( reco.shots[im1].pose.inverse()) t_after_norm = rel_pose_after.translation / np.linalg.norm( rel_pose_after.translation) print "*** after undo: t_after_norm={}".format(t_after_norm) # print reconstruction.triangulate_shot_features( graph, reco, im2, data.config.get('triangulation_threshold', 0.004), data.config.get('triangulation_min_ray_angle', 2.0)) reconstruction.bundle(graph, reco, None, data.config) reconstruction.remove_outliers(graph, reco, data.config) align.align_reconstruction(reco, None, data.config) reconstruction.retriangulate(graph, reco, data.config) reconstruction.bundle(graph, reco, None, data.config) debug("ended with") return True