visual_odometry.cpp
Go to the documentation of this file.
00001 // visual odometry, combining ideas from a number of different visual odometry
00002 // algorithms
00003 //
00004 //   Andrew Howard, "Real-Time Stereo Visual Odometry for Autonomous Ground Vehicles,"
00005 //   International Conference on Robots and Systems (IROS), September 2008
00006 //
00007 //   David Nister
00008 //
00009 //   Sibley et al.
00010 //
00011 //   TODO
00012 //
00013 // Modifications include:
00014 //  - multi-resolution odometry using gaussian pyramids
00015 //
00016 //  - adaptive feature detector thresholding
00017 //
00018 //  - subpixel refinement of feature matches
00019 //
00020 //  TODO
00021 
00022 #include <stdio.h>
00023 #include <stdlib.h>
00024 #include <string.h>
00025 #include <assert.h>
00026 #include "tictoc.hpp"
00027 #include "visual_odometry.hpp"
00028 #include "initial_homography_estimation.hpp"
00029 #include "internal_utils.hpp"
00030 
00031 #include <iostream>
00032 #include <iomanip>
00033 
00034 //#define dbg(...) fprintf(stderr, __VA_ARGS__)
00035 #define dbg(...)
00036 #define dump(var) (std::cerr<<" "#var<<" =[\n"<< std::setprecision (12)<<var<<"]"<<std::endl)
00037 
00038 #ifndef MIN
00039 #define MIN(a,b) ((a)<(b) ? (a) : (b))
00040 #endif
00041 
00042 namespace fovis
00043 {
00044 
00045 static inline int clamp(int val, int minval, int maxval)
00046 {
00047   if(val > maxval) return maxval;
00048   if(val < minval) return minval;
00049   return val;
00050 }
00051 
00052 static void validateOptions(const VisualOdometryOptions& options,
00053     const VisualOdometryOptions& defaults);
00054 
00055 // =================== VisualOdometry ===================
00056 
00057 VisualOdometry::VisualOdometry(const Rectification* rectification,
00058                                const VisualOdometryOptions& options) :
00059     _options(options)
00060 {
00061   _ref_frame = NULL;
00062   _prev_frame = NULL;
00063   _cur_frame = NULL;
00064 
00065   _change_reference_frames = false;
00066 
00067   _ref_frame_change_threshold = 150;
00068 
00069   _frame_count = 0;
00070 
00071   // check for any unrecognized options
00072   const VisualOdometryOptions& defaults = getDefaultOptions();
00073   validateOptions(options, defaults);
00074 
00075   // extract options
00076   _feature_window_size = optionsGetIntOrFromDefault(_options, "feature-window-size", defaults);
00077   _num_pyramid_levels = optionsGetIntOrFromDefault(_options, "max-pyramid-level", defaults);
00078   _target_pixels_per_feature = optionsGetIntOrFromDefault(_options, "target-pixels-per-feature", defaults);
00079   _ref_frame_change_threshold = optionsGetIntOrFromDefault(_options, "ref-frame-change-threshold", defaults);
00080   _use_homography_initialization = optionsGetBoolOrFromDefault(_options, "use-homography-initialization", defaults);
00081   _fast_threshold = optionsGetIntOrFromDefault(_options, "fast-threshold", defaults);
00082   _use_adaptive_threshold = optionsGetBoolOrFromDefault(_options, "use-adaptive-threshold", defaults);
00083   _fast_threshold_adaptive_gain = optionsGetDoubleOrFromDefault(_options, "fast-threshold-adaptive-gain", defaults);
00084 
00085   _fast_threshold_min = 5;
00086   _fast_threshold_max = 70;
00087 
00088   _p = new VisualOdometryPriv();
00089 
00090   _p->motion_estimate.setIdentity();
00091   _p->motion_estimate_covariance.setIdentity(6, 6);
00092   _p->pose.setIdentity();
00093 
00094   _p->ref_to_prev_frame.setIdentity();
00095 
00096   _rectification = rectification;
00097 
00098   _ref_frame = new OdometryFrame(_rectification, options);
00099 
00100   _prev_frame = new OdometryFrame(_rectification, options);
00101 
00102   _cur_frame = new OdometryFrame(_rectification, options);
00103 
00104   _estimator = new MotionEstimator(_rectification, _options);
00105 }
00106 
00107 VisualOdometry::~VisualOdometry()
00108 {
00109   delete _estimator;
00110   delete _ref_frame;
00111   delete _prev_frame;
00112   delete _cur_frame;
00113   delete _p;
00114   _ref_frame = NULL;
00115   _prev_frame = NULL;
00116   _cur_frame = NULL;
00117   tictoc_print_stats(TICTOC_AVG);
00118 }
00119 
00120 // Estimate an initial rotation by finding the 2D homography that best aligns
00121 // a template image (the previous image) with the current image.  From this
00122 // homography, extract initial rotation parameters.
00123 Eigen::Quaterniond
00124 VisualOdometry::estimateInitialRotation(const OdometryFrame* prev, const OdometryFrame* cur,
00125     const Eigen::Isometry3d &init_motion_estimate)
00126 {
00127   _initial_rotation_pyramid_level = 4;
00128   int num_pyr_levels = prev->getNumLevels();
00129   int pyrLevel = MIN(num_pyr_levels-1,_initial_rotation_pyramid_level);
00130   const PyramidLevel * ref_level = prev->getLevel(pyrLevel);
00131   const PyramidLevel * target_level = cur->getLevel(pyrLevel);
00132 
00133   InitialHomographyEstimator rotation_estimator;
00134   rotation_estimator.setTemplateImage(ref_level->getGrayscaleImage(),
00135       ref_level->getWidth(), ref_level->getHeight(),
00136       ref_level->getGrayscaleImageStride(),
00137       _initial_rotation_pyramid_level - pyrLevel);
00138 
00139   rotation_estimator.setTestImage(target_level->getGrayscaleImage(),
00140       target_level->getWidth(), target_level->getHeight(),
00141       target_level->getGrayscaleImageStride(),
00142       _initial_rotation_pyramid_level - pyrLevel);
00143 
00144   Eigen::Matrix3f H = Eigen::Matrix3f::Identity();
00145   double finalRMS = 0;
00146   H = rotation_estimator.track(H,8, &finalRMS);
00147   double scale_factor = 1 << _initial_rotation_pyramid_level;
00148   Eigen::Matrix3f S = Eigen::Matrix3f::Identity() * scale_factor;
00149   S(2, 2) = 1;
00150   //scale H up to the full size image
00151   H = S * H * S.inverse();
00152   _p->initial_homography_est = H.cast<double>();
00153   //    dump(H);
00154 
00155   //TODO: use a better method to get the rotation from homography.
00156   Eigen::Vector3d rpy = Eigen::Vector3d::Zero();
00157   const CameraIntrinsicsParameters& input_camera = _rectification->getInputCameraParameters();
00158   rpy(0) = asin(H(1, 2) / input_camera.fx);
00159   rpy(1) = -asin(H(0, 2) / input_camera.fx);
00160   rpy(2) = -atan2(H(1, 0), H(0, 0));
00161 
00162   //    Eigen::Vector3d rpy_deg = rpy.transpose() * 180 / M_PI;
00163   //    dbg("irot:(% 6.3f % 6.3f % 6.3f)",rpy_deg(0),rpy_deg(1),rpy_deg(2));
00164 
00165   Eigen::Quaterniond q = _rpy_to_quat(rpy);
00166   return q;
00167 }
00168 
00169 void
00170 VisualOdometry::processFrame(const uint8_t* gray, DepthSource* depth_source)
00171 {
00172   if(_change_reference_frames) {
00173     // current frame becomes the reference frame
00174     std::swap(_ref_frame, _cur_frame);
00175     _p->ref_to_prev_frame.setIdentity();
00176   } else {
00177     // reference frame doesn't change, current frame is now previous
00178     std::swap(_prev_frame, _cur_frame);
00179   }
00180 
00181   bool changed_reference_frames = _change_reference_frames;
00182 
00183   // initialize a null motion estimate
00184   _p->motion_estimate.setIdentity();
00185   _change_reference_frames = false;
00186 
00187   // detect features in new frame
00188   _cur_frame->prepareFrame(gray, _fast_threshold, depth_source);
00189 
00190   const CameraIntrinsicsParameters& input_camera = _rectification->getInputCameraParameters();
00191   int width = input_camera.width;
00192   int height = input_camera.height;
00193 
00194   if (_use_adaptive_threshold) {
00195     // adaptively adjust feature detector threshold based on how many features
00196     // were detected.  Use proportional control
00197     int target_num_features = width * height / _target_pixels_per_feature;
00198     int err = _cur_frame->getNumDetectedKeypoints() - target_num_features;
00199     int thresh_adjustment = (int)((err) * _fast_threshold_adaptive_gain);
00200     _fast_threshold += thresh_adjustment;
00201     _fast_threshold = clamp(_fast_threshold, _fast_threshold_min, _fast_threshold_max);
00202   //  dbg("Next FAST threshold: %d (%d)\n", _fast_threshold, thresh_adjustment);
00203   }
00204 
00205   _frame_count++;
00206 
00207   // Only do the temporal matching if we have feature descriptors from the
00208   // previous frame.
00209   if(_frame_count < 2) {
00210     _change_reference_frames = true;
00211     return;
00212   }
00213 
00214   //TODO: should add option to pass in the initial estimate from external source
00215   tictoc("estimateInitialRotation");
00216   Eigen::Quaterniond init_rotation_est;
00217   if (_use_homography_initialization) {
00218     if (changed_reference_frames) {
00219       //TODO:this is ugly, but necessary due cuz of swapping stuff above :-/
00220       init_rotation_est = estimateInitialRotation(_ref_frame, _cur_frame);
00221     } else {
00222       init_rotation_est = estimateInitialRotation(_prev_frame, _cur_frame);
00223     }
00224   } else {
00225     init_rotation_est = Eigen::Quaterniond(1, 0, 0, 0); // identity quaternion.
00226   }
00227 
00228   tictoc("estimateInitialRotation");
00229   _p->initial_motion_estimate = _p->ref_to_prev_frame.inverse();
00230   _p->initial_motion_estimate.rotate(init_rotation_est);
00231 
00232   _p->initial_motion_cov.setIdentity();
00233   //TODO:estimate the covariance
00234 
00235   _estimator->estimateMotion(_ref_frame,
00236                              _cur_frame,
00237                              depth_source,
00238                              _p->initial_motion_estimate,
00239                              _p->initial_motion_cov);
00240 
00241   if(_estimator->isMotionEstimateValid()) {
00242     Eigen::Isometry3d to_reference = _estimator->getMotionEstimate();
00243     _p->motion_estimate = _p->ref_to_prev_frame * to_reference;
00244     Eigen::MatrixXd to_reference_cov = _estimator->getMotionEstimateCov();
00245     _p->motion_estimate_covariance = to_reference_cov; //TODO: this should probably be transformed as well
00246     _p->ref_to_prev_frame = to_reference.inverse();
00247     _p->pose = _p->pose * _p->motion_estimate;
00248   } else if(!changed_reference_frames) {
00249     // if the motion estimation failed, then try estimating against the
00250     // previous frame if it's not the reference frame.
00251     dbg("  Failed against reference frame, trying previous frame...\n");
00252     _p->initial_motion_estimate.setIdentity();
00253     _p->initial_motion_estimate.rotate(init_rotation_est);
00254     _p->initial_motion_cov.setIdentity();
00255     //TODO:covariance?
00256     _estimator->estimateMotion(_prev_frame,
00257                                _cur_frame,
00258                                depth_source,
00259                                _p->initial_motion_estimate,
00260                                _p->initial_motion_cov);
00261 
00262     if(_estimator->isMotionEstimateValid()) {
00263       dbg("   ok, matched against previous frame.\n");
00264       _p->motion_estimate = _estimator->getMotionEstimate();
00265       _p->motion_estimate_covariance = _estimator->getMotionEstimateCov();
00266       _p->pose = _p->pose * _p->motion_estimate;
00267       _change_reference_frames = true;
00268     }
00269 
00270   }
00271 
00272   // switch reference frames?
00273   if(!_estimator->isMotionEstimateValid() ||
00274       _estimator->getNumInliers() < _ref_frame_change_threshold) {
00275     _change_reference_frames = true;
00276   }
00277   if(_change_reference_frames)
00278     dbg("Changing reference frames\n");
00279 }
00280 
00281 void
00282 VisualOdometry::sanityCheck() const
00283 {
00284   _cur_frame->sanityCheck();
00285   _ref_frame->sanityCheck();
00286   _estimator->sanityCheck();
00287 }
00288 
00289 static std::string _toString(double v)
00290 {
00291   char buf[80];
00292   snprintf(buf, sizeof(buf), "%f", v);
00293   return std::string(buf);
00294 }
00295 
00296 VisualOdometryOptions
00297 VisualOdometry::getDefaultOptions()
00298 {
00299   VisualOdometryOptions r;
00300   //TODO split defaults?
00301 
00302   // VisualOdometry, OdometryFrame
00303   r["feature-window-size"] = "9";
00304   r["max-pyramid-level"] = "3";
00305   r["min-pyramid-level"] = "0";
00306   r["target-pixels-per-feature"] = "250";
00307   r["fast-threshold"] = "20";
00308   r["use-adaptive-threshold"] = "true";
00309   r["fast-threshold-adaptive-gain"] = _toString(0.005);
00310   r["use-homography-initialization"] = "true";
00311   r["ref-frame-change-threshold"] = "150";
00312 
00313   // OdometryFrame
00314   r["use-bucketing"] = "true";
00315   r["bucket-width"] = "80";
00316   r["bucket-height"] = "80";
00317   r["max-keypoints-per-bucket"] = "25";
00318   r["use-image-normalization"] = "false";
00319 
00320   // MotionEstimator
00321   r["inlier-max-reprojection-error"] = _toString(1.5);
00322   r["clique-inlier-threshold"] = _toString(0.1);
00323   r["min-features-for-estimate"] = "10";
00324   r["max-mean-reprojection-error"] = _toString(10.0);
00325   r["use-subpixel-refinement"] = "true";
00326   r["feature-search-window"] = "25";
00327   r["update-target-features-with-refined"] = "false";
00328 
00329   // StereoDepth
00330   r["stereo-require-mutual-match"] = "true";
00331   r["stereo-max-dist-epipolar-line"] = _toString(1.5);
00332   r["stereo-max-refinement-displacement"] = _toString(1.0);
00333   r["stereo-max-disparity"] = "128";
00334 
00335   return r;
00336 }
00337 
00338 static void
00339 validateOptions(const VisualOdometryOptions& options,
00340     const VisualOdometryOptions& defaults)
00341 {
00342   VisualOdometryOptions::const_iterator oiter = options.begin();
00343   VisualOdometryOptions::const_iterator oend = options.end();
00344   for(; oiter != oend; ++oiter) {
00345     if(defaults.find(oiter->first) == defaults.end()) {
00346       fprintf(stderr, "VisualOdometry WARNING: unrecognized option [%s]\n", oiter->first.c_str());
00347     }
00348   }
00349 
00350   // TODO check option value ranges
00351 }
00352 
00353 
00354 }


libfovis
Author(s): Albert Huang, Maurice Fallon
autogenerated on Thu Jun 6 2019 20:16:12