def __init__( self, num_features=kMinNumFeatureDefault, num_levels=3, # number of pyramid levels for detector scale_factor=1.2, # detection scale factor (if it can be set, otherwise it is automatically computed) detector_type=FeatureDetectorTypes.FAST, descriptor_type=FeatureDescriptorTypes.NONE, match_ratio_test=kRatioTest, tracker_type=FeatureTrackerTypes.LK): super().__init__(num_features=num_features, num_levels=num_levels, scale_factor=scale_factor, detector_type=detector_type, descriptor_type=descriptor_type, tracker_type=tracker_type) self.feature_manager = feature_manager_factory( num_features=num_features, num_levels=num_levels, scale_factor=scale_factor, detector_type=detector_type, descriptor_type=descriptor_type) #if num_levels < 3: # Printer.green('LkFeatureTracker: forcing at least 3 levels on LK pyr optic flow') # num_levels = 3 optic_flow_num_levels = max(kLkPyrOpticFlowNumLevelsMin, num_levels) Printer.green('LkFeatureTracker: num levels on LK pyr optic flow: ', optic_flow_num_levels) # we use LK pyr optic flow for matching self.lk_params = dict(winSize=(21, 21), maxLevel=optic_flow_num_levels, criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 30, 0.01))
def locally_optimize(self, kf_ref, verbose=False, rounds=10, abort_flag=g2o.Flag()): keyframes, points, ref_keyframes = self.local_map.update(kf_ref) print('local optimization window: ', sorted([kf.id for kf in keyframes])) print(' refs: ', sorted([kf.id for kf in ref_keyframes])) print(' #points: ', len(points)) #print(' points: ', sorted([p.id for p in points])) #err = optimizer_g2o.optimize(frames, points, None, False, verbose, rounds) err, ratio_bad_observations = optimizer_g2o.local_bundle_adjustment( keyframes, points, ref_keyframes, False, verbose, rounds, abort_flag=abort_flag, map_lock=self.update_lock) Printer.green('local optimization - perc bad observations: %.2f %%' % (ratio_bad_observations * 100)) return err
if display2d is not None: getchar() else: key_cv = cv2.waitKey( 0) & 0xFF # useful when drawing stuff for debugging if do_step and img_id > 1: # stop at each frame if display2d is not None: getchar() else: key_cv = cv2.waitKey(0) & 0xFF if key == 'd' or (key_cv == ord('d')): do_step = not do_step Printer.green('do step: ', do_step) if key == 'q' or (key_cv == ord('q')): if display2d is not None: display2d.quit() if viewer3D is not None: viewer3D.quit() if matched_points_plt is not None: matched_points_plt.quit() break if viewer3D is not None: is_paused = not viewer3D.is_paused() slam.quit()
def track(self, img, frame_id, timestamp=None): Printer.cyan('@tracking') time_start = time.time() # check image size is coherent with camera params print("img.shape: ", img.shape) print("camera ", self.camera.height, " x ", self.camera.width) assert img.shape[0:2] == (self.camera.height, self.camera.width) if timestamp is not None: print('timestamp: ', timestamp) self.timer_main_track.start() # build current frame self.timer_frame.start() f_cur = Frame(img, self.camera, timestamp=timestamp) self.f_cur = f_cur print("frame: ", f_cur.id) self.timer_frame.refresh() # reset indexes of matches self.idxs_ref = [] self.idxs_cur = [] if self.state == SlamState.NO_IMAGES_YET: # push first frame in the inizializer self.intializer.init(f_cur) self.state = SlamState.NOT_INITIALIZED return # EXIT (jump to second frame) if self.state == SlamState.NOT_INITIALIZED: # try to inizialize initializer_output, intializer_is_ok = self.intializer.initialize( f_cur, img) if intializer_is_ok: kf_ref = initializer_output.kf_ref kf_cur = initializer_output.kf_cur # add the two initialized frames in the map self.map.add_frame( kf_ref) # add first frame in map and update its frame id self.map.add_frame( kf_cur) # add second frame in map and update its frame id # add the two initialized frames as keyframes in the map self.map.add_keyframe( kf_ref) # add first keyframe in map and update its kid self.map.add_keyframe( kf_cur) # add second keyframe in map and update its kid kf_ref.init_observations() kf_cur.init_observations() # add points in map new_pts_count, _, _ = self.map.add_points( initializer_output.pts, None, kf_cur, kf_ref, initializer_output.idxs_cur, initializer_output.idxs_ref, img, do_check=False) Printer.green("map: initialized %d new points" % (new_pts_count)) # update covisibility graph connections kf_ref.update_connections() kf_cur.update_connections() # update tracking info self.f_cur = kf_cur self.f_cur.kf_ref = kf_ref self.kf_ref = kf_cur # set reference keyframe self.kf_last = kf_cur # set last added keyframe self.map.local_map.update(self.kf_ref) self.state = SlamState.OK self.update_tracking_history() self.motion_model.update_pose(kf_cur.timestamp, kf_cur.position, kf_cur.quaternion) self.motion_model.is_ok = False # after initialization you cannot use motion model for next frame pose prediction (time ids of initialized poses may not be consecutive) self.intializer.reset() if kUseDynamicDesDistanceTh: self.descriptor_distance_sigma = self.dyn_config.update_descriptor_stat( kf_ref, kf_cur, initializer_output.idxs_ref, initializer_output.idxs_cur) return # EXIT (jump to next frame) # get previous frame in map as reference f_ref = self.map.get_frame(-1) #f_ref_2 = self.map.get_frame(-2) self.f_ref = f_ref # add current frame f_cur to map self.map.add_frame(f_cur) self.f_cur.kf_ref = self.kf_ref # reset pose state flag self.pose_is_ok = False with self.map.update_lock: # check for map point replacements in previous frame f_ref (some points might have been replaced by local mapping during point fusion) self.f_ref.check_replaced_map_points() if kUseDynamicDesDistanceTh: print('descriptor_distance_sigma: ', self.descriptor_distance_sigma) self.local_mapping.descriptor_distance_sigma = self.descriptor_distance_sigma # udpdate (velocity) old motion model # c1=ref_ref, c2=ref, c3=cur; c=cur, r=ref #self.velocity = np.dot(f_ref.pose, inv_T(f_ref_2.pose)) # Tc2c1 = Tc2w * Twc1 (predicted Tcr) #self.predicted_pose = g2o.Isometry3d(np.dot(self.velocity, f_ref.pose)) # Tc3w = Tc2c1 * Tc2w (predicted Tcw) # set intial guess for current pose optimization if kUseMotionModel and self.motion_model.is_ok: print('using motion model for next pose prediction') # update f_ref pose according to its reference keyframe (the pose of the reference keyframe could be updated by local mapping) self.f_ref.update_pose( self.tracking_history.relative_frame_poses[-1] * self.f_ref.kf_ref.isometry3d) # predict pose by using motion model self.predicted_pose, _ = self.motion_model.predict_pose( timestamp, self.f_ref.position, self.f_ref.orientation) f_cur.update_pose(self.predicted_pose) else: print('setting f_cur.pose <-- f_ref.pose') # use reference frame pose as initial guess f_cur.update_pose(f_ref.pose) # track camera motion from f_ref to f_cur self.track_previous_frame(f_ref, f_cur) if not self.pose_is_ok: # if previous track didn't go well then track the camera motion from kf_ref to f_cur self.track_keyframe(self.kf_ref, f_cur) # now, having a better estimate of f_cur pose, we can find more map point matches: # find matches between {local map points} (points in the local map) and {unmatched keypoints of f_cur} if self.pose_is_ok: self.track_local_map(f_cur) # end block {with self.map.update_lock:} # TODO: add relocalization # HACK: since local mapping is not fast enough in python (and tracking is not in real-time) => give local mapping more time to process stuff self.wait_for_local_mapping( ) # N.B.: this must be outside the `with self.map.update_lock:` block with self.map.update_lock: # update slam state if self.pose_is_ok: self.state = SlamState.OK else: self.state = SlamState.LOST Printer.red('tracking failure') # update motion model state self.motion_model.is_ok = self.pose_is_ok if self.pose_is_ok: # if tracking was successful # update motion model self.motion_model.update_pose(timestamp, f_cur.position, f_cur.quaternion) f_cur.clean_vo_map_points() # do we need a new KeyFrame? need_new_kf = self.need_new_keyframe(f_cur) if need_new_kf: Printer.green('adding new KF with frame id % d: ' % (f_cur.id)) if kLogKFinfoToFile: self.kf_info_logger.info( 'adding new KF with frame id % d: ' % (f_cur.id)) kf_new = KeyFrame(f_cur, img) self.kf_last = kf_new self.kf_ref = kf_new f_cur.kf_ref = kf_new self.local_mapping.push_keyframe(kf_new) if not kLocalMappingOnSeparateThread: self.local_mapping.do_local_mapping() else: Printer.yellow('NOT KF') # From ORBSLAM2: # Clean outliers once keyframe generation has been managed: # we allow points with high innovation (considered outliers by the Huber Function) # pass to the new keyframe, so that bundle adjustment will finally decide # if they are outliers or not. We don't want next frame to estimate its position # with those points so we discard them in the frame. f_cur.clean_outlier_map_points() if self.f_cur.kf_ref is None: self.f_cur.kf_ref = self.kf_ref self.update_tracking_history( ) # must stay after having updated slam state (self.state) Printer.green("map: %d points, %d keyframes" % (self.map.num_points(), self.map.num_keyframes())) #self.update_history() self.timer_main_track.refresh() duration = time.time() - time_start print('tracking duration: ', duration)
def initialize(self, f_cur, img_cur): if self.num_failures > kNumOfFailuresAfterWichNumMinTriangulatedPointsIsHalved: self.num_min_triangulated_points = 0.5 * Parameters.kInitializerNumMinTriangulatedPoints self.num_failures = 0 Printer.orange( 'Initializer: halved min num triangulated features to ', self.num_min_triangulated_points) # prepare the output out = InitializerOutput() is_ok = False #print('num frames: ', len(self.frames)) # if too many frames have passed, move the current idx_f_ref forward # this is just one very simple policy that can be used if self.f_ref is not None: if f_cur.id - self.f_ref.id > kMaxIdDistBetweenIntializingFrames: self.f_ref = self.frames[-1] # take last frame in the buffer #self.idx_f_ref = len(self.frames)-1 # take last frame in the buffer #self.idx_f_ref = self.frames.index(self.f_ref) # since we are using a deque, the code of the previous commented line is not valid anymore #print('*** idx_f_ref:',self.idx_f_ref) #self.f_ref = self.frames[self.idx_f_ref] f_ref = self.f_ref #print('ref fid: ',self.f_ref.id,', curr fid: ', f_cur.id, ', idxs_ref: ', self.idxs_ref) # append current frame self.frames.append(f_cur) # if the current frames do no have enough features exit if len(f_ref.kps) < self.num_min_features or len( f_cur.kps) < self.num_min_features: Printer.red('Inializer: ko - not enough features!') self.num_failures += 1 return out, is_ok # find keypoint matches idxs_cur, idxs_ref = match_frames(f_cur, f_ref, kFeatureMatchRatioTestInitializer) print('|------------') #print('deque ids: ', [f.id for f in self.frames]) print('initializing frames ', f_cur.id, ', ', f_ref.id) print("# keypoint matches: ", len(idxs_cur)) Trc = self.estimatePose(f_ref.kpsn[idxs_ref], f_cur.kpsn[idxs_cur]) Tcr = inv_T(Trc) # Tcr w.r.t. ref frame f_ref.update_pose(np.eye(4)) f_cur.update_pose(Tcr) # remove outliers from keypoint matches by using the mask computed with inter frame pose estimation mask_idxs = (self.mask_match.ravel() == 1) self.num_inliers = sum(mask_idxs) print('# keypoint inliers: ', self.num_inliers) idx_cur_inliers = idxs_cur[mask_idxs] idx_ref_inliers = idxs_ref[mask_idxs] # create a temp map for initializing map = Map() f_ref.reset_points() f_cur.reset_points() #map.add_frame(f_ref) #map.add_frame(f_cur) kf_ref = KeyFrame(f_ref) kf_cur = KeyFrame(f_cur, img_cur) map.add_keyframe(kf_ref) map.add_keyframe(kf_cur) pts3d, mask_pts3d = triangulate_normalized_points( kf_cur.Tcw, kf_ref.Tcw, kf_cur.kpsn[idx_cur_inliers], kf_ref.kpsn[idx_ref_inliers]) new_pts_count, mask_points, _ = map.add_points( pts3d, mask_pts3d, kf_cur, kf_ref, idx_cur_inliers, idx_ref_inliers, img_cur, do_check=True, cos_max_parallax=Parameters.kCosMaxParallaxInitializer) print("# triangulated points: ", new_pts_count) if new_pts_count > self.num_min_triangulated_points: err = map.optimize(verbose=False, rounds=20, use_robust_kernel=True) print("init optimization error^2: %f" % err) num_map_points = len(map.points) print("# map points: %d" % num_map_points) is_ok = num_map_points > self.num_min_triangulated_points out.pts = pts3d[mask_points] out.kf_cur = kf_cur out.idxs_cur = idx_cur_inliers[mask_points] out.kf_ref = kf_ref out.idxs_ref = idx_ref_inliers[mask_points] # set scene median depth to equal desired_median_depth' desired_median_depth = Parameters.kInitializerDesiredMedianDepth median_depth = kf_cur.compute_points_median_depth(out.pts) depth_scale = desired_median_depth / median_depth print('forcing current median depth ', median_depth, ' to ', desired_median_depth) out.pts[:, :3] = out.pts[:, :3] * depth_scale # scale points tcw = kf_cur.tcw * depth_scale # scale initial baseline kf_cur.update_translation(tcw) map.delete() if is_ok: Printer.green('Inializer: ok!') else: self.num_failures += 1 Printer.red('Inializer: ko!') print('|------------') return out, is_ok