00001 /** 00002 \file Robots/LoBot/misc/LoScanMatch.C 00003 \brief Implementation of Lu and Milios's Iterative Dual Correspondence 00004 laser range finder scan matching. 00005 */ 00006 00007 // //////////////////////////////////////////////////////////////////// // 00008 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005 // 00009 // by the University of Southern California (USC) and the iLab at USC. // 00010 // See http://iLab.usc.edu for information about this project. // 00011 // //////////////////////////////////////////////////////////////////// // 00012 // Major portions of the iLab Neuromorphic Vision Toolkit are protected // 00013 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency // 00014 // in Visual Environments, and Applications'' by Christof Koch and // 00015 // Laurent Itti, California Institute of Technology, 2001 (patent // 00016 // pending; application number 09/912,225 filed July 23, 2001; see // 00017 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status). // 00018 // //////////////////////////////////////////////////////////////////// // 00019 // This file is part of the iLab Neuromorphic Vision C++ Toolkit. // 00020 // // 00021 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can // 00022 // redistribute it and/or modify it under the terms of the GNU General // 00023 // Public License as published by the Free Software Foundation; either // 00024 // version 2 of the License, or (at your option) any later version. // 00025 // // 00026 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope // 00027 // that it will be useful, but WITHOUT ANY WARRANTY; without even the // 00028 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // 00029 // PURPOSE. See the GNU General Public License for more details. // 00030 // // 00031 // You should have received a copy of the GNU General Public License // 00032 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write // 00033 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, // 00034 // Boston, MA 02111-1307 USA. // 00035 // //////////////////////////////////////////////////////////////////// // 00036 // 00037 // Primary maintainer for this file: mviswana usc edu 00038 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Robots/LoBot/slam/LoScanMatch.C $ 00039 // $Id: LoScanMatch.C 13572 2010-06-16 18:42:45Z mviswana $ 00040 // 00041 00042 //------------------------------ HEADERS -------------------------------- 00043 00044 // lobot headers 00045 #include "Robots/LoBot/slam/LoScanMatch.H" 00046 #include "Robots/LoBot/config/LoConfigHelpers.H" 00047 #include "Robots/LoBot/misc/LoExcept.H" 00048 #include "Robots/LoBot/util/LoMath.H" 00049 #include "Robots/LoBot/util/triple.hh" 00050 00051 // INVT utilities 00052 #include "Util/log.H" 00053 00054 // GSL headers 00055 #ifdef INVT_HAVE_LIBGSL 00056 #include <gsl/gsl_eigen.h> 00057 #include <gsl/gsl_matrix.h> 00058 #endif 00059 00060 // Standard C++ headers 00061 #include <vector> 00062 #include <algorithm> 00063 #include <iterator> 00064 #include <functional> 00065 #include <limits> 00066 00067 //----------------------------- NAMESPACE ------------------------------- 00068 00069 namespace lobot { 00070 00071 //-------------------------- KNOB TWIDDLING ----------------------------- 00072 00073 // DEVNOTE: For some reason, this anonymous namespace does not seem to 00074 // work properly. The parameters all seem to be zero. Not sure why. 00075 // Anyhoo, removing the anonymous namespace to get this to work... 00076 //namespace { 00077 00078 // Quick helper to return settings from scan_match section of config file 00079 template<typename T> 00080 inline T conf(const std::string& key, T default_value) 00081 { 00082 return get_conf<T>("scan_match", key, default_value) ; 00083 } 00084 00085 // Override for retrieving triples 00086 template<typename T> 00087 inline triple<T, T, T> 00088 conf(const std::string& key, const triple<T, T, T>& default_value) 00089 { 00090 return get_conf<T>("scan_match", key, default_value) ; 00091 } 00092 00093 /// This local class encapsulates various parameters that can be used to 00094 /// tweak different aspects of scan matching. 00095 class Params : public singleton<Params> { 00096 /// Since different laser range finders have varying capabilities and 00097 /// because it would be nice to be able to use this implementation of 00098 /// the ICP algorithm with devices other than just the Hokuyo attached 00099 /// to lobot, we require users to specify the relevant LRF 00100 /// characteristics with three numbers. 00101 /// 00102 /// The first of these numbers specifies the start angle for the range 00103 /// readings. Usually, this would be a negative number indicating that 00104 /// the laser range finder sweeps from the right to the left. 00105 /// 00106 /// The second number specifies the angular step between consecutive 00107 /// range readings. For lobot's Hokuyo, this is one degree; but for 00108 /// other LRF's, it could be 0.5 degrees. 00109 /// 00110 /// Finally, the third number in the above triumvirate specifies the 00111 /// total number of range readings. 00112 /// 00113 /// DEVNOTE: The third number should actually be an integer. But we 00114 /// declare it float for the sake of convenience (so as to avoid 00115 /// writing a special API for retrieving triple<float, float, int> 00116 /// from the config file). 00117 triple<float, float, float> m_lrf_specs ; 00118 00119 /// In each iteration of ICP, the algorithm searches a small angular 00120 /// range around each LRF reading to find the best correspondence 00121 /// between the current and previous LRF scans. This setting specifies 00122 /// the above-mentioned angular search range. 00123 int m_lrf_search_range ; 00124 00125 /// When finding correspondences between the current and previous 00126 /// scans, we reject any wherein the LRF range measurements exceed 00127 /// this threshold. 00128 float m_outlier_threshold ; 00129 00130 /// The ICP algorithm works by iteratively solving for the rotation 00131 /// and translation required to register a data shape with a model 00132 /// shape. Initially, the transformation is assumed to be zero. 00133 /// However, we can seed the initial transformation using the robot's 00134 /// odometry so that the algorithm has a better estimate to start off 00135 /// with. This can reduce the amount of work the algorithm has to do 00136 /// to register the previous LRF scan with the current one. 00137 /// 00138 /// By default, this flag is off so that the implementation follows 00139 /// the classic ICP description by Besl and McKay, viz., starting off 00140 /// with zero registration. 00141 bool m_seed_using_odometry ; 00142 00143 /// Since the ICP algorithm uses multiple iterations to converge to a 00144 /// solution, it could (potentially) go on forever. To prevent an 00145 /// infinite loop, we use this setting to specify the maximum number 00146 /// of iterations to use. 00147 int m_max_iterations ; 00148 00149 /// In general, the ICP algorithm should converge within a few 00150 /// iterations rather than going on for the maximum set above. In each 00151 /// iteration, the algorithm will compute a transformation and a total 00152 /// error between the two scans when that transformation is applied. 00153 /// The algorithm will terminate when the difference in this error for 00154 /// two consecutive iterations falls below the threshold specified by 00155 /// the following setting. 00156 float m_tau ; 00157 00158 /// Private constructor because this is a singleton. 00159 Params() ; 00160 friend class singleton<Params> ; 00161 00162 public: 00163 /// Accessing the various parameters 00164 //@{ 00165 static float lrf_start() {return instance().m_lrf_specs.first ;} 00166 static float lrf_res() {return instance().m_lrf_specs.second ;} 00167 static int lrf_num() {return int(instance().m_lrf_specs.third) ;} 00168 static int lrf_search_range() {return instance().m_lrf_search_range ;} 00169 static float outlier_threshold() {return instance().m_outlier_threshold ;} 00170 static bool seed_using_odometry(){return instance().m_seed_using_odometry;} 00171 static int max_iterations() {return instance().m_max_iterations ;} 00172 static float tau() {return instance().m_tau ;} 00173 //@} 00174 } ; 00175 00176 // Parameter initialization 00177 Params::Params() 00178 : m_lrf_specs(conf("lrf_specs", make_triple(-119.0f, 1.0f, 255.0f))), 00179 m_lrf_search_range(clamp(conf("lrf_search_range", 15), 0, 360)), 00180 m_outlier_threshold(clamp(conf("outlier_threshold", 100.0f), 00181 0.001f, 1000.0f)), 00182 m_seed_using_odometry(conf("seed_using_odometry", false)), 00183 m_max_iterations(clamp(conf("max_iterations", 30), 5, 100)), 00184 m_tau(clamp(conf("tau", 0.1f), 1e-6f, 1e+6f)) 00185 {} 00186 00187 //} // end of local anonymous namespace encapsulating above helpers 00188 00189 //--------------------- SCANS AND TRANSFORMATIONS ----------------------- 00190 00191 // Initializing a scan's range reading given its polar and Cartesian 00192 // coordinates. 00193 Scan::RangeReading::RangeReading(float r, float x, float y) 00194 : m_r(r), m_x(x), m_y(y) 00195 {} 00196 00197 // Quick function object to convert a polar range reading into 00198 // corresponding Cartesian coordinates. 00199 namespace { 00200 00201 class polar_to_cartesian : 00202 public std::binary_function<float, Pose, Scan::RangeReading> { 00203 typedef result_type R ; 00204 typedef first_argument_type A1 ; 00205 typedef second_argument_type A2 ; 00206 mutable float t ; 00207 public: 00208 polar_to_cartesian() ; 00209 R operator()(A1 r, const A2& pose) const ; 00210 } ; 00211 00212 polar_to_cartesian::polar_to_cartesian() 00213 : t(Params::lrf_start()) 00214 {} 00215 00216 polar_to_cartesian::R 00217 polar_to_cartesian:: 00218 operator()(polar_to_cartesian::A1 r, const polar_to_cartesian::A2& P) const 00219 { 00220 R range_reading(r, // polar 00221 P.x() + r * cos(P.theta() + t), // Cartesian x 00222 P.y() + r * sin(P.theta() + t)) ; // Cartesian y 00223 t += Params::lrf_res() ; 00224 return range_reading ; 00225 } 00226 00227 } // end of local anonymous namespace encapsulating above helper 00228 00229 // Initialize a scan given its pose and the range readings 00230 Scan::Scan(const Pose& P, const std::vector<int>& R) 00231 : m_pose(P) 00232 { 00233 m_range_readings.reserve(R.size()) ; 00234 std::transform(R.begin(), R.end(), std::back_inserter(m_range_readings), 00235 std::bind2nd(polar_to_cartesian(), P)) ; 00236 } 00237 00238 Scan::Scan(const Pose& P, const std::vector<float>& R) 00239 : m_pose(P) 00240 { 00241 m_range_readings.reserve(R.size()) ; 00242 std::transform(R.begin(), R.end(), std::back_inserter(m_range_readings), 00243 std::bind2nd(polar_to_cartesian(), P)) ; 00244 } 00245 00246 // Scan clean-up 00247 Scan::~Scan(){} 00248 00249 // A transformation encapsulates the rotation and translation required to 00250 // register one scan with another. 00251 Transformation::Transformation(float w, float Tx, float Ty) 00252 : m_w(w), m_Tx(Tx), m_Ty(Ty) 00253 {} 00254 00255 // Convenience constructor for initializing a transformation using a 00256 // triple. 00257 Transformation::Transformation(const triple<float, float, float>& T) 00258 : m_w(T.first), m_Tx(T.second), m_Ty(T.third) 00259 {} 00260 00261 // Quick helper to compute the pose difference between two Scans and 00262 // return the result via a Transformation. 00263 static Transformation operator-(const Scan& a, const Scan& b) 00264 { 00265 return Transformation(a.theta() - b.theta(), a.x() - b.x(), a.y() - b.y()) ; 00266 } 00267 00268 //----------------------- POINT CORRESPONDENCES ------------------------- 00269 00270 namespace { 00271 00272 // The Iterative Closest Point algorithm works by finding corresponding 00273 // points between two scans without knowing the transformation required 00274 // to convert one scan to the other and then using these correspondence 00275 // pairs to iteratively solve for the unknown transformation. 00276 // 00277 // This local class encapsulates the correspondence. It is simply a Nx1 00278 // matrix wherein C[i] = j implies that the i-th point in one scan 00279 // corresponds to the j-th point in the other. A value of -1 for C[i] 00280 // indicates that there is no correspondence for the i-th point. 00281 class Correspondence { 00282 // The Nx1 correspondence matrix is simply a one-dimensional array. 00283 std::vector<int> C ; 00284 00285 public: 00286 // On initialization, all correspondence pairs are set to -1. 00287 Correspondence(int N) ; 00288 00289 // Retrieving the i-th correspondence pair. 00290 int operator[](int i) const {return C[i] ;} 00291 00292 // Setting the i-th correspondence pair. 00293 int& operator[](int i) {return C[i] ;} 00294 } ; 00295 00296 Correspondence::Correspondence(int N) 00297 { 00298 C.reserve(N) ; 00299 std::fill_n(std::back_inserter(C), N, -1) ; 00300 } 00301 00302 // This function finds the model point closest to the i-th data point. 00303 // Only the LRF angular range specified by [min, max] is searched. 00304 int closest_point(const Scan& data, const Scan& model, int i, int min, int max) 00305 { 00306 int cp = -1 ; 00307 float min_d = std::numeric_limits<float>::max() ; 00308 00309 const Scan::RangeReading& D = data[i] ; 00310 for (int j = min; j <= max; ++j) 00311 { 00312 const Scan::RangeReading& M = model[j] ; 00313 if (M.r() < 0) // bad LRF reading 00314 continue ; 00315 00316 float d = sqrtf(sqr(D.x() - M.x()) + sqr(D.y() - M.y())) ; 00317 if (d < min_d) { 00318 cp = j ; 00319 min_d = d ; 00320 } 00321 } 00322 return cp ; 00323 } 00324 00325 // This function finds the correspondence pairs between the data and 00326 // model shapes. 00327 Correspondence find_correspondence(const Scan& data, const Scan& model) 00328 { 00329 const int N = Params::lrf_num() ; 00330 const int n = round(Params::lrf_search_range()/Params::lrf_res()) ; 00331 00332 Correspondence C(N) ; 00333 for (int i = 0; i < N; ++i) 00334 { 00335 const float r = data[i].r() ; 00336 if (r < 0) // bad LRF reading 00337 continue ; 00338 00339 int j = closest_point(data, model, 00340 i, std::max(i - n, 0), std::min(i + n, N - 1)) ; 00341 if (abs(r - model[j].r()) < Params::outlier_threshold()) 00342 C[i] = j ; 00343 //LERROR("C[%3d] = %3d", i, C[i]) ; 00344 } 00345 return C ; 00346 } 00347 00348 } // end of local anononymous namespace encapsulating above helpers 00349 00350 //----------------- QUATERNION BASED TRANSFORMATIONS -------------------- 00351 00352 namespace { 00353 00354 // Besl and McKay represent the rotation for registering a data shape 00355 // with a model shape using a quaternion. The translation vector is then 00356 // tacked on to the quaternion to produce a so-called registration 00357 // vector. The first four components of this vector are the 00358 // quaternion-based rotation and the last three components specify the 00359 // translation component of the transformation. 00360 // 00361 // We use this helper class to encapsulate the registration vector 00362 // described above. This class also holds the mean-square error 00363 // associated with the transformation between the data and model shapes. 00364 class ICPTransformation { 00365 // The 7-component registration vector: first four components are the 00366 // quaternion used to represent the rotation between data and model 00367 // shapes; the next three components are the translation vector 00368 // between the two shapes. 00369 float q[7] ; 00370 00371 // The mean-square error in the distances between the corresponding 00372 // points of the two shapes when the data shape is registered with the 00373 // model shape using the transformation specified by the registration 00374 // vector. 00375 float mserr ; 00376 00377 public: 00378 // This constructor initializes the registration vector so that the 00379 // rotation and translation are zero. 00380 ICPTransformation() ; 00381 00382 // This constructor initializes the registration vector and 00383 // mean-square error using the supplied parameters. 00384 ICPTransformation(float q[7], float mserr) ; 00385 00386 // This constructor initializes the registration vector using the 00387 // supplied z-axis rotation and 2D translation. Usually, these values 00388 // would be obtained from odometry. Thus, this constructor "seeds" the 00389 // ICP algorithm using odometry rather than zero. 00390 ICPTransformation(const Transformation&) ; 00391 00392 // Copy and assignment. 00393 ICPTransformation(const ICPTransformation&) ; 00394 ICPTransformation& operator=(const ICPTransformation&) ; 00395 00396 // Return the mean-square error associated with this registration. 00397 float error() const {return mserr ;} 00398 00399 // A helper function to transform an LRF scan by applying the 00400 // registration vector held by this object. The transformed scan is 00401 // returned via a new Scan object. 00402 Scan apply(const Scan&) const ; 00403 00404 // This function converts the quaternion-based rotation into the 00405 // corresponding Euler angle. It also extracts the translation 00406 // component of the transformation from the registration vector. 00407 // 00408 // NOTE: Since we are only interested in 2D for the LRF scan matching 00409 // procedure, the resulting transformation only needs one Euler angle 00410 // (corresponding to a rotation about the z-axis) and two translation 00411 // components (z-component is zero). 00412 Transformation convert() const ; 00413 } ; 00414 00415 // Initialize registration vector for zero rotation and zero translation. 00416 // This constructor is used at the start of the ICP algorithm. 00417 ICPTransformation::ICPTransformation() 00418 : mserr(-1) 00419 { 00420 q[0] = 1 ; 00421 q[1] = q[2] = q[3] = q[4] = q[5] = q[6] = 0 ; 00422 } 00423 00424 // Initialize registration vector using supplied parameters. This 00425 // constructor is used when we solve for the rotation and translation 00426 // between data and model shapes in each iteration of the ICP algorithm. 00427 ICPTransformation::ICPTransformation(float quat[7], float err) 00428 : mserr(err) 00429 { 00430 q[0] = quat[0] ; 00431 q[1] = quat[1] ; 00432 q[2] = quat[2] ; 00433 q[3] = quat[3] ; 00434 q[4] = quat[4] ; 00435 q[5] = quat[5] ; 00436 q[6] = quat[6] ; 00437 } 00438 00439 // Initialize registration vector using supplied rotation about z-axis 00440 // and 2D translation vector. 00441 ICPTransformation::ICPTransformation(const Transformation& t) 00442 : mserr(-1) 00443 { 00444 q[0] = cos(t.w()/2) ; 00445 q[1] = 0 ; 00446 q[2] = 0 ; 00447 q[3] = sin(t.w()/2) ; 00448 q[4] = t.Tx() ; 00449 q[5] = t.Ty() ; 00450 q[6] = 0 ; 00451 } 00452 00453 // Copy constructor 00454 ICPTransformation::ICPTransformation(const ICPTransformation& that) 00455 : mserr(that.mserr) 00456 { 00457 q[0] = that.q[0] ; 00458 q[1] = that.q[1] ; 00459 q[2] = that.q[2] ; 00460 q[3] = that.q[3] ; 00461 q[4] = that.q[4] ; 00462 q[5] = that.q[5] ; 00463 q[6] = that.q[6] ; 00464 } 00465 00466 // Assignment operator 00467 ICPTransformation& ICPTransformation::operator=(const ICPTransformation& that) 00468 { 00469 if (this != &that) { 00470 q[0] = that.q[0] ; 00471 q[1] = that.q[1] ; 00472 q[2] = that.q[2] ; 00473 q[3] = that.q[3] ; 00474 q[4] = that.q[4] ; 00475 q[5] = that.q[5] ; 00476 q[6] = that.q[6] ; 00477 mserr = that.mserr ; 00478 } 00479 return *this ; 00480 } 00481 00482 // Apply rotation and translation to each point in the given LRF scan and 00483 // return a new Scan as the result. 00484 Scan ICPTransformation::apply(const Scan& s) const 00485 { 00486 float R11 = sqr(q[0]) + sqr(q[1]) - sqr(q[2]) - sqr(q[3]) ; 00487 float R12 = 2 * (q[1]*q[2] - q[0]*q[3]) ; 00488 float R21 = 2 * (q[1]*q[2] + q[0]*q[3]) ; 00489 float R22 = sqr(q[0]) + sqr(q[2]) - sqr(q[1]) - sqr(q[3]) ; 00490 00491 const int N = Params::lrf_num() ; 00492 00493 Scan t(s) ; 00494 for (int i = 0; i < N; ++i) 00495 { 00496 Scan::RangeReading& r = t[i] ; 00497 r = Scan::RangeReading(r.r(), 00498 R11 * r.x() + R12 * r.y() + q[4], 00499 R21 * r.x() + R22 * r.y() + q[5]) ; 00500 } 00501 return t ; 00502 } 00503 00504 // Convert the quaternion to an Euler angle specifying rotation about 00505 // z-axis and extract the 2D component of the translation from the 00506 // registration vector. 00507 // 00508 // NOTE: See 00509 // http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles 00510 // for explanation of these formulae. 00511 Transformation ICPTransformation::convert() const 00512 { 00513 /* 00514 float x = atan(2*(q[0]*q[1] + q[2]*q[3]), 1 - 2*(sqr(q[1]) + sqr(q[2]))) ; 00515 float y = asin(2*(q[0]*q[2] - q[3]*q[1])) ; 00516 // */ 00517 float z = atan(2*(q[0]*q[3] + q[1]*q[2]), 1 - 2*(sqr(q[2]) + sqr(q[3]))) ; 00518 //LERROR("rotation = [%g %g %g]", x, y, z) ; 00519 00520 return Transformation(z, q[4], q[5]) ; 00521 } 00522 00523 } // end of local anonymous namespace encapsulating above helper class 00524 00525 //---------------------- ITERATIVE CLOSEST POINT ------------------------ 00526 00527 // This function solves for the rotation and translation between two 00528 // scans using the supplied point correspondences. Section III (pages 241 00529 // through 243) of the Besl-McKay paper describes the math implemented 00530 // here. 00531 // 00532 // NOTE: The paper works for 3D shapes. In our case, we only need 2D. 00533 // Therefore, the formulae used in this function are slightly different 00534 // from the math in the paper. Basically, the stuff required for 3D is 00535 // taken out and only the necessary 2D stuff is computed. 00536 #ifdef INVT_HAVE_LIBGSL 00537 00538 static ICPTransformation 00539 solve(const Scan& data, const Scan& model, const Correspondence& C) 00540 { 00541 const int N = Params::lrf_num() ; 00542 00543 // First, we compute the center of mass of the data and model shapes. 00544 // The CoM of the data shape is denoted mu_p and that of the model is 00545 // denoted mu_x. See equation (23) on page 243. 00546 // 00547 // In this loop, we can also compute the cross-covariance matrix, 00548 // which is referred to as sigma_px in the paper and denoted as E_px 00549 // here. This is equation (24) on page 243. 00550 // 00551 // NOTE: Since we only need 2D, we ignore the z-components of the CoM 00552 // points mu_p and mu_x. Furthermore, we ignore the third row and 00553 // third column of the cross-covariance matrix (they are all zeros). 00554 int n = 0 ; // total number of correspondences 00555 float mu_p_x = 0, mu_p_y = 0, mu_x_x = 0, mu_x_y = 0 ; 00556 float E_px_11 = 0, E_px_12 = 0, E_px_21 = 0, E_px_22 = 0 ; 00557 for (int i = 0; i < N; ++i) 00558 if (C[i] >= 0) // i-th data point corresponds to something on model shape 00559 { 00560 ++n ; 00561 00562 const Scan::RangeReading& D = data[i] ; 00563 const Scan::RangeReading& M = model[C[i]] ; 00564 00565 // Equation (23), page 243 00566 mu_p_x += D.x() ; 00567 mu_p_y += D.y() ; 00568 mu_x_x += M.x() ; 00569 mu_x_y += M.y() ; 00570 00571 // Equation (24), page 243 00572 E_px_11 += D.x() * M.x() ; 00573 E_px_12 += D.x() * M.y() ; 00574 E_px_21 += D.y() * M.x() ; 00575 E_px_22 += D.y() * M.y() ; 00576 } 00577 00578 // Now we have the sums. We need the averages for the two CoM's. This 00579 // will complete equation (23). 00580 mu_p_x /= n ; 00581 mu_p_y /= n ; 00582 mu_x_x /= n ; 00583 mu_x_y /= n ; 00584 00585 // For the cross-covariance matrix, we have the sum in the first term 00586 // of equation (24). Dividing by the total number of correspondences 00587 // will give us the first term. 00588 E_px_11 /= n ; 00589 E_px_12 /= n ; 00590 E_px_21 /= n ; 00591 E_px_22 /= n ; 00592 00593 // Finally, we subtract the CoM's to complete equation (24)... 00594 E_px_11 -= mu_p_x * mu_x_x ; 00595 E_px_12 -= mu_p_x * mu_x_y ; 00596 E_px_21 -= mu_p_y * mu_x_x ; 00597 E_px_22 -= mu_p_y * mu_x_y ; 00598 00599 // Now, we formulate the symmetric 4x4 matrix Q(E_px) as shown in 00600 // equation (25), page 243. 00601 // 00602 // NOTE: Since we're working in 2D, the four corners and the four 00603 // central elements of Q are the only relevant ones. The remaining 00604 // eight elements of the matrix are zero. 00605 // 00606 // NOTE 2: We have skipped a few things getting to this formulation. 00607 // First, we have to compute E_px - transpose(E_px). Then, we have to 00608 // use elements (2,3), (3,1) and (1,2) of the resulting matrix to 00609 // create a column vector denoted as Delta. Finally, trace(E_px), 00610 // Delta, transpose(Delta) and E_px + transpose(E_px) - trace(E_px) * I_3 00611 // are used to formulate Q. 00612 // 00613 // Taking advantage of the fact that we're dealing with 2D rather than 00614 // 3D, it turns out that only the third element of the column vector 00615 // Delta is significant (the other two are zero). Furthermore, as the 00616 // third row and third column of the cross-covariance matrix E_px 00617 // contain all zeros, the math for formulating Q can be done by hand. 00618 // 00619 // Putting it all together, gives us the following value for the 00620 // matrix Q. 00621 gsl_matrix* Q = gsl_matrix_alloc(4, 4) ; 00622 gsl_matrix_set_zero(Q) ; 00623 gsl_matrix_set(Q, 0, 0, E_px_11 + E_px_22) ; 00624 gsl_matrix_set(Q, 0, 3, E_px_12 - E_px_21) ; 00625 gsl_matrix_set(Q, 1, 1, E_px_11 - E_px_22) ; 00626 gsl_matrix_set(Q, 1, 2, E_px_12 + E_px_21) ; 00627 gsl_matrix_set(Q, 2, 1, E_px_21 + E_px_12) ; 00628 gsl_matrix_set(Q, 2, 2, E_px_22 - E_px_11) ; 00629 gsl_matrix_set(Q, 3, 0, E_px_12 - E_px_21) ; 00630 gsl_matrix_set(Q, 3, 3, -E_px_11 - E_px_22) ; 00631 00632 // Once we have Q(E_px), we can solve for the rotation as the 00633 // eigenvector corresponding to the maximum eigenvalue of the matrix 00634 // Q. See the paragraph between equations (25) and (26) on page 243 of 00635 // the Besl-McKay paper. 00636 gsl_eigen_symmv_workspace* w = gsl_eigen_symmv_alloc(4) ; 00637 gsl_vector* eigen_values = gsl_vector_alloc(4) ; 00638 gsl_matrix* eigen_vectors = gsl_matrix_alloc(4, 4) ; 00639 00640 gsl_eigen_symmv(Q, eigen_values, eigen_vectors, w) ; 00641 gsl_eigen_symmv_sort(eigen_values, eigen_vectors, GSL_EIGEN_SORT_VAL_DESC) ; 00642 00643 float q[7] ; 00644 q[0] = static_cast<float>(gsl_matrix_get(eigen_vectors, 0, 0)) ; 00645 q[1] = static_cast<float>(gsl_matrix_get(eigen_vectors, 1, 0)) ; 00646 q[2] = static_cast<float>(gsl_matrix_get(eigen_vectors, 2, 0)) ; 00647 q[3] = static_cast<float>(gsl_matrix_get(eigen_vectors, 3, 0)) ; 00648 00649 gsl_matrix_free(eigen_vectors) ; 00650 gsl_vector_free(eigen_values) ; 00651 gsl_eigen_symmv_free(w) ; 00652 gsl_matrix_free(Q) ; 00653 00654 // This is the rotation matrix associated with the above quaternion. 00655 // See equation (21) on page 243. 00656 // 00657 // NOTE: Since we're dealing with 2D, the third row and third column 00658 // of the rotation matrix R are of no consequence to us. Therefore, we 00659 // can get by with just the top-left four elements of R. 00660 float R11 = sqr(q[0]) + sqr(q[1]) - sqr(q[2]) - sqr(q[3]) ; 00661 float R12 = 2 * (q[1]*q[2] - q[0]*q[3]) ; 00662 float R21 = 2 * (q[1]*q[2] + q[0]*q[3]) ; 00663 float R22 = sqr(q[0]) + sqr(q[2]) - sqr(q[1]) - sqr(q[3]) ; 00664 00665 // To find the translation between the data shape and the model, we 00666 // rotate the CoM of the data shape and then subtract the result from 00667 // the CoM of the model shape. See equation (26) on page 243. 00668 // 00669 // NOTE: Due to our concern with only 2D, the z-component of the 00670 // translation vector is zero. 00671 q[4] = mu_x_x - (R11 * mu_p_x + R12 * mu_p_y) ; 00672 q[5] = mu_x_y - (R21 * mu_p_x + R22 * mu_p_y) ; 00673 q[6] = 0 ; 00674 00675 // Finally, we need to figure out the amount of error between the data 00676 // shape and the model corresponding to the transformation computed 00677 // above. We do that by applying the rotation and translation to each 00678 // point in the data shape and then finding the Euclidean distance to 00679 // the corresponding point on the model shape. The sum of the squares 00680 // of each of these distances will be the mean-square error that we're 00681 // interested in. 00682 // 00683 // This loop implements equation (22) on page 243 of the Besl-McKay 00684 // paper. As usual, we have omitted the z-component of the 00685 // transformation because, for Robolocust's scan matching, we're 00686 // working in two dimensions. 00687 float err = 0 ; 00688 for (int i = 0; i < N; ++i) 00689 if (C[i] >= 0) // i-th data point corresponds to something on model shape 00690 { 00691 const Scan::RangeReading& D = data[i] ; 00692 const Scan::RangeReading& M = model[C[i]] ; 00693 00694 err += sqr(M.x() - (R11 * D.x() + R12 * D.y()) - q[4]) 00695 + sqr(M.y() - (R21 * D.x() + R22 * D.y()) - q[5]) ; 00696 } 00697 err /= n ; 00698 00699 /* 00700 LERROR("q = [%8.3f %8.3f %8.3f %8.3f] [%8.3f %8.3f %8.3f]", 00701 q[0], q[1], q[2], q[3], q[4], q[5], q[6]) ; 00702 // */ 00703 00704 // That's it: we have the quaternion-based rotation and translation 00705 // plus the mean-square error associated with this transformation. We 00706 // bundle these two things together and return the result for use in 00707 // step b of the ICP algorithm (see section A, page 244 of the 00708 // Besl-McKay paper). 00709 return ICPTransformation(q, err) ; 00710 } 00711 00712 #else // GNU Scientific Library not available 00713 00714 // Can't do eigenvalue and eigenvector computations 00715 static ICPTransformation solve(const Scan&, const Scan&, const Correspondence&) 00716 { 00717 throw missing_libs(MISSING_LIBGSL) ; 00718 } 00719 00720 #endif // INVT_HAVE_LIBGSL 00721 00722 // This function implements the Iterative Closest Point algorithm 00723 // described by Besl and McKay. The goal for Robolocust is to match two 00724 // laser range finder scans and return the transformation required to 00725 // properly register the previous scan with reference to the current one. 00726 // 00727 // In terms of the paper's terminology: we consider the current scan to 00728 // be the model shape and the previous one to be the data shape; we want 00729 // to register the data (previous scan) to the model (current scan). The 00730 // resulting transformation required to go from the previous scan to the 00731 // current one should then be able to correct for odometric drift/errors. 00732 Transformation match_scans(const Scan& curr, const Scan& prev) 00733 { 00734 // ICP init: the transformation required to register the data shape to 00735 // the model shape starts off with zero rotation and zero translation. 00736 // Additionally, the intermediate data shape that converges onto the 00737 // model shape as the algorithm progresses is intialized using the 00738 // original data shape. 00739 // 00740 // NOTE: In the paper, the authors use P_k and P_[k+1] to denote the 00741 // fact that the "intermediate" data shape changes with each iteration 00742 // of the algorithm. Here, we simply use the variable P and omit the 00743 // subscripts. 00744 ICPTransformation q ; 00745 Scan P = prev ; 00746 00747 // Optionally, instead of zeros, we can use raw odometry to "seed" the 00748 // initial transformation. 00749 if (Params::seed_using_odometry()) { 00750 q = curr - prev ; 00751 P = q.apply(prev) ; 00752 } 00753 00754 // We will need to keep track of the mean square errors in the 00755 // transformation computed in each iteration of the ICP algorithm so 00756 // that we know when we can terminate (when the error drops below some 00757 // preset threshold). 00758 float prev_error = std::numeric_limits<float>::max() ; 00759 00760 // ICP loop: now, we solve for the rotation and translation required 00761 // to align the data shape (prev scan) to the model shape (current 00762 // scan) until the mean square error associated with transformation 00763 // drops below some preset threshold. To ensure that this loop doesn't 00764 // go on forever (in case the algorithm never converges), we cap the 00765 // maximum number of iterations. 00766 for (int i = 1; i <= Params::max_iterations(); ++i) 00767 { 00768 // Steps a and b (section A, page 244): find the correspondence 00769 // pairs between the data and model shapes and then use these 00770 // correspondences to compute the appropriate rotation and 00771 // translation required for the registration. 00772 q = solve(prev, curr, find_correspondence(P, curr)) ; 00773 00774 // Step c (section A, page 244): apply the above registration to 00775 // the data shape to get the new intermediate, registered data 00776 // shape, viz., P_[k+1]. 00777 P = q.apply(prev) ; 00778 00779 // Step d (section A, page 244): terminate the iteration when the 00780 // change in the mean-square error falls below a preset threshold 00781 // tau. 00782 float error = abs(prev_error - q.error()) ; 00783 /* 00784 Transformation t = q.convert() ; 00785 LERROR("iter %3d: [%8.3f%9.3f%7.1f] [%9.3e %10.3e]", 00786 i, t.Tx(), t.Ty(), t.w(), q.error(), error) ; 00787 // */ 00788 if (error < Params::tau()) 00789 break ; 00790 00791 // Setup for next iteration 00792 prev_error = q.error() ; 00793 } 00794 00795 // Either ICP converged within the max allowable iterations or it 00796 // didn't. Either ways, return the latest transformation. 00797 return q.convert() ; 00798 } 00799 00800 //----------------------------------------------------------------------- 00801 00802 } // end of namespace encapsulating this file's definitions 00803 00804 /* So things look consistent in everyone's emacs... */ 00805 /* Local Variables: */ 00806 /* indent-tabs-mode: nil */ 00807 /* End: */