Ejemplo n.º 1
0
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])
Ejemplo n.º 2
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
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
    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)
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
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)
Ejemplo n.º 8
0
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])
Ejemplo n.º 9
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)
Ejemplo n.º 10
0
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))
Ejemplo n.º 11
0
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")
Ejemplo n.º 12
0
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")
Ejemplo n.º 13
0
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