예제 #1
0
파일: tracks.py 프로젝트: borglab/gtsfm
def classify_tracks3d_with_gt_cameras(
    tracks: List[SfmTrack], cameras_gt: List[PinholeCameraCal3Bundler], reproj_error_thresh_px: float = 3
) -> List[TriangulationExitCode]:
    """Classifies the 3D tracks w.r.t ground truth cameras by performing triangulation and collecting exit codes.

    Args:
        tracks: list of 3d tracks, of length J.
        cameras_gt: cameras with GT params.
        reproj_error_thresh_px (optional): Reprojection error threshold (in pixels) for a track to be considered an
                                           all-inlier one. Defaults to 3.

    Returns:
        The triangulation exit code for each input track, as list of length J (same as input).
    """
    # convert the 3D tracks to 2D tracks
    tracks_2d: List[SfmTrack2d] = []
    for track_3d in tracks:
        num_measurements = track_3d.numberMeasurements()

        measurements: List[SfmMeasurement] = []
        for k in range(num_measurements):
            i, uv = track_3d.measurement(k)

            measurements.append(SfmMeasurement(i, uv))

        tracks_2d.append(SfmTrack2d(measurements))

    return classify_tracks2d_with_gt_cameras(tracks_2d, cameras_gt, reproj_error_thresh_px)
예제 #2
0
def test_compute_point_reprojection_errors():
    """Ensure a hypothesized 3d point is projected correctly and compared w/ 2 measurements.
    # For camera 0:
    # [13] = [10,0,3]   [1,0,0 | 0]  [1]
    # [24] = [0,10,4] * [0,1,0 | 0] *[2]
    #  [1] = [0, 0,1]   [0,0,1 | 0]  [1]
    #                                [1]

    # For camera 1:
    # [-7] = [10,0,3]   [1,0,0 |-2]  [1]
    # [44] = [0,10,4] * [0,1,0 | 2] *[2]
    #  [1] = [0, 0,1]   [0,0,1 | 0]  [1]
    #                                [1]
    """
    wTi0 = Pose3(Rot3.RzRyRx(0, 0, 0), np.zeros((3, 1)))
    wTi1 = Pose3(Rot3.RzRyRx(0, 0, 0), np.array([2, -2, 0]))

    f = 10
    k1 = 0
    k2 = 0
    u0 = 3
    v0 = 4

    K0 = Cal3Bundler(f, k1, k2, u0, v0)
    K1 = Cal3Bundler(f, k1, k2, u0, v0)

    track_camera_dict = {
        0: PinholeCameraCal3Bundler(wTi0, K0),
        1: PinholeCameraCal3Bundler(wTi1, K1)
    }
    point3d = np.array([1, 2, 1])
    measurements = [
        SfmMeasurement(i=1, uv=np.array([-8, 43])),
        SfmMeasurement(i=0, uv=np.array([13, 24]))
    ]

    errors, avg_track_reproj_error = reproj_utils.compute_point_reprojection_errors(
        track_camera_dict, point3d, measurements)
    expected_errors = np.array([np.sqrt(2), 0])
    np.testing.assert_allclose(errors, expected_errors)
    assert avg_track_reproj_error == np.sqrt(2) / 2
예제 #3
0
    def test_eq_check_with_different_measurements(self) -> None:
        """Tests the __eq__ function with one measurement having different value of the 2d point."""

        track_1 = SfmTrack2d(SAMPLE_MEASUREMENTS)
        # changing the value of the last measurement
        old_measurement = SAMPLE_MEASUREMENTS[-1]
        track_2 = SfmTrack2d(
            SAMPLE_MEASUREMENTS[:3] +
            [SfmMeasurement(old_measurement.i, np.random.rand(2))])

        self.assertNotEqual(track_1, track_2)
        self.assertNotEqual(track_2, track_1)
예제 #4
0
def get_track_with_duplicate_measurements() -> List[SfmMeasurement]:
    """Generates a track with 2 measurements in an image."""

    new_measurements = copy.deepcopy(MEASUREMENTS)

    new_measurements.append(
        SfmMeasurement(
            new_measurements[0].i,
            new_measurements[0].uv + Point2(2.0, -3.0),
        ))

    return new_measurements
예제 #5
0
def get_track_with_one_outlier() -> List[SfmMeasurement]:
    """Generates a track with outlier measurement."""
    # perturb one measurement
    idx_to_perturb = 5

    perturbed_measurements = copy.deepcopy(MEASUREMENTS)

    original_measurement = perturbed_measurements[idx_to_perturb]
    perturbed_measurements[idx_to_perturb] = SfmMeasurement(
        original_measurement.i,
        perturbed_measurements[idx_to_perturb].uv + Point2(20.0, -10.0),
    )

    return perturbed_measurements
예제 #6
0
    def testSimpleTriangulationOnDoorDataset(self):
        """Test the tracks of the door dataset using simple triangulation initialization. Using computed tracks with
        ground truth camera params.

        Expecting failures on 2 tracks which have incorrect matches."""
        with open(DOOR_TRACKS_PATH, "rb") as handle:
            tracks = pickle.load(handle)

        loader = FolderLoader(DOOR_DATASET_PATH, image_extension="JPG")

        camera_dict = {
            i: PinholeCameraCal3Bundler(loader.get_camera_pose(i),
                                        loader.get_camera_intrinsics(i))
            for i in range(len(loader))
        }

        initializer = Point3dInitializer(camera_dict,
                                         TriangulationParam.NO_RANSAC,
                                         reproj_error_thresh=1e5)

        # tracks which have expected failures
        # (both tracks have incorrect measurements)
        expected_failures = [
            SfmTrack2d(measurements=[
                SfmMeasurement(i=1,
                               uv=np.array([1252.22729492, 1487.29431152])),
                SfmMeasurement(i=2,
                               uv=np.array([1170.96679688, 1407.35876465])),
                SfmMeasurement(i=4, uv=np.array([263.32104492, 1489.76965332
                                                 ])),
            ]),
            SfmTrack2d(measurements=[
                SfmMeasurement(i=6, uv=np.array([1142.34545898, 735.92169189
                                                 ])),
                SfmMeasurement(i=7, uv=np.array([1179.84155273, 763.04095459
                                                 ])),
                SfmMeasurement(i=9, uv=np.array([216.54107666, 774.74017334])),
            ]),
        ]

        for track_2d in tracks:
            triangulated_track = initializer.triangulate(track_2d)

            if triangulated_track is None:
                # assert we have failures which are already expected
                self.assertIn(track_2d, expected_failures)
예제 #7
0
Unit tests for the FeatureTrackGenerator class.

Authors: Sushmita Warrier, Xiaolong Wu, John Lambert
"""

import copy
from typing import Dict, List, Tuple

import numpy as np
from gtsam.utils.test_case import GtsamTestCase

from gtsfm.common.keypoints import Keypoints
from gtsfm.common.sfm_track import SfmMeasurement, SfmTrack2d

SAMPLE_MEASUREMENTS = [
    SfmMeasurement(0, np.random.rand(2)),
    SfmMeasurement(2, np.random.rand(2)),
    SfmMeasurement(3, np.random.rand(2)),
    SfmMeasurement(5, np.random.rand(2)),
]


def get_dummy_keypoints_list() -> List[Keypoints]:
    """ """
    img1_kp_coords = np.array([[1, 1], [2, 2], [3, 3]])
    img1_kp_scale = np.array([6.0, 9.0, 8.5])
    img2_kp_coords = np.array([
        [1, 1],
        [2, 2],
        [3, 3],
        [4, 4],
예제 #8
0
# Generate 8 camera poses arranged in a circle of radius 40 m
CAMERAS = {
    i: PinholeCameraCal3Bundler(pose, CALIBRATION)
    for i, pose in enumerate(
        SFMdata.createPoses(
            Cal3_S2(
                CALIBRATION.fx(),
                CALIBRATION.fx(),
                0,
                CALIBRATION.px(),
                CALIBRATION.py(),
            )))
}
LANDMARK_POINT = Point3(0.0, 0.0, 0.0)
MEASUREMENTS = [
    SfmMeasurement(i, cam.project(LANDMARK_POINT))
    for i, cam in CAMERAS.items()
]


def get_track_with_one_outlier() -> List[SfmMeasurement]:
    """Generates a track with outlier measurement."""
    # perturb one measurement
    idx_to_perturb = 5

    perturbed_measurements = copy.deepcopy(MEASUREMENTS)

    original_measurement = perturbed_measurements[idx_to_perturb]
    perturbed_measurements[idx_to_perturb] = SfmMeasurement(
        original_measurement.i,
        perturbed_measurements[idx_to_perturb].uv + Point2(20.0, -10.0),
예제 #9
0
    def run(self, matches_dict: Dict[Tuple[int, int], np.ndarray],
            keypoints_list: List[Keypoints]) -> List[SfmTrack2d]:
        """Estimate tracks from feature correspondences.

        Creates a disjoint-set forest (DSF) and 2d tracks from pairwise matches. We create a singleton for union-find
        set elements from camera index of a detection and the index of that detection in that camera's keypoint list,
        i.e. (i,k).

        Args:
            matches_dict: Dict of pairwise matches of type:
                    key: indices for the matched pair of images
                    val: feature indices, as array of Nx2 shape; N being number of features. A row is (feature_idx1,
                         feature_idx2).
            keypoints_list: List of keypoints for each image.

        Returns:
            list of all valid SfmTrack2d generated by the matches.
        """
        # check to ensure dimensions of coordinates are correct
        dims_valid = all([kps.coordinates.ndim == 2 for kps in keypoints_list])
        if not dims_valid:
            raise Exception(
                "Dimensions for Keypoint coordinates incorrect. Array needs to be 2D"
            )

        # Generate the DSF to form tracks
        dsf = gtsam.DSFMapIndexPair()
        track_2d_list = []
        # for DSF finally
        # measurement_idxs represented by ks
        for (i1, i2), k_pairs in matches_dict.items():
            for (k1, k2) in k_pairs:
                dsf.merge(gtsam.IndexPair(i1, k1), gtsam.IndexPair(i2, k2))

        key_set = dsf.sets()

        erroneous_track_count = 0
        # create a landmark map: a list of tracks
        # Each track is represented as a list of (camera_idx, measurements)
        for set_id in key_set:
            index_pair_set = key_set[
                set_id]  # key_set is a wrapped C++ map, so this unusual syntax is required

            # Initialize track from measurements
            track_measurements = []
            for index_pair in gtsam.IndexPairSetAsArray(index_pair_set):
                # camera_idx is represented by i
                # measurement_idx is represented by k
                i = index_pair.i()
                k = index_pair.j()
                # add measurement in this track
                track_measurements += [
                    SfmMeasurement(i, keypoints_list[i].coordinates[k])
                ]

            track_2d = SfmTrack2d(track_measurements)

            # Skip erroneous track that had repeated measurements within the same image (i.e., violates transitivity).
            # This is an expected result from an incorrect correspondence slipping through.
            if track_2d.validate_unique_cameras():
                track_2d_list += [track_2d]
            else:
                erroneous_track_count += 1

        erroneous_track_pct = erroneous_track_count / len(
            key_set) * 100 if len(key_set) > 0 else np.NaN
        logger.info(
            f"DSF Union-Find: {erroneous_track_pct:.2f}% of tracks discarded from multiple obs. in a single image."
        )
        return track_2d_list