Exemple #1
0
    def test_align_poses_along_straight_line_gauge(self):
        """Test if Align Pose3Pairs method can account for gauge ambiguity.

        Scenario:
           3 object poses
           with gauge ambiguity (2x scale)
           world frame has poses rotated about z-axis (90 degree yaw)
           world and egovehicle frame translated by 11 meters w.r.t. each other
        """
        Rz90 = Rot3.Rz(np.deg2rad(90))

        # Create source poses (three objects o1, o2, o3 living in the egovehicle "e" frame)
        # Suppose they are 3d cuboids detected by an onboard sensor in the egovehicle frame
        eTo0 = Pose3(Rot3(), np.array([1, 0, 0]))
        eTo1 = Pose3(Rot3(), np.array([2, 0, 0]))
        eTo2 = Pose3(Rot3(), np.array([4, 0, 0]))

        eToi_list = [eTo0, eTo1, eTo2]

        # Create destination poses
        # (same three objects, but instead living in the world/city "w" frame)
        wTo0 = Pose3(Rz90, np.array([0, 12, 0]))
        wTo1 = Pose3(Rz90, np.array([0, 14, 0]))
        wTo2 = Pose3(Rz90, np.array([0, 18, 0]))

        wToi_list = [wTo0, wTo1, wTo2]

        we_pairs = Pose3Pairs(list(zip(wToi_list, eToi_list)))

        # Recover the transformation wSe (i.e. world_S_egovehicle)
        wSe = Similarity3.Align(we_pairs)

        for wToi, eToi in zip(wToi_list, eToi_list):
            self.gtsamAssertEquals(wToi, wSe.transformFrom(eToi))
Exemple #2
0
def align_poses_sim3(aTi_list: List[Pose3],
                     bTi_list: List[Pose3]) -> Similarity3:
    """Align two pose graphs via similarity transformation. Note: poses cannot be missing/invalid.

    We force SIM(3) alignment rather than SE(3) alignment.
    We assume the two trajectories are of the exact same length.

    Args:
        aTi_list: reference poses in frame "a" which are the targets for alignment
        bTi_list: input poses which need to be aligned to frame "a"

    Returns:
        aSb: Similarity(3) object that aligns the two pose graphs.
    """
    n_to_align = len(aTi_list)
    assert len(aTi_list) == len(bTi_list)
    assert n_to_align >= 2, "SIM(3) alignment uses at least 2 frames"

    ab_pairs = Pose3Pairs(list(zip(aTi_list, bTi_list)))

    aSb = Similarity3.Align(ab_pairs)

    if np.isnan(aSb.scale()) or aSb.scale() == 0:
        # we have run into a case where points have no translation between them (i.e. panorama).
        # We will first align the rotations and then align the translation by using centroids.
        # TODO: handle it in GTSAM

        # align the rotations first, so that we can find the translation between the two panoramas
        aSb = Similarity3(aSb.rotation(), np.zeros((3, )), 1.0)
        aTi_list_rot_aligned = [aSb.transformFrom(bTi) for bTi in bTi_list]

        # fit a single translation motion to the centroid
        aTi_centroid = np.array([aTi.translation()
                                 for aTi in aTi_list]).mean(axis=0)
        aTi_rot_aligned_centroid = np.array(
            [aTi.translation() for aTi in aTi_list_rot_aligned]).mean(axis=0)

        # construct the final SIM3 transform
        aSb = Similarity3(aSb.rotation(),
                          aTi_centroid - aTi_rot_aligned_centroid, 1.0)

    return aSb
Exemple #3
0
    def test_align_poses_scaled_squares(self):
        """Test if Align Pose3Pairs method can account for gauge ambiguity.

        Make sure a big and small square can be aligned.
        The u's represent a big square (10x10), and v's represents a small square (4x4).

        Scenario:
           4 object poses
           with gauge ambiguity (2.5x scale)
        """
        # 0, 90, 180, and 270 degrees yaw
        R0 = Rot3.Rz(np.deg2rad(0))
        R90 = Rot3.Rz(np.deg2rad(90))
        R180 = Rot3.Rz(np.deg2rad(180))
        R270 = Rot3.Rz(np.deg2rad(270))

        aTi0 = Pose3(R0, np.array([2, 3, 0]))
        aTi1 = Pose3(R90, np.array([12, 3, 0]))
        aTi2 = Pose3(R180, np.array([12, 13, 0]))
        aTi3 = Pose3(R270, np.array([2, 13, 0]))

        aTi_list = [aTi0, aTi1, aTi2, aTi3]

        bTi0 = Pose3(R0, np.array([4, 3, 0]))
        bTi1 = Pose3(R90, np.array([8, 3, 0]))
        bTi2 = Pose3(R180, np.array([8, 7, 0]))
        bTi3 = Pose3(R270, np.array([4, 7, 0]))

        bTi_list = [bTi0, bTi1, bTi2, bTi3]

        ab_pairs = Pose3Pairs(list(zip(aTi_list, bTi_list)))

        # Recover the transformation wSe (i.e. world_S_egovehicle)
        aSb = Similarity3.Align(ab_pairs)

        for aTi, bTi in zip(aTi_list, bTi_list):
            self.gtsamAssertEquals(aTi, aSb.transformFrom(bTi))
def align_poses_sim3(aTi_list: List[Pose3],
                     bTi_list: List[Pose3]) -> Tuple[List[Pose3], Similarity3]:
    """Align two pose graphs via similarity transformation. Note: poses cannot be missing/invalid.

    We force SIM(3) alignment rather than SE(3) alignment.
    We assume the two trajectories are of the exact same length.

    Args:
        aTi_list: reference poses in frame "a" which are the targets for alignment
        bTi_list: input poses which need to be aligned to frame "a"

    Returns:
        aTi_list_: transformed input poses previously "bTi_list" but now which
            have the same origin and scale as reference (now living in "a" frame)
        aSb: Similarity(3) object that aligns the two pose graphs.
    """
    assert len(aTi_list) == len(bTi_list)

    valid_pose_tuples = [
        pose_tuple for pose_tuple in list(zip(aTi_list, bTi_list))
        if pose_tuple[0] is not None and pose_tuple[1] is not None
    ]
    n_to_align = len(valid_pose_tuples)
    if n_to_align < 2:
        logger.error("SIM(3) alignment uses at least 2 frames; Skipping")
        return bTi_list, Similarity3(Rot3(), np.zeros((3, )), 1.0)

    ab_pairs = Pose3Pairs(valid_pose_tuples)

    aSb = Similarity3.Align(ab_pairs)

    if np.isnan(aSb.scale()) or aSb.scale() == 0:
        # we have run into a case where points have no translation between them (i.e. panorama).
        # We will first align the rotations and then align the translation by using centroids.
        # TODO: handle it in GTSAM

        # align the rotations first, so that we can find the translation between the two panoramas
        aSb = Similarity3(aSb.rotation(), np.zeros((3, )), 1.0)
        aTi_list_rot_aligned = [
            aSb.transformFrom(bTi) for _, bTi in valid_pose_tuples
        ]

        # fit a single translation motion to the centroid
        aTi_centroid = np.array(
            [aTi.translation() for aTi, _ in valid_pose_tuples]).mean(axis=0)
        aTi_rot_aligned_centroid = np.array(
            [aTi.translation() for aTi in aTi_list_rot_aligned]).mean(axis=0)

        # construct the final SIM3 transform
        aSb = Similarity3(aSb.rotation(),
                          aTi_centroid - aTi_rot_aligned_centroid, 1.0)

    # TODO(johnwlambert): fix bug in GTSAM, where scale can flip to a small negative number
    # a negative scale destroys cheirality when applied.
    # See GTSAM issue here: https://github.com/borglab/gtsam/issues/995
    aSb = Similarity3(R=aSb.rotation(),
                      t=aSb.translation(),
                      s=np.absolute(aSb.scale()))

    # provide a summary of the estimated alignment transform
    aRb = aSb.rotation().matrix()
    atb = aSb.translation()
    rz, ry, rx = Rotation.from_matrix(aRb).as_euler("zyx", degrees=True)
    logger.info(
        "Sim(3) Rotation `aRb`: rz=%.2f deg., ry=%.2f deg., rx=%.2f deg.", rz,
        ry, rx)
    logger.info(f"Sim(3) Translation `atb`: [tx,ty,tz]={str(np.round(atb,2))}")
    logger.info("Sim(3) Scale `asb`: %.2f", float(aSb.scale()))

    aTi_list_ = []
    for bTi in bTi_list:
        if bTi is None:
            aTi_list_.append(None)
        else:
            aTi_list_.append(aSb.transformFrom(bTi))

    logger.info("Pose graph Sim(3) alignment complete.")

    return aTi_list_, aSb
Exemple #5
0
def align_poses_sim3(aTi_list: List[Pose3],
                     bTi_list: List[Pose3]) -> List[Pose3]:
    """Align by similarity transformation.

    We force SIM(3) alignment rather than SE(3) alignment.
    We assume the two trajectories are of the exact same length.

    Args:
        aTi_list: reference poses in frame "a" which are the targets for alignment
        bTi_list: input poses which need to be aligned to frame "a"

    Returns:
        aTi_list_: transformed input poses previously "bTi_list" but now which
            have the same origin and scale as reference (now living in "a" frame)
    """
    n_to_align = len(aTi_list)
    assert len(aTi_list) == len(bTi_list)
    assert n_to_align >= 2, "SIM(3) alignment uses at least 2 frames"

    ab_pairs = Pose3Pairs(list(zip(aTi_list, bTi_list)))

    aSb = Similarity3.Align(ab_pairs)

    if np.isnan(aSb.scale()) or aSb.scale() == 0:
        # we have run into a case where points have no translation between them (i.e. panorama).
        # We will first align the rotations and then align the translation by using centroids.
        # TODO: handle it in GTSAM

        # align the rotations first, so that we can find the translation between the two panoramas
        aSb = Similarity3(aSb.rotation(), np.zeros((3, )), 1.0)
        aTi_list_rot_aligned = [aSb.transformFrom(bTi) for bTi in bTi_list]

        # fit a single translation motion to the centroid
        aTi_centroid = np.array([aTi.translation()
                                 for aTi in aTi_list]).mean(axis=0)
        aTi_rot_aligned_centroid = np.array(
            [aTi.translation() for aTi in aTi_list_rot_aligned]).mean(axis=0)

        # construct the final SIM3 transform
        aSb = Similarity3(aSb.rotation(),
                          aTi_centroid - aTi_rot_aligned_centroid, 1.0)

    # provide a summary of the estimated alignment transform
    aRb = aSb.rotation().matrix()
    atb = aSb.translation()
    rz, ry, rx = Rotation.from_matrix(aRb).as_euler("zyx", degrees=True)
    logger.info(
        f"Sim(3) Rotation `aRb`: rz={rz:.2f} deg., ry={ry:.2f} deg., rx={rx:.2f} deg.",
    )
    logger.info(f"Sim(3) Translation `atb`: [tx,ty,tz]={str(np.round(atb,2))}")
    logger.info(f"Sim(3) Scale `asb`: {float(aSb.scale()):.2f}")

    aTi_list_ = []
    for i in range(n_to_align):
        bTi = bTi_list[i]

        aTi_list_.append(aSb.transformFrom(bTi))

    logger.info("Pose graph Sim(3) alignment complete.")

    return aTi_list_