Point Cloud Library (PCL)  1.6.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
spin_image.hpp
Go to the documentation of this file.
1 /*
2  * Software License Agreement (BSD License)
3  *
4  * Point Cloud Library (PCL) - www.pointclouds.org
5  * Copyright (c) 2010-2012, Willow Garage, Inc.
6  *
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * * Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  * * Redistributions in binary form must reproduce the above
16  * copyright notice, this list of conditions and the following
17  * disclaimer in the documentation and/or other materials provided
18  * with the distribution.
19  * * Neither the name of Willow Garage, Inc. nor the names of its
20  * contributors may be used to endorse or promote products derived
21  * from this software without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34  * POSSIBILITY OF SUCH DAMAGE.
35  *
36  * $Id: spin_image.hpp 4961 2012-03-07 23:44:07Z rusu $
37  *
38  */
39 
40 #ifndef PCL_FEATURES_IMPL_SPIN_IMAGE_H_
41 #define PCL_FEATURES_IMPL_SPIN_IMAGE_H_
42 
43 #include <limits>
44 #include <pcl/point_cloud.h>
45 #include <pcl/point_types.h>
46 #include <pcl/exceptions.h>
49 #include <cmath>
50 
52 template <typename PointInT, typename PointNT, typename PointOutT>
54  unsigned int image_width, double support_angle_cos, unsigned int min_pts_neighb) :
55  input_normals_ (), rotation_axes_cloud_ (),
56  is_angular_ (false), rotation_axis_ (), use_custom_axis_(false), use_custom_axes_cloud_ (false),
57  is_radial_ (false), image_width_ (image_width), support_angle_cos_ (support_angle_cos),
58  min_pts_neighb_ (min_pts_neighb)
59 {
60  assert (support_angle_cos_ <= 1.0 && support_angle_cos_ >= 0.0); // may be permit negative cosine?
61 
62  feature_name_ = "SpinImageEstimation";
63 }
64 
65 
67 template <typename PointInT, typename PointNT, typename PointOutT> Eigen::ArrayXXd
69 {
70  assert (image_width_ > 0);
71  assert (support_angle_cos_ <= 1.0 && support_angle_cos_ >= 0.0); // may be permit negative cosine?
72 
73  const Eigen::Vector3f origin_point (input_->points[index].getVector3fMap ());
74 
75  Eigen::Vector3f origin_normal;
76  origin_normal =
77  input_normals_ ?
78  input_normals_->points[index].getNormalVector3fMap () :
79  Eigen::Vector3f (); // just a placeholder; should never be used!
80 
81  const Eigen::Vector3f rotation_axis = use_custom_axis_ ?
82  rotation_axis_.getNormalVector3fMap () :
83  use_custom_axes_cloud_ ?
84  rotation_axes_cloud_->points[index].getNormalVector3fMap () :
85  origin_normal;
86 
87  Eigen::ArrayXXd m_matrix (Eigen::ArrayXXd::Zero (image_width_+1, 2*image_width_+1));
88  Eigen::ArrayXXd m_averAngles (Eigen::ArrayXXd::Zero (image_width_+1, 2*image_width_+1));
89 
90  // OK, we are interested in the points of the cylinder of height 2*r and
91  // base radius r, where r = m_dBinSize * in_iImageWidth
92  // it can be embedded to the sphere of radius sqrt(2) * m_dBinSize * in_iImageWidth
93  // suppose that points are uniformly distributed, so we lose ~40%
94  // according to the volumes ratio
95  double bin_size = 0.0;
96  if (is_radial_)
97  bin_size = search_radius_ / image_width_;
98  else
99  bin_size = search_radius_ / image_width_ / sqrt(2.0);
100 
101  std::vector<int> nn_indices;
102  std::vector<float> nn_sqr_dists;
103  const int neighb_cnt = this->searchForNeighbors (index, search_radius_, nn_indices, nn_sqr_dists);
104  if (neighb_cnt < static_cast<int> (min_pts_neighb_))
105  {
106  throw PCLException (
107  "Too few points for spin image, use setMinPointCountInNeighbourhood() to decrease the threshold or use larger feature radius",
108  "spin_image.hpp", "computeSiForPoint");
109  }
110 
111  // for all neighbor points
112  for (int i_neigh = 0; i_neigh < neighb_cnt ; i_neigh++)
113  {
114  // first, skip the points with distant normals
115  double cos_between_normals = -2.0; // should be initialized if used
116  if (support_angle_cos_ > 0.0 || is_angular_) // not bogus
117  {
118  cos_between_normals = origin_normal.dot (input_normals_->points[nn_indices[i_neigh]].getNormalVector3fMap ());
119  if (fabs (cos_between_normals) > (1.0 + 10*std::numeric_limits<float>::epsilon ())) // should be okay for numeric stability
120  {
121  PCL_ERROR ("[pcl::%s::computeSiForPoint] Normal for the point %d and/or the point %d are not normalized, dot ptoduct is %f.\n",
122  getClassName ().c_str (), nn_indices[i_neigh], index, cos_between_normals);
123  throw PCLException ("Some normals are not normalized",
124  "spin_image.hpp", "computeSiForPoint");
125  }
126  cos_between_normals = std::max (-1.0, std::min (1.0, cos_between_normals));
127 
128  if (fabs (cos_between_normals) < support_angle_cos_ ) // allow counter-directed normals
129  {
130  continue;
131  }
132 
133  if (cos_between_normals < 0.0)
134  {
135  cos_between_normals = -cos_between_normals; // the normal is not used explicitly from now
136  }
137  }
138 
139  // now compute the coordinate in cylindric coordinate system associated with the origin point
140  const Eigen::Vector3f direction (
141  surface_->points[nn_indices[i_neigh]].getVector3fMap () - origin_point);
142  const double direction_norm = direction.norm ();
143  if (fabs(direction_norm) < 10*std::numeric_limits<double>::epsilon ())
144  continue; // ignore the point itself; it does not contribute really
145  assert (direction_norm > 0.0);
146 
147  // the angle between the normal vector and the direction to the point
148  double cos_dir_axis = direction.dot(rotation_axis) / direction_norm;
149  if (fabs(cos_dir_axis) > (1.0 + 10*std::numeric_limits<float>::epsilon())) // should be okay for numeric stability
150  {
151  PCL_ERROR ("[pcl::%s::computeSiForPoint] Rotation axis for the point %d are not normalized, dot ptoduct is %f.\n",
152  getClassName ().c_str (), index, cos_dir_axis);
153  throw PCLException ("Some rotation axis is not normalized",
154  "spin_image.hpp", "computeSiForPoint");
155  }
156  cos_dir_axis = std::max (-1.0, std::min (1.0, cos_dir_axis));
157 
158  // compute coordinates w.r.t. the reference frame
159  double beta = std::numeric_limits<double>::signaling_NaN ();
160  double alpha = std::numeric_limits<double>::signaling_NaN ();
161  if (is_radial_) // radial spin image structure
162  {
163  beta = asin (cos_dir_axis); // yes, arc sine! to get the angle against tangent, not normal!
164  alpha = direction_norm;
165  }
166  else // rectangular spin-image structure
167  {
168  beta = direction_norm * cos_dir_axis;
169  alpha = direction_norm * sqrt (1.0 - cos_dir_axis*cos_dir_axis);
170 
171  if (fabs (beta) >= bin_size * image_width_ || alpha >= bin_size * image_width_)
172  {
173  continue; // outside the cylinder
174  }
175  }
176 
177  assert (alpha >= 0.0);
178  assert (alpha <= bin_size * image_width_ + 20 * std::numeric_limits<float>::epsilon () );
179 
180 
181  // bilinear interpolation
182  double beta_bin_size = is_radial_ ? (M_PI / 2 / image_width_) : bin_size;
183  int beta_bin = int(std::floor (beta / beta_bin_size)) + int(image_width_);
184  assert (0 <= beta_bin && beta_bin < m_matrix.cols ());
185  int alpha_bin = int(std::floor (alpha / bin_size));
186  assert (0 <= alpha_bin && alpha_bin < m_matrix.rows ());
187 
188  if (alpha_bin == static_cast<int> (image_width_)) // border points
189  {
190  alpha_bin--;
191  // HACK: to prevent a > 1
192  alpha = bin_size * (alpha_bin + 1) - std::numeric_limits<double>::epsilon ();
193  }
194  if (beta_bin == int(2*image_width_) ) // border points
195  {
196  beta_bin--;
197  // HACK: to prevent b > 1
198  beta = beta_bin_size * (beta_bin - int(image_width_) + 1) - std::numeric_limits<double>::epsilon ();
199  }
200 
201  double a = alpha/bin_size - double(alpha_bin);
202  double b = beta/beta_bin_size - double(beta_bin-int(image_width_));
203 
204  assert (0 <= a && a <= 1);
205  assert (0 <= b && b <= 1);
206 
207  m_matrix (alpha_bin, beta_bin) += (1-a) * (1-b);
208  m_matrix (alpha_bin+1, beta_bin) += a * (1-b);
209  m_matrix (alpha_bin, beta_bin+1) += (1-a) * b;
210  m_matrix (alpha_bin+1, beta_bin+1) += a * b;
211 
212  if (is_angular_)
213  {
214  m_averAngles (alpha_bin, beta_bin) += (1-a) * (1-b) * acos (cos_between_normals);
215  m_averAngles (alpha_bin+1, beta_bin) += a * (1-b) * acos (cos_between_normals);
216  m_averAngles (alpha_bin, beta_bin+1) += (1-a) * b * acos (cos_between_normals);
217  m_averAngles (alpha_bin+1, beta_bin+1) += a * b * acos (cos_between_normals);
218  }
219  }
220 
221  if (is_angular_)
222  {
223  // transform sum to average
224  m_matrix = m_averAngles / (m_matrix + std::numeric_limits<double>::epsilon ()); // +eps to avoid division by zero
225  }
226  else if (neighb_cnt > 1) // to avoid division by zero, also no need to divide by 1
227  {
228  // normalization
229  m_matrix /= m_matrix.sum();
230  }
231 
232  return m_matrix;
233 }
234 
235 
237 template <typename PointInT, typename PointNT, typename PointOutT> bool
239 {
240  if (!Feature<PointInT, PointOutT>::initCompute ())
241  {
242  PCL_ERROR ("[pcl::%s::initCompute] Init failed.\n", getClassName ().c_str ());
243  return (false);
244  }
245 
246  // Check if input normals are set
247  if (!input_normals_)
248  {
249  PCL_ERROR ("[pcl::%s::initCompute] No input dataset containing normals was given!\n", getClassName ().c_str ());
250  Feature<PointInT, PointOutT>::deinitCompute ();
251  return (false);
252  }
253 
254  // Check if the size of normals is the same as the size of the surface
255  if (input_normals_->points.size () != input_->points.size ())
256  {
257  PCL_ERROR ("[pcl::%s::initCompute] ", getClassName ().c_str ());
258  PCL_ERROR ("The number of points in the input dataset differs from ");
259  PCL_ERROR ("the number of points in the dataset containing the normals!\n");
260  Feature<PointInT, PointOutT>::deinitCompute ();
261  return (false);
262  }
263 
264  // We need a positive definite search radius to continue
265  if (search_radius_ == 0)
266  {
267  PCL_ERROR ("[pcl::%s::initCompute] Need a search radius different than 0!\n", getClassName ().c_str ());
268  Feature<PointInT, PointOutT>::deinitCompute ();
269  return (false);
270  }
271  if (k_ != 0)
272  {
273  PCL_ERROR ("[pcl::%s::initCompute] K-nearest neighbor search for spin images not implemented. Used a search radius instead!\n", getClassName ().c_str ());
274  Feature<PointInT, PointOutT>::deinitCompute ();
275  return (false);
276  }
277  // If the surface won't be set, make fake surface and fake surface normals
278  // if we wouldn't do it here, the following method would alarm that no surface normals is given
279  if (!surface_)
280  {
281  surface_ = input_;
282  fake_surface_ = true;
283  }
284 
285  //if (fake_surface_ && !input_normals_)
286  // input_normals_ = normals_; // normals_ is set, as checked earlier
287 
288  assert(!(use_custom_axis_ && use_custom_axes_cloud_));
289 
290  if (!use_custom_axis_ && !use_custom_axes_cloud_ // use input normals as rotation axes
291  && !input_normals_)
292  {
293  PCL_ERROR ("[pcl::%s::initCompute] No normals for input cloud were given!\n", getClassName ().c_str ());
294  // Cleanup
295  Feature<PointInT, PointOutT>::deinitCompute ();
296  return (false);
297  }
298 
299  if ((is_angular_ || support_angle_cos_ > 0.0) // support angle is not bogus NOTE this is for randomly-flipped normals
300  && !input_normals_)
301  {
302  PCL_ERROR ("[pcl::%s::initCompute] No normals for input cloud were given!\n", getClassName ().c_str ());
303  // Cleanup
304  Feature<PointInT, PointOutT>::deinitCompute ();
305  return (false);
306  }
307 
308  if (use_custom_axes_cloud_
309  && rotation_axes_cloud_->size () == input_->size ())
310  {
311  PCL_ERROR ("[pcl::%s::initCompute] Rotation axis cloud have different size from input!\n", getClassName ().c_str ());
312  // Cleanup
313  Feature<PointInT, PointOutT>::deinitCompute ();
314  return (false);
315  }
316 
317  return (true);
318 }
319 
320 
322 template <typename PointInT, typename PointNT, typename PointOutT> void
324 {
325  for (int i_input = 0; i_input < static_cast<int> (indices_->size ()); ++i_input)
326  {
327  Eigen::ArrayXXd res = computeSiForPoint (indices_->at (i_input));
328 
329  // Copy into the resultant cloud
330  for (int iRow = 0; iRow < res.rows () ; iRow++)
331  {
332  for (int iCol = 0; iCol < res.cols () ; iCol++)
333  {
334  output.points[i_input].histogram[ iRow*res.cols () + iCol ] = static_cast<float> (res (iRow, iCol));
335  }
336  }
337  }
338 }
339 
341 template <typename PointInT, typename PointNT> void
343 {
344  // Set up the output channels
345  output.channels["spin_image"].name = "spin_image";
346  output.channels["spin_image"].offset = 0;
347  output.channels["spin_image"].size = 4;
348  output.channels["spin_image"].count = 153;
349  output.channels["spin_image"].datatype = sensor_msgs::PointField::FLOAT32;
350 
351  output.points.resize (indices_->size (), 153);
352  for (int i_input = 0; i_input < static_cast<int> (indices_->size ()); ++i_input)
353  {
354  Eigen::ArrayXXd res = this->computeSiForPoint (indices_->at (i_input));
355 
356  // Copy into the resultant cloud
357  for (int iRow = 0; iRow < res.rows () ; iRow++)
358  {
359  for (int iCol = 0; iCol < res.cols () ; iCol++)
360  {
361  output.points (i_input, iRow*res.cols () + iCol) = static_cast<float> (res (iRow, iCol));
362  }
363  }
364  }
365 }
366 
367 
368 #define PCL_INSTANTIATE_SpinImageEstimation(T,NT,OutT) template class PCL_EXPORTS pcl::SpinImageEstimation<T,NT,OutT>;
369 
370 #endif // PCL_FEATURES_IMPL_SPIN_IMAGE_H_
371