Exemplo n.º 1
0
class SPTAM(object):
    """
    - The interaction between tracking and mapping is through keyframes and CovisibilityGraph

    Args:
        object ([type]): [description]
    """
    def __init__(self, params):
        self.params = params

        self.tracker = Tracking(params)
        self.motion_model = MotionModel()

        self.graph = CovisibilityGraph()
        self.mapping = MappingThread(self.graph, params)

        self.loop_closing = LoopClosing(self, params)
        self.loop_correction = None

        self.reference = None  # reference keyframe which contains the most local map points
        self.preceding = None  # last keyframe
        self.current = None  # current frame
        self.status = defaultdict(bool)

    def stop(self):
        self.mapping.stop()
        if self.loop_closing is not None:
            self.loop_closing.stop()

    def initialize(self, frame):
        """Use stereo triangulation to initialize the map   

        Args:
            frame (StereoFrame): new incoming stereo frames(with feature extracted)
        """
        mappoints, measurements = frame.triangulate()
        assert len(mappoints) >= self.params.init_min_points, (
            'Not enough points to initialize map.')
        # The first frame is always KF
        keyframe = frame.to_keyframe()
        # The first keyframe should be fixed
        keyframe.set_fixed(True)
        self.graph.add_keyframe(keyframe)
        # All the measurements and mappoints are anchored to this very keyframe
        self.mapping.add_measurements(keyframe, mappoints, measurements)

        if self.loop_closing is not None:
            self.loop_closing.add_keyframe(keyframe)

        self.reference = keyframe
        self.preceding = keyframe
        self.current = keyframe
        self.status['initialized'] = True

        self.motion_model.update_pose(frame.timestamp, frame.position,
                                      frame.orientation)

    def track(self, frame):
        """
        - Step 1: predict the pose with constant velocity model to get predicted_pose
        - Step 2: use the sptam.preceding and self.reference frames as seed frame to extract
                  the local map points (sptam.filter_points(frame)), which can be viewed within current 
                  frame with the initial pose estimation
        - Step 3: Find the 2D image matchings with 3D map points' feature descriptors. Also update
                  the feature descriptor for the matched 3D map points to inprove the long term tracking capability
        - Step 4: Update the self.reference frame by querying the graph to find which frame 
                  has containts the most of current local map points set
        - Step 5: Do a motion only BA to refine the current frame pose
        - Step 6: Promote current frame to be KF if
                    - a. number of matched 3D map points is less than 20
                    - b. ration between matched 3D map points in current frame vs reference frame is less than a threhsold
        """
        while self.is_paused():
            time.sleep(1e-4)
        self.set_tracking(True)

        self.current = frame
        print('Tracking:', frame.idx, ' <- ', self.reference.id,
              self.reference.idx)
        # Step 1: predict the pose
        predicted_pose, _ = self.motion_model.predict_pose(frame.timestamp)
        frame.update_pose(predicted_pose)

        if self.loop_closing is not None:
            if self.loop_correction is not None:
                estimated_pose = g2o.Isometry3d(frame.orientation,
                                                frame.position)
                estimated_pose = estimated_pose * self.loop_correction
                frame.update_pose(estimated_pose)
                self.motion_model.apply_correction(self.loop_correction)
                self.loop_correction = None
        # Step 2: find the local map points using self.reference and self.preceding
        # frame as seed
        local_mappoints = self.filter_points(frame)
        # Step 3: find the matching of the 3D map points in current image with descriptor
        measurements = frame.match_mappoints(local_mappoints,
                                             Measurement.Source.TRACKING)

        print('measurements:', len(measurements), '   ', len(local_mappoints))

        tracked_map = set()
        # Update the map point feature descripotr
        for m in measurements:
            mappoint = m.mappoint
            mappoint.update_descriptor(m.get_descriptor())
            mappoint.increase_measurement_count()
            tracked_map.add(mappoint)
        try:
            # Find which KF contains the most seedpoints
            self.reference = self.graph.get_reference_frame(tracked_map)

            pose = self.tracker.refine_pose(frame.pose, frame.cam,
                                            measurements)
            frame.update_pose(pose)

            self.motion_model.update_pose(frame.timestamp, pose.position(),
                                          pose.orientation())
            tracking_is_ok = True
        except:
            tracking_is_ok = False
            print('tracking failed!!!')

        if tracking_is_ok and self.should_be_keyframe(frame, measurements):
            print('new keyframe', frame.idx)
            keyframe = frame.to_keyframe()
            keyframe.update_reference(self.reference)
            keyframe.update_preceding(self.preceding)

            self.mapping.add_keyframe(keyframe, measurements)
            if self.loop_closing is not None:
                self.loop_closing.add_keyframe(keyframe)
            self.preceding = keyframe

        self.set_tracking(False)

    def filter_points(self, frame):
        """Use the preceding and reference frame as seeds to extrat the local 
        3D map points.
        - Step 1: Use preceding and reference as seed to get a set of local 3D map points
        - Step 2: Remove the map points which cannot be viewed by the current frame with the initial estimate pose
        - Step 3: Add the 3d points in the preceding and reference frames into the list
        """
        # get local 3D map points
        local_mappoints = self.graph.get_local_map_v2(
            [self.preceding, self.reference])[0]

        # Check whether those map points are within the frustrum of current view point
        can_view = frame.can_view(local_mappoints)
        print('filter points:', len(local_mappoints), can_view.sum(),
              len(self.preceding.mappoints()), len(self.reference.mappoints()))

        checked = set()
        filtered = []
        for i in np.where(can_view)[0]:
            pt = local_mappoints[i]
            if pt.is_bad():
                continue
            pt.increase_projection_count()
            filtered.append(pt)
            checked.add(pt)

        # Add the 3D map points with in the preceding and reference frames
        # into the local map points
        for reference in set([self.preceding, self.reference]):
            for pt in reference.mappoints():  # neglect can_view test
                if pt in checked or pt.is_bad():
                    continue
                pt.increase_projection_count()
                filtered.append(pt)

        return filtered

    def should_be_keyframe(self, frame, measurements):
        if self.adding_keyframes_stopped():
            return False

        n_matches = len(measurements)
        n_matches_ref = len(self.reference.measurements())

        print('keyframe check:', n_matches, '   ', n_matches_ref)

        return ((n_matches / n_matches_ref) <
                self.params.min_tracked_points_ratio) or n_matches < 20

    def set_loop_correction(self, T):
        self.loop_correction = T

    def is_initialized(self):
        return self.status['initialized']

    def pause(self):
        self.status['paused'] = True

    def unpause(self):
        self.status['paused'] = False

    def is_paused(self):
        return self.status['paused']

    def is_tracking(self):
        return self.status['tracking']

    def set_tracking(self, status):
        self.status['tracking'] = status

    def stop_adding_keyframes(self):
        self.status['adding_keyframes_stopped'] = True

    def resume_adding_keyframes(self):
        self.status['adding_keyframes_stopped'] = False

    def adding_keyframes_stopped(self):
        return self.status['adding_keyframes_stopped']
Exemplo n.º 2
0
class SPTAM(object):
    def __init__(self, params):
        self.params = params

        self.tracker = Tracking(params)
        self.motion_model = MotionModel()

        self.graph = CovisibilityGraph()
        self.mapping = MappingThread(self.graph, params)

        self.loop_closing = LoopClosing(self, params)
        self.loop_correction = None
        
        self.reference = None        # reference keyframe
        self.preceding = None        # last keyframe
        self.current = None          # current frame
        self.status = defaultdict(bool)
        
    def stop(self):
        self.mapping.stop()
        if self.loop_closing is not None:
            self.loop_closing.stop()

    def initialize(self, frame):
        mappoints, measurements = frame.triangulate()
        assert len(mappoints) >= self.params.init_min_points, (
            'Not enough points to initialize map.')

        keyframe = frame.to_keyframe()
        keyframe.set_fixed(True)
        self.graph.add_keyframe(keyframe)
        self.mapping.add_measurements(keyframe, mappoints, measurements)
        if self.loop_closing is not None:
            self.loop_closing.add_keyframe(keyframe)

        self.reference = keyframe
        self.preceding = keyframe
        self.current = keyframe
        self.status['initialized'] = True

        self.motion_model.update_pose(
            frame.timestamp, frame.position, frame.orientation)

    def track(self, frame):
        while self.is_paused():
            time.sleep(1e-4)
        self.set_tracking(True)

        self.current = frame
        print('Tracking:', frame.idx, ' <- ', self.reference.id, self.reference.idx)

        predicted_pose, _ = self.motion_model.predict_pose(frame.timestamp)
        frame.update_pose(predicted_pose)

        if self.loop_closing is not None:
            if self.loop_correction is not None:
                estimated_pose = g2o.Isometry3d(
                    frame.orientation,
                    frame.position)
                estimated_pose = estimated_pose * self.loop_correction
                frame.update_pose(estimated_pose)
                self.motion_model.apply_correction(self.loop_correction)
                self.loop_correction = None

        local_mappoints = self.filter_points(frame)
        measurements = frame.match_mappoints(
            local_mappoints, Measurement.Source.TRACKING)

        print('measurements:', len(measurements), '   ', len(local_mappoints))

        tracked_map = set()
        for m in measurements:
            mappoint = m.mappoint
            mappoint.update_descriptor(m.get_descriptor())
            mappoint.increase_measurement_count()
            tracked_map.add(mappoint)
        
        try:
            self.reference = self.graph.get_reference_frame(tracked_map)

            pose = self.tracker.refine_pose(frame.pose, frame.cam, measurements)
            frame.update_pose(pose)
            self.motion_model.update_pose(
                frame.timestamp, pose.position(), pose.orientation())
            tracking_is_ok = True
        except:
            tracking_is_ok = False
            print('tracking failed!!!')

        if tracking_is_ok and self.should_be_keyframe(frame, measurements):
            print('new keyframe', frame.idx)
            keyframe = frame.to_keyframe()
            keyframe.update_reference(self.reference)
            keyframe.update_preceding(self.preceding)

            self.mapping.add_keyframe(keyframe, measurements)
            if self.loop_closing is not None:
                self.loop_closing.add_keyframe(keyframe)
            self.preceding = keyframe

        self.set_tracking(False)


    def filter_points(self, frame):
        local_mappoints = self.graph.get_local_map_v2(
            [self.preceding, self.reference])[0]

        can_view = frame.can_view(local_mappoints)
        print('filter points:', len(local_mappoints), can_view.sum(), 
            len(self.preceding.mappoints()),
            len(self.reference.mappoints()))
        
        checked = set()
        filtered = []
        for i in np.where(can_view)[0]:
            pt = local_mappoints[i]
            if pt.is_bad():
                continue
            pt.increase_projection_count()
            filtered.append(pt)
            checked.add(pt)

        for reference in set([self.preceding, self.reference]):
            for pt in reference.mappoints():  # neglect can_view test
                if pt in checked or pt.is_bad():
                    continue
                pt.increase_projection_count()
                filtered.append(pt)

        return filtered


    def should_be_keyframe(self, frame, measurements):
        if self.adding_keyframes_stopped():
            return False

        n_matches = len(measurements)
        n_matches_ref = len(self.reference.measurements())

        print('keyframe check:', n_matches, '   ', n_matches_ref)

        return ((n_matches / n_matches_ref) < 
            self.params.min_tracked_points_ratio) or n_matches < 20


    def set_loop_correction(self, T):
        self.loop_correction = T

    def is_initialized(self):
        return self.status['initialized']

    def pause(self):
        self.status['paused'] = True

    def unpause(self):
        self.status['paused'] = False

    def is_paused(self):
        return self.status['paused']

    def is_tracking(self):
        return self.status['tracking']

    def set_tracking(self, status):
        self.status['tracking'] = status

    def stop_adding_keyframes(self):
        self.status['adding_keyframes_stopped'] = True

    def resume_adding_keyframes(self):
        self.status['adding_keyframes_stopped'] = False

    def adding_keyframes_stopped(self):
        return self.status['adding_keyframes_stopped']
Exemplo n.º 3
0
class SPTAM(object):
    # Initialize the variables
    # Set connection to the three threads: Tracking, Local Mapping, Loop Closure
    # Tracking:         Detect the robots position and create a keyframe of comparison between 3D points and 2D map
    # Local Mapping:    Refine 3D and 2D comparison, minimize the reprojection error and remove bad points
    # Loop Closure:     Detect revisited places, estimate the relative transformation and correct the keyframes and map features
    def __init__(self, params):
        self.params = params
        # Set thread Tracking
        self.tracker = Tracking(params)
        self.motion_model = MotionModel()

        # Set thread Local Mapping
        self.graph = CovisibilityGraph()
        self.mapping = MappingThread(self.graph, params)

        # Set thread Loop Closure
        self.loop_closing = LoopClosing(self, params)
        self.loop_correction = None

        # Set the keyframe
        self.reference = None  # reference keyframe
        self.preceding = None  # last keyframe
        self.current = None  # current frame
        self.status = defaultdict(bool)

# Stop Loop Closure, if already executed

    def stop(self):
        self.mapping.stop()
        if self.loop_closing is not None:
            self.loop_closing.stop()

    def initialize(self, frame):
        # Create initial map
        mappoints, measurements = frame.triangulate()
        assert len(mappoints) >= self.params.init_min_points, (
            'Not enough points to initialize map.')

        # Create initial keyframe from initial map
        keyframe = frame.to_keyframe()
        keyframe.set_fixed(True)
        self.graph.add_keyframe(keyframe)
        self.mapping.add_measurements(keyframe, mappoints, measurements)
        if self.loop_closing is not None:
            self.loop_closing.add_keyframe(keyframe)

# Set reference, preceding and current keyframe to initial keyframe
        self.reference = keyframe
        self.preceding = keyframe
        self.current = keyframe
        self.status['initialized'] = True

        # Set initial pose of the robot
        self.motion_model.update_pose(frame.timestamp, frame.position,
                                      frame.orientation)

# THREAD - TRACKING

    def track(self, frame):
        # While robot is not moving, wait
        while self.is_paused():
            time.sleep(1e-4)
# When robot is moving, start tracking
        self.set_tracking(True)

        # STEP - FEATURE EXTRACTION: Capture the actual frame of the 3D world
        self.current = frame
        print('Tracking:', frame.idx, ' <- ', self.reference.id,
              self.reference.idx)

        # STEP - POSE PREDICTION: Predict the current position of the robot
        predicted_pose, _ = self.motion_model.predict_pose(frame.timestamp)
        frame.update_pose(predicted_pose)

        # While step Loop Closing, correct current pose
        if self.loop_closing is not None:
            if self.loop_correction is not None:

                # Use g2o for pose graph optimization
                estimated_pose = g2o.Isometry3d(frame.orientation,
                                                frame.position)

                # Create copy of the frame and execute correction
                estimated_pose = estimated_pose * self.loop_correction
                frame.update_pose(estimated_pose)
                self.motion_model.apply_correction(self.loop_correction)
                self.loop_correction = None

# STEP - MATCHING: Project map points and search for matches in the neighbourhood
        local_mappoints = self.filter_points(frame)
        measurements = frame.match_mappoints(local_mappoints,
                                             Measurement.Source.TRACKING)

        print('measurements:', len(measurements), '   ', len(local_mappoints))

        # Use BRISK descriptor to describe the features of the points
        # Compare the descriptors between map point and features
        tracked_map = set()
        for m in measurements:
            mappoint = m.mappoint
            mappoint.update_descriptor(m.get_descriptor())
            mappoint.increase_measurement_count()
            tracked_map.add(mappoint)

# STEP - POSE REFINEMENT: first get actual pose
        try:
            self.reference = self.graph.get_reference_frame(tracked_map)
            # Compare the previous camera pose with the relative motion in the current, local frame
            pose = self.tracker.refine_pose(frame.pose, frame.cam,
                                            measurements)
            # Update the pose
            frame.update_pose(pose)
            self.motion_model.update_pose(frame.timestamp, pose.position(),
                                          pose.orientation())
            tracking_is_ok = True
        except:
            tracking_is_ok = False
            print('tracking failed!!!')

# STEP - KEYFRAME SELECTION
        if tracking_is_ok and self.should_be_keyframe(frame, measurements):
            print('new keyframe', frame.idx)
            keyframe = frame.to_keyframe()
            keyframe.update_reference(self.reference)
            keyframe.update_preceding(self.preceding)

            # Set new keyframe
            self.mapping.add_keyframe(keyframe, measurements)
            # THREAD - LOOP CLOSURE
            # Add the keyframe to see if this place already has been visited
            if self.loop_closing is not None:
                self.loop_closing.add_keyframe(keyframe)
            self.preceding = keyframe

        self.set_tracking(False)

# Helping method for STEP - MATCHING

    def filter_points(self, frame):
        # Project the mappoints
        local_mappoints = self.graph.get_local_map_v2(
            [self.preceding, self.reference])[0]
        # Set current view points
        can_view = frame.can_view(local_mappoints)
        print('filter points:', len(local_mappoints), can_view.sum(),
              len(self.preceding.mappoints()), len(self.reference.mappoints()))

        checked = set()
        filtered = []
        # Check if points are in current frame, if yes increase count
        for i in np.where(can_view)[0]:
            pt = local_mappoints[i]
            if pt.is_bad():
                continue
            pt.increase_projection_count()
            filtered.append(pt)
            checked.add(pt)

# Get all references for this points
        for reference in set([self.preceding, self.reference]):
            for pt in reference.mappoints():  # neglect can_view test
                if pt in checked or pt.is_bad():
                    continue
                pt.increase_projection_count()
                filtered.append(pt)

# Return filtered map points that are in the current view
        return filtered

# Helping method for STEP - KEYFRAME SELECTION

    def should_be_keyframe(self, frame, measurements):
        if self.adding_keyframes_stopped():
            return False

# Set actual matches and the ones of the current keyframe
        n_matches = len(measurements)
        n_matches_ref = len(self.reference.measurements())

        print('keyframe check:', n_matches, '   ', n_matches_ref)

        # Set only as new keyframe, if minimum number of tracked points ratio is fullfilled
        # or if the current keyframe has less than 20 matches
        return ((n_matches / n_matches_ref) <
                self.params.min_tracked_points_ratio) or n_matches < 20

# THREAD - LOOP CLOSURE
# STEP - LOOP CORRECTION

    def set_loop_correction(self, T):
        self.loop_correction = T

# Other helping methods

    def is_initialized(self):
        return self.status['initialized']

    def pause(self):
        self.status['paused'] = True

    def unpause(self):
        self.status['paused'] = False

    def is_paused(self):
        return self.status['paused']

    def is_tracking(self):
        return self.status['tracking']

    def set_tracking(self, status):
        self.status['tracking'] = status

    def stop_adding_keyframes(self):
        self.status['adding_keyframes_stopped'] = True

    def resume_adding_keyframes(self):
        self.status['adding_keyframes_stopped'] = False

    def adding_keyframes_stopped(self):
        return self.status['adding_keyframes_stopped']