|
Point Cloud Library (PCL)
1.4.0
|
00001 /* 00002 * Software License Agreement (BSD License) 00003 * 00004 * Point Cloud Library (PCL) - www.pointclouds.org 00005 * Copyright (c) 2011, Willow Garage, Inc. 00006 * 00007 * All rights reserved. 00008 * 00009 * Redistribution and use in source and binary forms, with or without 00010 * modification, are permitted provided that the following conditions 00011 * are met: 00012 * 00013 * * Redistributions of source code must retain the above copyright 00014 * notice, this list of conditions and the following disclaimer. 00015 * * Redistributions in binary form must reproduce the above 00016 * copyright notice, this list of conditions and the following 00017 * disclaimer in the documentation and/or other materials provided 00018 * with the distribution. 00019 * * Neither the name of Willow Garage, Inc. nor the names of its 00020 * contributors may be used to endorse or promote products derived 00021 * from this software without specific prior written permission. 00022 * 00023 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 00024 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 00025 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 00026 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 00027 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 00028 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 00029 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 00030 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 00031 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 00032 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 00033 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 00034 * POSSIBILITY OF SUCH DAMAGE. 00035 * 00036 */ 00037 #ifndef PCL_NDT_IMPL_H_ 00038 #define PCL_NDT_IMPL_H_ 00039 #include <cmath> 00040 00041 #include <boost/noncopyable.hpp> 00042 #include <boost/make_shared.hpp> 00043 00044 #include <Eigen/Dense> 00045 00046 namespace pcl 00047 { 00048 namespace ndt 00049 { 00054 template<unsigned N=3, typename T=double> 00055 struct ValueAndDerivatives 00056 { 00057 Eigen::Matrix<T, N, N> hessian; 00058 Eigen::Matrix<T, N, 1> grad; 00059 T value; 00060 00061 static ValueAndDerivatives<N,T> 00062 Zero () 00063 { 00064 ValueAndDerivatives<N,T> r; 00065 r.hessian = Eigen::Matrix<T, N, N>::Zero (); 00066 r.grad = Eigen::Matrix<T, N, 1>::Zero (); 00067 r.value = 0; 00068 return r; 00069 } 00070 00071 ValueAndDerivatives<N,T>& 00072 operator+= (ValueAndDerivatives<N,T> const& r) 00073 { 00074 hessian += r.hessian; 00075 grad += r.grad; 00076 value += r.value; 00077 return *this; 00078 } 00079 }; 00080 00092 template <typename PointT> 00093 class NormalDist 00094 { 00095 typedef pcl::PointCloud<PointT> PointCloud; 00096 00097 public: 00098 NormalDist () 00099 : min_n_ (3), n_ (0), pt_indices_ (), mean_ (), covar_inv_ () 00100 { 00101 } 00102 00106 void 00107 addIdx (size_t i) 00108 { 00109 pt_indices_.push_back (i); 00110 } 00111 00116 void 00117 estimateParams (const PointCloud& cloud, double min_covar_eigvalue_mult = 0.001) 00118 { 00119 Eigen::Vector2d sx = Eigen::Vector2d::Zero (); 00120 Eigen::Matrix2d sxx = Eigen::Matrix2d::Zero (); 00121 00122 std::vector<size_t>::const_iterator i; 00123 for (i = pt_indices_.begin (); i != pt_indices_.end (); i++) 00124 { 00125 Eigen::Vector2d p (cloud[*i]. x, cloud[*i]. y); 00126 sx += p; 00127 sxx += p * p.transpose (); 00128 } 00129 00130 n_ = pt_indices_.size (); 00131 00132 if (n_ >= min_n_) 00133 { 00134 mean_ = sx / n_; 00135 // Using maximum likelihood estimation as in the original paper 00136 Eigen::Matrix2d covar = (sxx - 2 * (sx * mean_.transpose ())) / n_ + mean_ * mean_.transpose (); 00137 00138 Eigen::SelfAdjointEigenSolver<Eigen::Matrix2d> solver (covar); 00139 if (solver.eigenvalues ()[0] < min_covar_eigvalue_mult * solver.eigenvalues ()[1]) 00140 { 00141 PCL_DEBUG ("NDT normal fit: adjusting eigenvalue %f\n", solver.eigenvalues ()[0]); 00142 Eigen::Matrix2d l = solver.eigenvalues ().asDiagonal (); 00143 Eigen::Matrix2d q = solver.eigenvectors (); 00144 // set minimum smallest eigenvalue: 00145 l (0,0) = l (1,1) * min_covar_eigvalue_mult; 00146 covar = q * l * q.transpose (); 00147 } 00148 covar_inv_ = covar.inverse (); 00149 } 00150 00151 pt_indices_.clear (); 00152 } 00153 00161 ValueAndDerivatives<3,double> 00162 test (const PointT& transformed_pt, const double& cos_theta, const double& sin_theta) const 00163 { 00164 if (n_ < min_n_) 00165 return ValueAndDerivatives<3,double>::Zero (); 00166 00167 ValueAndDerivatives<3,double> r; 00168 const double x = transformed_pt.x; 00169 const double y = transformed_pt.y; 00170 const Eigen::Vector2d p_xy (transformed_pt.x, transformed_pt.y); 00171 const Eigen::Vector2d q = p_xy - mean_; 00172 const Eigen::RowVector2d qt_cvi = q.transpose () * covar_inv_; 00173 const double exp_qt_cvi_q = std::exp (-0.5 * double (qt_cvi * q)); 00174 r.value = -exp_qt_cvi_q; 00175 00176 Eigen::Matrix<double, 2, 3> jacobian; 00177 jacobian << 00178 1, 0, -(x * sin_theta + y*cos_theta), 00179 0, 1, x * cos_theta - y*sin_theta; 00180 00181 for (size_t i = 0; i < 3; i++) 00182 r.grad[i] = double (qt_cvi * jacobian.col (i)) * exp_qt_cvi_q; 00183 00184 // second derivative only for i == j == 2: 00185 const Eigen::Vector2d d2q_didj ( 00186 y * sin_theta - x*cos_theta, 00187 -(x * sin_theta + y*cos_theta) 00188 ); 00189 00190 for (size_t i = 0; i < 3; i++) 00191 for (size_t j = 0; j < 3; j++) 00192 r.hessian (i,j) = -exp_qt_cvi_q * ( 00193 double (-qt_cvi*jacobian.col (i)) * double (-qt_cvi*jacobian.col (j)) + 00194 (-qt_cvi * ((i==2 && j==2)? d2q_didj : Eigen::Vector2d::Zero ())) + 00195 (-jacobian.col (j).transpose () * covar_inv_ * jacobian.col (i)) 00196 ); 00197 00198 return r; 00199 } 00200 00201 protected: 00202 const size_t min_n_; 00203 00204 size_t n_; 00205 std::vector<size_t> pt_indices_; 00206 Eigen::Vector2d mean_; 00207 Eigen::Matrix2d covar_inv_; 00208 }; 00209 00214 template <typename PointT> 00215 class NDTSingleGrid: public boost::noncopyable 00216 { 00217 typedef typename pcl::PointCloud<PointT> PointCloud; 00218 typedef typename pcl::PointCloud<PointT>::ConstPtr PointCloudConstPtr; 00219 typedef typename pcl::ndt::NormalDist<PointT> NormalDist; 00220 00221 public: 00222 NDTSingleGrid (PointCloudConstPtr cloud, 00223 const Eigen::Vector2f& about, 00224 const Eigen::Vector2f& extent, 00225 const Eigen::Vector2f& step) 00226 : min_ (about - extent), max_ (min_ + 2*extent), step_ (step), 00227 cells_ ((max_[0]-min_[0]) / step_[0], 00228 (max_[1]-min_[1]) / step_[1]), 00229 normal_distributions_ (cells_[0], cells_[1]) 00230 { 00231 // sort through all points, assigning them to distributions: 00232 NormalDist* n; 00233 size_t used_points = 0; 00234 for (size_t i = 0; i < cloud->size (); i++) 00235 if ((n = normalDistForPoint (cloud->at (i)))) 00236 { 00237 n->addIdx (i); 00238 used_points++; 00239 } 00240 00241 PCL_DEBUG ("NDT single grid %dx%d using %d/%d points\n", cells_[0], cells_[1], used_points, cloud->size ()); 00242 00243 // then bake the distributions such that they approximate the 00244 // points (and throw away memory of the points) 00245 for (int x = 0; x < cells_[0]; x++) 00246 for (int y = 0; y < cells_[1]; y++) 00247 normal_distributions_.coeffRef (x,y).estimateParams (*cloud); 00248 } 00249 00255 ValueAndDerivatives<3,double> 00256 test (const PointT& transformed_pt, const double& cos_theta, const double& sin_theta) const 00257 { 00258 const NormalDist* n = normalDistForPoint (transformed_pt); 00259 // index is in grid, return score from the normal distribution from 00260 // the correct part of the grid: 00261 if (n) 00262 return n->test (transformed_pt, cos_theta, sin_theta); 00263 else 00264 return ValueAndDerivatives<3,double>::Zero (); 00265 } 00266 00267 protected: 00271 NormalDist* 00272 normalDistForPoint (PointT const& p) const 00273 { 00274 // this would be neater in 3d... 00275 Eigen::Vector2f idxf; 00276 for (size_t i = 0; i < 2; i++) 00277 idxf[i] = (p.getVector3fMap ()[i] - min_[i]) / step_[i]; 00278 Eigen::Vector2i idxi = idxf.cast<int> (); 00279 for (size_t i = 0; i < 2; i++) 00280 if (idxi[i] >= cells_[i] || idxi[i] < 0) 00281 return NULL; 00282 // const cast to avoid duplicating this function in const and 00283 // non-const variants... 00284 return const_cast<NormalDist*> (&normal_distributions_.coeffRef (idxi[0], idxi[1])); 00285 } 00286 00287 Eigen::Vector2f min_; 00288 Eigen::Vector2f max_; 00289 Eigen::Vector2f step_; 00290 Eigen::Vector2i cells_; 00291 00292 Eigen::Matrix<NormalDist, Eigen::Dynamic, Eigen::Dynamic> normal_distributions_; 00293 }; 00294 00301 template <typename PointT> 00302 class NDT: public boost::noncopyable 00303 { 00304 typedef typename pcl::PointCloud<PointT> PointCloud; 00305 typedef typename pcl::PointCloud<PointT>::ConstPtr PointCloudConstPtr; 00306 typedef NDTSingleGrid<PointT> SingleGrid; 00307 00308 public: 00315 NDT (PointCloudConstPtr cloud, 00316 const Eigen::Vector2f& about, 00317 const Eigen::Vector2f& extent, 00318 const Eigen::Vector2f& step) 00319 { 00320 Eigen::Vector2f dx (step[0]/2, 0); 00321 Eigen::Vector2f dy (0, step[1]/2); 00322 single_grids_[0] = boost::make_shared<SingleGrid> (cloud, about, extent, step); 00323 single_grids_[1] = boost::make_shared<SingleGrid> (cloud, about +dx, extent, step); 00324 single_grids_[2] = boost::make_shared<SingleGrid> (cloud, about +dy, extent, step); 00325 single_grids_[3] = boost::make_shared<SingleGrid> (cloud, about +dy+dy, extent, step); 00326 } 00327 00333 ValueAndDerivatives<3,double> 00334 test (const PointT& transformed_pt, const double& cos_theta, const double& sin_theta) const 00335 { 00336 ValueAndDerivatives<3,double> r = ValueAndDerivatives<3,double>::Zero (); 00337 for (size_t i = 0; i < 4; i++) 00338 r += single_grids_[i]->test (transformed_pt, cos_theta, sin_theta); 00339 return r; 00340 } 00341 00342 protected: 00343 boost::shared_ptr<SingleGrid> single_grids_[4]; 00344 }; 00345 00346 } // namespace ndt 00347 } // namespace pcl 00348 00349 00350 namespace Eigen 00351 { 00352 /* This NumTraits specialisation is necessary because NormalDist is used as 00353 * the element type of an Eigen Matrix. 00354 */ 00355 template<typename PointT> struct NumTraits<pcl::ndt::NormalDist<PointT> > 00356 { 00357 typedef double Real; 00358 static Real dummy_precision () { return 1.0; } 00359 enum { 00360 IsComplex = 0, 00361 IsInteger = 0, 00362 IsSigned = 0, 00363 RequireInitialization = 1, 00364 ReadCost = 1, 00365 AddCost = 1, 00366 MulCost = 1 00367 }; 00368 }; 00369 } 00370 00372 template <typename PointSource, typename PointTarget> void 00373 pcl::NormalDistributionsTransform<PointSource, PointTarget>::computeTransformation (PointCloudSource &output, const Eigen::Matrix4f &guess) 00374 { 00375 PointCloudSource intm_cloud = output; 00376 00377 if (guess != Eigen::Matrix4f::Identity ()) 00378 { 00379 transformation_ = guess; 00380 transformPointCloud (output, intm_cloud, transformation_); 00381 } 00382 00383 // build Normal Distribution Transform of target cloud: 00384 ndt::NDT<PointTarget> target_ndt (target_, grid_centre_, grid_extent_, grid_step_); 00385 00386 // can't seem to use .block<> () member function on transformation_ 00387 // directly... gcc bug? 00388 Eigen::Matrix4f& transformation = transformation_; 00389 00390 00391 // work with x translation, y translation and z rotation: extending to 3D 00392 // would be some tricky maths, but not impossible. 00393 const Eigen::Matrix3f initial_rot (transformation.block<3,3> (0,0)); 00394 const Eigen::Vector3f rot_x (initial_rot*Eigen::Vector3f::UnitX ()); 00395 const double z_rotation = std::atan2 (rot_x[1], rot_x[0]); 00396 00397 Eigen::Vector3d xytheta_transformation ( 00398 transformation (0,3), 00399 transformation (1,3), 00400 z_rotation 00401 ); 00402 00403 while (!converged_) 00404 { 00405 const double cos_theta = std::cos (xytheta_transformation[2]); 00406 const double sin_theta = std::sin (xytheta_transformation[2]); 00407 previous_transformation_ = transformation; 00408 00409 ndt::ValueAndDerivatives<3, double> score = ndt::ValueAndDerivatives<3, double>::Zero (); 00410 for (size_t i = 0; i < intm_cloud.size (); i++) 00411 score += target_ndt.test (intm_cloud[i], cos_theta, sin_theta); 00412 00413 PCL_DEBUG ("NDT score %f (x=%f,y=%f,r=%f)\n", 00414 float (score.value), xytheta_transformation[0], xytheta_transformation[1], xytheta_transformation[2] 00415 ); 00416 00417 //std::cout << "grad:\n" << score.grad << std::endl; 00418 //std::cout << "hessian:\n" << score.hessian << std::endl; 00419 00420 if (score.value != 0) 00421 { 00422 // test for positive definiteness, and adjust to ensure it if necessary: 00423 Eigen::EigenSolver<Eigen::Matrix3d> solver; 00424 solver.compute (score.hessian, false); 00425 double min_eigenvalue = 0; 00426 for (int i = 0; i <3; i++) 00427 if (solver.eigenvalues ()[i].real () < min_eigenvalue) 00428 min_eigenvalue = solver.eigenvalues ()[i].real (); 00429 00430 // ensure "safe" positive definiteness: this is a detail missing 00431 // from the original paper 00432 if (min_eigenvalue < 0) 00433 { 00434 double lambda = 1.1 * min_eigenvalue - 1; 00435 score.hessian += Eigen::Vector3d (-lambda, -lambda, -lambda).asDiagonal (); 00436 solver.compute (score.hessian, false); 00437 PCL_DEBUG ("adjust hessian: %f: new eigenvalues:%f %f %f\n", 00438 float (lambda), 00439 solver.eigenvalues ()[0].real (), 00440 solver.eigenvalues ()[1].real (), 00441 solver.eigenvalues ()[2].real () 00442 ); 00443 } 00444 assert (solver.eigenvalues ()[0].real () >= 0 && 00445 solver.eigenvalues ()[1].real () >= 0 && 00446 solver.eigenvalues ()[2].real () >= 0); 00447 00448 Eigen::Vector3d delta_transformation = -score.hessian.inverse () * score.grad; 00449 Eigen::Vector3d new_transformation = xytheta_transformation + newton_lambda_.cwiseProduct (delta_transformation); 00450 00451 xytheta_transformation = new_transformation; 00452 00453 // update transformation matrix from x, y, theta: 00454 transformation.block<3,3> (0,0) = Eigen::Matrix3f (Eigen::AngleAxisf (xytheta_transformation[2], Eigen::Vector3f::UnitZ ())); 00455 transformation.block<3,1> (0,3) = Eigen::Vector3f (xytheta_transformation[0], xytheta_transformation[1], 0); 00456 00457 //std::cout << "new transformation:\n" << transformation << std::endl; 00458 } 00459 else 00460 { 00461 PCL_ERROR ("no overlap: try increasing the size or reducing the step of the grid\n"); 00462 break; 00463 } 00464 00465 transformPointCloud (output, intm_cloud, transformation); 00466 00467 nr_iterations_++; 00468 00469 if (update_visualizer_ != 0) 00470 update_visualizer_ (output, *indices_, *target_, *indices_); 00471 00472 //std::cout << "eps=" << fabs ((transformation - previous_transformation_).sum ()) << std::endl; 00473 00474 if (nr_iterations_ > max_iterations_ || 00475 fabs ((transformation - previous_transformation_).sum ()) < transformation_epsilon_) 00476 { 00477 converged_ = true; 00478 } 00479 } 00480 final_transformation_ = transformation; 00481 output = intm_cloud; 00482 } 00483 00484 #endif // PCL_NDT_IMPL_H_ 00485
1.7.6.1