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