Point Cloud Library (PCL)  1.4.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines
ndt.hpp
Go to the documentation of this file.
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  
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines