Point Cloud Library (PCL)  1.5.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines
spin_image.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) 2010-2012, 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  * $Id: spin_image.hpp 4702 2012-02-23 09:39:33Z gedikli $
00037  *
00038  */
00039 
00040 #ifndef PCL_FEATURES_IMPL_SPIN_IMAGE_H_
00041 #define PCL_FEATURES_IMPL_SPIN_IMAGE_H_
00042 
00043 #include <limits>
00044 #include <pcl/point_cloud.h>
00045 #include <pcl/point_types.h>
00046 #include <pcl/exceptions.h>
00047 #include <pcl/kdtree/kdtree_flann.h>
00048 #include <pcl/features/spin_image.h>
00049 
00050 
00051 template <typename PointInT, typename PointNT, typename PointOutT>
00052 const double pcl::SpinImageEstimation<PointInT, PointNT, PointOutT>::PI = 4.0 * std::atan2(1.0, 1.0);
00053 
00055 template <typename PointInT, typename PointNT, typename PointOutT>
00056 pcl::SpinImageEstimation<PointInT, PointNT, PointOutT>::SpinImageEstimation (
00057   unsigned int image_width, double support_angle_cos, unsigned int min_pts_neighb):
00058     is_angular_(false), use_custom_axis_(false), use_custom_axes_cloud_(false), 
00059     is_radial_(false),
00060     image_width_(image_width), support_angle_cos_(support_angle_cos), min_pts_neighb_(min_pts_neighb)
00061 {
00062   assert (support_angle_cos_ <= 1.0 && support_angle_cos_ >= 0.0); // may be permit negative cosine?
00063 
00064   feature_name_ = "SpinImageEstimation";
00065 }
00066 
00067 
00069 template <typename PointInT, typename PointNT, typename PointOutT> Eigen::ArrayXXd 
00070 pcl::SpinImageEstimation<PointInT, PointNT, PointOutT>::computeSiForPoint (int index) const
00071 {
00072   assert (image_width_ > 0);
00073   assert (support_angle_cos_ <= 1.0 && support_angle_cos_ >= 0.0); // may be permit negative cosine?
00074 
00075   const Eigen::Vector3f origin_point (input_->points[index].getVector3fMap ());
00076 
00077   Eigen::Vector3f origin_normal;
00078   origin_normal = 
00079     input_normals_ ? 
00080       input_normals_->points[index].getNormalVector3fMap () :
00081       Eigen::Vector3f (); // just a placeholder; should never be used!
00082 
00083   const Eigen::Vector3f rotation_axis = use_custom_axis_ ? 
00084     rotation_axis_.getNormalVector3fMap () : 
00085     use_custom_axes_cloud_ ?
00086       rotation_axes_cloud_->points[index].getNormalVector3fMap () :
00087       origin_normal;  
00088 
00089   Eigen::ArrayXXd m_matrix (Eigen::ArrayXXd::Zero (image_width_+1, 2*image_width_+1));
00090   Eigen::ArrayXXd m_averAngles (Eigen::ArrayXXd::Zero (image_width_+1, 2*image_width_+1));
00091 
00092   // OK, we are interested in the points of the cylinder of height 2*r and
00093   // base radius r, where r = m_dBinSize * in_iImageWidth
00094   // it can be embedded to the sphere of radius sqrt(2) * m_dBinSize * in_iImageWidth
00095   // suppose that points are uniformly distributed, so we lose ~40%
00096   // according to the volumes ratio
00097   double bin_size = 0.0;
00098   if (is_radial_)
00099     bin_size = search_radius_ / image_width_;  
00100   else
00101     bin_size = search_radius_ / image_width_ / sqrt(2.0);
00102 
00103   std::vector<int> nn_indices;
00104   std::vector<float> nn_sqr_dists;
00105   const int neighb_cnt = this->searchForNeighbors (index, search_radius_, nn_indices, nn_sqr_dists);
00106   if (neighb_cnt < (int)min_pts_neighb_)
00107   {
00108     throw PCLException (
00109       "Too few points for spin image, use setMinPointCountInNeighbourhood() to decrease the threshold or use larger feature radius",
00110       "spin_image.hpp", "computeSiForPoint");
00111   }
00112 
00113   // for all neighbor points
00114   for (int i_neigh = 0; i_neigh < neighb_cnt ; i_neigh++)
00115   {
00116     // first, skip the points with distant normals
00117     double cos_between_normals = -2.0; // should be initialized if used
00118     if (support_angle_cos_ > 0.0 || is_angular_) // not bogus
00119     {
00120       cos_between_normals = origin_normal.dot (input_normals_->points[nn_indices[i_neigh]].getNormalVector3fMap ());
00121       if (fabs (cos_between_normals) > (1.0 + 10*std::numeric_limits<float>::epsilon ())) // should be okay for numeric stability
00122       {      
00123         PCL_ERROR ("[pcl::%s::computeSiForPoint] Normal for the point %d and/or the point %d are not normalized, dot ptoduct is %f.\n", 
00124           getClassName ().c_str (), nn_indices[i_neigh], index, cos_between_normals);
00125         throw PCLException ("Some normals are not normalized",
00126           "spin_image.hpp", "computeSiForPoint");
00127       }
00128       cos_between_normals = std::max (-1.0, std::min (1.0, cos_between_normals));
00129 
00130       if (fabs (cos_between_normals) < support_angle_cos_ )    // allow counter-directed normals
00131       {
00132         continue;
00133       }
00134 
00135       if (cos_between_normals < 0.0)
00136       {
00137         cos_between_normals = -cos_between_normals; // the normal is not used explicitly from now
00138       }
00139     }
00140     
00141     // now compute the coordinate in cylindric coordinate system associated with the origin point
00142     const Eigen::Vector3f direction (
00143       surface_->points[nn_indices[i_neigh]].getVector3fMap () - origin_point);
00144     const double direction_norm = direction.norm ();
00145     if (fabs(direction_norm) < 10*std::numeric_limits<double>::epsilon ())  
00146       continue;  // ignore the point itself; it does not contribute really
00147     assert (direction_norm > 0.0);
00148 
00149     // the angle between the normal vector and the direction to the point
00150     double cos_dir_axis = direction.dot(rotation_axis) / direction_norm;
00151     if (fabs(cos_dir_axis) > (1.0 + 10*std::numeric_limits<float>::epsilon())) // should be okay for numeric stability
00152     {      
00153       PCL_ERROR ("[pcl::%s::computeSiForPoint] Rotation axis for the point %d are not normalized, dot ptoduct is %f.\n", 
00154         getClassName ().c_str (), index, cos_dir_axis);
00155       throw PCLException ("Some rotation axis is not normalized",
00156         "spin_image.hpp", "computeSiForPoint");
00157     }
00158     cos_dir_axis = std::max (-1.0, std::min (1.0, cos_dir_axis));
00159 
00160     // compute coordinates w.r.t. the reference frame
00161     double beta = std::numeric_limits<double>::signaling_NaN ();
00162     double alpha = std::numeric_limits<double>::signaling_NaN ();
00163     if (is_radial_) // radial spin image structure
00164     {
00165       beta = asin (cos_dir_axis);  // yes, arc sine! to get the angle against tangent, not normal!
00166       alpha = direction_norm;
00167     }
00168     else // rectangular spin-image structure
00169     {
00170       beta = direction_norm * cos_dir_axis;
00171       alpha = direction_norm * sqrt (1.0 - cos_dir_axis*cos_dir_axis);
00172 
00173       if (fabs (beta) >= bin_size * image_width_ || alpha >= bin_size * image_width_)
00174       {
00175         continue;  // outside the cylinder
00176       }
00177     }
00178 
00179     assert (alpha >= 0.0);
00180     assert (alpha <= bin_size * image_width_ + 20 * std::numeric_limits<float>::epsilon () );
00181 
00182 
00183     // bilinear interpolation
00184     double beta_bin_size = is_radial_ ? (PI / 2 / image_width_) : bin_size;
00185     int beta_bin = int(std::floor (beta / beta_bin_size)) + int(image_width_);
00186     assert (0 <= beta_bin && beta_bin < m_matrix.cols ());
00187     int alpha_bin = int(std::floor (alpha / bin_size));
00188     assert (0 <= alpha_bin && alpha_bin < m_matrix.rows ());
00189 
00190     if (alpha_bin == (int)image_width_)  // border points
00191     {
00192       alpha_bin--;
00193       // HACK: to prevent a > 1
00194       alpha = bin_size * (alpha_bin + 1) - std::numeric_limits<double>::epsilon ();
00195     }
00196     if (beta_bin == int(2*image_width_) )  // border points
00197     {
00198       beta_bin--;
00199       // HACK: to prevent b > 1
00200       beta = beta_bin_size * (beta_bin - int(image_width_) + 1) - std::numeric_limits<double>::epsilon ();
00201     }
00202 
00203     double a = alpha/bin_size - double(alpha_bin);
00204     double b = beta/beta_bin_size - double(beta_bin-int(image_width_)); 
00205 
00206     assert (0 <= a && a <= 1);
00207     assert (0 <= b && b <= 1);
00208 
00209     m_matrix (alpha_bin, beta_bin) += (1-a) * (1-b);
00210     m_matrix (alpha_bin+1, beta_bin) += a * (1-b);
00211     m_matrix (alpha_bin, beta_bin+1) += (1-a) * b;
00212     m_matrix (alpha_bin+1, beta_bin+1) += a * b;
00213 
00214     if (is_angular_)
00215     {
00216       m_averAngles (alpha_bin, beta_bin) += (1-a) * (1-b) * acos (cos_between_normals); 
00217       m_averAngles (alpha_bin+1, beta_bin) += a * (1-b) * acos (cos_between_normals);
00218       m_averAngles (alpha_bin, beta_bin+1) += (1-a) * b * acos (cos_between_normals);
00219       m_averAngles (alpha_bin+1, beta_bin+1) += a * b * acos (cos_between_normals);
00220     }
00221   }
00222 
00223   if (is_angular_)
00224   {
00225     // transform sum to average
00226     m_matrix = m_averAngles / (m_matrix + std::numeric_limits<double>::epsilon ()); // +eps to avoid division by zero
00227   }
00228   else if (neighb_cnt > 1) // to avoid division by zero, also no need to divide by 1
00229   {
00230     // normalization
00231     m_matrix /= m_matrix.sum();
00232   }
00233 
00234   return m_matrix;
00235 }
00236 
00237 
00239 template <typename PointInT, typename PointNT, typename PointOutT> bool 
00240 pcl::SpinImageEstimation<PointInT, PointNT, PointOutT>::initCompute ()
00241 {
00242   if (!Feature<PointInT, PointOutT>::initCompute ())
00243   {
00244     PCL_ERROR ("[pcl::%s::initCompute] Init failed.\n", getClassName ().c_str ());
00245     return (false);
00246   }
00247 
00248   // Check if input normals are set
00249   if (!input_normals_)
00250   {
00251     PCL_ERROR ("[pcl::%s::initCompute] No input dataset containing normals was given!\n", getClassName ().c_str ());
00252     Feature<PointInT, PointOutT>::deinitCompute ();
00253     return (false);
00254   }
00255 
00256   // Check if the size of normals is the same as the size of the surface
00257   if (input_normals_->points.size () != input_->points.size ())
00258   {
00259     PCL_ERROR ("[pcl::%s::initCompute] ", getClassName ().c_str ());
00260     PCL_ERROR ("The number of points in the input dataset differs from ");
00261     PCL_ERROR ("the number of points in the dataset containing the normals!\n");
00262     Feature<PointInT, PointOutT>::deinitCompute ();
00263     return (false);
00264   }
00265 
00266    // We need a positive definite search radius to continue
00267   if (search_radius_ == 0)
00268   {
00269     PCL_ERROR ("[pcl::%s::initCompute] Need a search radius different than 0!\n", getClassName ().c_str ());
00270     Feature<PointInT, PointOutT>::deinitCompute ();
00271     return (false);
00272   }
00273   if (k_ != 0)
00274   {
00275     PCL_ERROR ("[pcl::%s::initCompute] K-nearest neighbor search for spin images not implemented. Used a search radius instead!\n", getClassName ().c_str ());
00276     Feature<PointInT, PointOutT>::deinitCompute ();
00277     return (false);
00278   }
00279   // If the surface won't be set, make fake surface and fake surface normals
00280   // if we wouldn't do it here, the following method would alarm that no surface normals is given
00281   if (!surface_)
00282   {
00283     surface_ = input_;
00284     fake_surface_ = true;
00285   }
00286 
00287   //if (fake_surface_ && !input_normals_)
00288   //  input_normals_ = normals_; // normals_ is set, as checked earlier
00289   
00290   assert(!(use_custom_axis_ && use_custom_axes_cloud_));
00291 
00292   if (!use_custom_axis_ && !use_custom_axes_cloud_ // use input normals as rotation axes
00293     && !input_normals_)
00294   {
00295     PCL_ERROR ("[pcl::%s::initCompute] No normals for input cloud were given!\n", getClassName ().c_str ());
00296     // Cleanup
00297     Feature<PointInT, PointOutT>::deinitCompute ();
00298     return (false);
00299   }
00300 
00301   if ((is_angular_ || support_angle_cos_ > 0.0) // support angle is not bogus NOTE this is for randomly-flipped normals
00302     && !input_normals_)
00303   {
00304     PCL_ERROR ("[pcl::%s::initCompute] No normals for input cloud were given!\n", getClassName ().c_str ());
00305     // Cleanup
00306     Feature<PointInT, PointOutT>::deinitCompute ();
00307     return (false);
00308   }
00309 
00310   if (use_custom_axes_cloud_ 
00311     && rotation_axes_cloud_->size () == input_->size ())
00312   {
00313     PCL_ERROR ("[pcl::%s::initCompute] Rotation axis cloud have different size from input!\n", getClassName ().c_str ());
00314     // Cleanup
00315     Feature<PointInT, PointOutT>::deinitCompute ();
00316     return (false);
00317   }
00318 
00319   return (true);
00320 }
00321 
00322 
00324 template <typename PointInT, typename PointNT, typename PointOutT> void 
00325 pcl::SpinImageEstimation<PointInT, PointNT, PointOutT>::computeFeature (PointCloudOut &output)
00326 { 
00327   for (int i_input = 0; i_input < (int)indices_->size (); ++i_input)
00328   {
00329     Eigen::ArrayXXd res = computeSiForPoint (indices_->at (i_input));
00330 
00331     // Copy into the resultant cloud
00332     for (int iRow = 0; iRow < res.rows () ; iRow++)
00333     {
00334       for (int iCol = 0; iCol < res.cols () ; iCol++)
00335       {
00336         output.points[i_input].histogram[ iRow*res.cols () + iCol ] = (float)res(iRow, iCol);
00337       }
00338     }   
00339   } 
00340 }
00341 
00343 template <typename PointInT, typename PointNT> void 
00344 pcl::SpinImageEstimation<PointInT, PointNT, Eigen::MatrixXf>::computeFeatureEigen (pcl::PointCloud<Eigen::MatrixXf> &output)
00345 { 
00346   // Set up the output channels
00347   output.channels["spin_image"].name     = "spin_image";
00348   output.channels["spin_image"].offset   = 0;
00349   output.channels["spin_image"].size     = 4;
00350   output.channels["spin_image"].count    = 153;
00351   output.channels["spin_image"].datatype = sensor_msgs::PointField::FLOAT32;
00352 
00353   output.points.resize (indices_->size (), 153);
00354   for (int i_input = 0; i_input < (int)indices_->size (); ++i_input)
00355   {
00356     Eigen::ArrayXXd res = this->computeSiForPoint (indices_->at (i_input));
00357 
00358     // Copy into the resultant cloud
00359     for (int iRow = 0; iRow < res.rows () ; iRow++)
00360     {
00361       for (int iCol = 0; iCol < res.cols () ; iCol++)
00362       {
00363         output.points (i_input, iRow*res.cols () + iCol) = (float)res(iRow, iCol);
00364       }
00365     }   
00366   } 
00367 }
00368 
00369 
00370 #define PCL_INSTANTIATE_SpinImageEstimation(T,NT,OutT) template class PCL_EXPORTS pcl::SpinImageEstimation<T,NT,OutT>;
00371 
00372 #endif    // PCL_FEATURES_IMPL_SPIN_IMAGE_H_
00373