curvature_estimation_taubin.h
Go to the documentation of this file.
1 /*
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2014, Andreas ten Pas
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above
14  * copyright notice, this list of conditions and the following
15  * disclaimer in the documentation and/or other materials provided
16  * with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #ifndef PCL_FEATURES_CURVATURE_ESTIMATION_TAUBIN_H_
33 #define PCL_FEATURES_CURVATURE_ESTIMATION_TAUBIN_H_
34 
35 #include <pcl/features/feature.h>
36 #include <pcl/filters/extract_indices.h>
37 #include <pcl/point_types.h>
38 #include <Eigen/Dense>
39 #include <vector>
40 
41 // Lapack function to solve the generalized eigenvalue problem
42 extern "C" void dggev_(const char* JOBVL, const char* JOBVR, const int* N, const double* A, const int* LDA,
43  const double* B, const int* LDB, double* ALPHAR, double* ALPHAI, double* BETA, double* VL,
44  const int* LDVL, double* VR, const int* LDVR, double* WORK, const int* LWORK, int* INFO);
45 
46 namespace pcl
47 {
52 {
53  union
54  {
55  float normal[4];
56  struct
57  {
58  float normal_x;
59  float normal_y;
60  float normal_z;
61  };
62  };
63  union
64  {
65  float curvature_axis[4];
66  struct
67  {
71  };
72  };
73  union
74  {
76  struct
77  {
81  };
82  };
83  union
84  {
86  };EIGEN_MAKE_ALIGNED_OPERATOR_NEW // make sure the allocators are aligned (SSE optimization)
88 
89 // size of matrices in Taubin Quadric Fitting
90 const int TAUBIN_MATRICES_SIZE = 10;
91 
98 template<typename PointInT, typename PointOutT>
99 class CurvatureEstimationTaubin : public Feature<PointInT, PointOutT>
100 {
101 public:
102  using Feature<PointInT, PointOutT>::feature_name_;
103  using Feature<PointInT, PointOutT>::indices_;
104  using Feature<PointInT, PointOutT>::input_;
105  using Feature<PointInT, PointOutT>::surface_;
106  using Feature<PointInT, PointOutT>::k_;
107  using Feature<PointInT, PointOutT>::search_radius_;
108  using Feature<PointInT, PointOutT>::search_parameter_;
109 
110  typedef typename Feature<PointInT, PointOutT>::PointCloudOut PointCloudOut;
111 
115  CurvatureEstimationTaubin(unsigned int num_threads = 0)
116  {
117  num_threads_ = num_threads;
118  feature_name_ = "CurvatureEstimationTaubin";
119  }
120 
130  inline void fitQuadric(const std::vector<int> &indices, Eigen::VectorXd &quadric_parameters,
131  Eigen::Vector3d &quadric_centroid, Eigen::Matrix3d &quadric_covariance_matrix)
132  {
133  int n = indices.size();
134 
135  // calculate matrices M and N
136  Eigen::Matrix<double, TAUBIN_MATRICES_SIZE, TAUBIN_MATRICES_SIZE> M;
137  Eigen::Matrix<double, TAUBIN_MATRICES_SIZE, TAUBIN_MATRICES_SIZE> N;
138  M.setZero(10, 10);
139  N.setZero(10, 10);
140 
141  for (int i = 0; i < n; i++)
142  {
143  if (isnan(this->input_->points[indices[i]].x))
144  continue;
145 
146  double x = this->input_->points[indices[i]].x;
147  double y = this->input_->points[indices[i]].y;
148  double z = this->input_->points[indices[i]].z;
149  double x2 = x * x;
150  double y2 = y * y;
151  double z2 = z * z;
152  double xy = x * y;
153  double yz = y * z;
154  double xz = x * z;
155 
156  // required calculations for M
157  M(0, 0) += x2 * x2;
158  M(0, 1) += x2 * y2;
159  M(0, 2) += x2 * z2;
160  M(0, 3) += x2 * xy;
161  M(0, 4) += x2 * yz;
162  M(0, 5) += x2 * xz;
163  M(0, 6) += x2 * x;
164  M(0, 7) += x2 * y;
165  M(0, 8) += x2 * z;
166  M(0, 9) += x2;
167  M(1, 1) += y2 * y2;
168  M(1, 2) += y2 * z2;
169  M(1, 3) += y2 * xy;
170  M(1, 4) += y2 * yz;
171  M(1, 5) += y2 * xz;
172  M(1, 6) += y2 * x;
173  M(1, 7) += y2 * y;
174  M(1, 8) += y2 * z;
175  M(1, 9) += y2;
176  M(2, 2) += z2 * z2;
177  M(2, 3) += z2 * xy;
178  M(2, 4) += z2 * yz;
179  M(2, 5) += z2 * xz;
180  M(2, 6) += z2 * x;
181  M(2, 7) += z2 * y;
182  M(2, 8) += z2 * z;
183  M(2, 9) += z2;
184  M(3, 8) += x * yz;
185  M(3, 9) += xy;
186  M(4, 9) += yz;
187  M(5, 9) += xz;
188  M(6, 9) += x;
189  M(7, 9) += y;
190  M(8, 9) += z;
191 
192  // repeating elements in M
193  M(3, 3) = M(0, 1);
194  M(5, 5) = M(0, 2);
195  M(3, 5) = M(0, 4);
196  M(3, 6) = M(0, 7);
197  M(5, 6) = M(0, 8);
198  M(6, 6) = M(0, 9);
199 
200  M(4, 4) = M(1, 2);
201  M(3, 4) = M(1, 5);
202  M(3, 7) = M(1, 6);
203  M(4, 7) = M(1, 8);
204  M(7, 7) = M(1, 9);
205 
206  M(4, 5) = M(2, 3);
207  M(5, 8) = M(2, 6);
208  M(4, 8) = M(2, 7);
209  M(8, 8) = M(2, 9);
210 
211  M(4, 6) = M(3, 8);
212  M(5, 7) = M(3, 8);
213  M(6, 7) = M(3, 9);
214 
215  M(7, 8) = M(4, 9);
216 
217  M(6, 8) = M(5, 9);
218 
219  // required calculations for N
220  N(0, 0) += 4 * x2;
221  N(0, 3) += 2 * xy;
222  N(0, 5) += 2 * xz;
223  N(0, 6) += 2 * x;
224 
225  N(1, 1) += 4 * y2;
226  N(1, 3) += 2 * xy;
227  N(1, 4) += 2 * yz;
228  N(1, 7) += 2 * y;
229 
230  N(2, 2) += 4 * z2;
231  N(2, 4) += 2 * yz;
232  N(2, 5) += 2 * xz;
233  N(2, 8) += 2 * z;
234 
235  N(3, 3) += x2 + y2;
236  N(3, 4) += xz;
237  N(3, 5) += yz;
238  N(3, 6) += y;
239  N(3, 7) += x;
240 
241  N(4, 4) += y2 + z2;
242  N(4, 5) += xy;
243  N(4, 7) += z;
244  N(4, 8) += y;
245 
246  N(5, 5) += x2 + z2;
247  N(5, 6) += z;
248  N(5, 8) += x;
249  }
250 
251  M(9, 9) = n;
252  // reflect upper triangular part in lower triangular part
253  M.triangularView<Eigen::StrictlyLower>() = M.triangularView<Eigen::StrictlyUpper>().transpose();
254  N(6, 6) = n;
255  N(7, 7) = n;
256  N(8, 8) = n;
257  // reflect upper triangular part in lower triangular part
258  N.triangularView<Eigen::StrictlyLower>() = N.triangularView<Eigen::StrictlyUpper>().transpose();
259 
260  // solve generalized Eigen problem to find quadric parameters
261  Eigen::MatrixXd eigen_vectors;
262  Eigen::MatrixXd lambda;
263  this->solveGeneralizedEigenProblem(M, N, eigen_vectors, lambda);
264  Eigen::VectorXd eigen_values = lambda.col(0).cwiseQuotient(lambda.col(2));
265  int min_index;
266  eigen_values.segment(0, 9).minCoeff(&min_index);
267  quadric_parameters = eigen_vectors.col(min_index);
268  quadric_parameters.segment(3, 3) *= 0.5;
269 
270  // compute centroid and covariance matrix of quadric
271  this->unpackQuadric(quadric_parameters, quadric_centroid, quadric_covariance_matrix);
272  }
273 
278  static inline bool isSecondElementSmaller(const std::vector<double>& p1, const std::vector<double>& p2)
279  {
280  return p1[1] < p2[1];
281  }
282 
294  inline void estimateMedianCurvature(const std::vector<int> &indices, const Eigen::VectorXd &quadric_parameters,
295  double &median_curvature, Eigen::Vector3d &normal,
296  Eigen::Vector3d &curvature_axis, Eigen::Vector3d &curvature_centroid,
297  bool is_deterministic = false)
298  {
299  // quadric parameters in implicit form
300  double a = quadric_parameters(0);
301  double b = quadric_parameters(1);
302  double c = quadric_parameters(2);
303  double d = 2 * quadric_parameters(3);
304  double e = 2 * quadric_parameters(4);
305  double f = 2 * quadric_parameters(5);
306  double g = quadric_parameters(6);
307  double h = quadric_parameters(7);
308  double i = quadric_parameters(8);
309 
310  // create matrix to store <sample_num> samples that are close to the quadric
311  Eigen::Matrix<double, 3, Eigen::Dynamic> samples_near_surf;
312  int sample_num;
313 
314  // stochastic algorithm: look at a sample of 50 neighborhood points
315  if (!is_deterministic)
316  {
317  sample_num = 50;
318  Eigen::Matrix<double, 3, Eigen::Dynamic> samples_matrix(3, sample_num);
319 
320  for (int t = 0; t < sample_num; t++)
321  {
322  int r = rand() % indices.size();
323 
324  if (isnan(this->input_->points[indices[r]].x))
325  continue;
326 
327  samples_matrix.col(t) << this->input_->points[indices[r]].x, this->input_->points[indices[r]].y, this->input_->points[indices[r]].z;
328  }
329 
330  samples_near_surf = samples_matrix;
331  }
332  // deterministic algorithm: look at all points in the neighborhood
333  else
334  {
335  Eigen::Matrix<double, 3, Eigen::Dynamic> samples_matrix(3, indices.size());
336  sample_num = 0;
337 
338  for (std::size_t t = 0; t < indices.size(); t++)
339  {
340  if (isnan(this->input_->points[indices[t]].x))
341  continue;
342 
343  samples_matrix(0, t) = this->input_->points[indices[t]].x;
344  samples_matrix(1, t) = this->input_->points[indices[t]].y;
345  samples_matrix(2, t) = this->input_->points[indices[t]].z;
346  sample_num++;
347  }
348 
349  samples_matrix.conservativeResize(3, sample_num);
350  samples_near_surf = samples_matrix;
351  sample_num = samples_near_surf.cols();
352  }
353 
354  // calculate normals and gradient magnitude at each of these pts
355  Eigen::MatrixXd fx =
356  (2 * a * samples_near_surf.row(0) + d * samples_near_surf.row(1) + f * samples_near_surf.row(2)).array() + g;
357  Eigen::MatrixXd fy =
358  (2 * b * samples_near_surf.row(1) + d * samples_near_surf.row(0) + e * samples_near_surf.row(2)).array() + h;
359  Eigen::MatrixXd fz =
360  (2 * c * samples_near_surf.row(2) + e * samples_near_surf.row(1) + f * samples_near_surf.row(0)).array() + i;
361  Eigen::MatrixXd normals(3, sample_num);
362  normals << fx, fy, fz;
363  Eigen::MatrixXd gradient_magnitude = ((normals.cwiseProduct(normals)).colwise().sum()).cwiseSqrt();
364  normals = normals.cwiseQuotient(gradient_magnitude.replicate(3, 1));
365 
366  // calculate 2nd derivative of implicit quadric form
367  Eigen::Matrix3d second_derivative_f;
368  second_derivative_f << 2 * a, d, f, d, 2 * b, e, f, e, 2 * c;
369  std::vector < std::vector<double> > list(sample_num, std::vector<double>(2));
370 
371  // iterate over the samples
372  for (int t = 0; t < sample_num; t++)
373  {
374  // compute curvature by solving Eigen problem
375  Eigen::Matrix3d curvature_matrix = -1
376  * (Eigen::MatrixXd::Identity(3, 3) - normals.col(t) * normals.col(t).transpose()) * second_derivative_f
377  / gradient_magnitude(t);
378  Eigen::EigenSolver<Eigen::Matrix3d> eigen_solver(curvature_matrix);
379  Eigen::Vector3d curvatures = eigen_solver.eigenvalues().real();
380  int index;
381  double max_coeff = curvatures.cwiseAbs().maxCoeff(&index);
382  list[t][0] = t;
383  list[t][1] = curvatures(index);
384  normals.col(t) *= this->sign(list[t][1]);
385  list[t][1] = fabs(list[t][1]);
386  }
387 
388  // sort list of curvatures in increasing order
389  std::sort(list.begin(), list.end(), isSecondElementSmaller);
390 
391  // compute median curvature
392  int median_curvature_index = list[sample_num / 2 - 1][0];
393  Eigen::Vector3d median_curvature_point = samples_near_surf.col(median_curvature_index);
394  median_curvature = list[sample_num / 2 - 1][1];
395  normal = normals.col(median_curvature_index);
396  curvature_centroid = median_curvature_point + (normal / median_curvature);
397 
398  // find curvature axis by solving Eigen problem
399  Eigen::Matrix3d curvature_matrix = (-1) * (Eigen::MatrixXd::Identity(3, 3) - normal * normal.transpose())
400  * second_derivative_f / gradient_magnitude(median_curvature_index);
401  Eigen::EigenSolver<Eigen::Matrix3d> eigen_solver(curvature_matrix);
402  Eigen::Matrix3d curvature_vectors = eigen_solver.eigenvectors().real();
403  int max_index;
404  eigen_solver.eigenvalues().real().cwiseAbs().maxCoeff(&max_index);
405  curvature_axis = curvature_vectors.col(max_index).cross(normal);
406  }
407 
411  inline void setNumSamples(int num_samples)
412  {
413  num_samples_ = num_samples;
414  }
415 
419  inline void setNumThreads(int num_threads)
420  {
421  num_threads_ = num_threads;
422  }
423 
426  inline std::vector<std::vector<int> > const &getNeighborhoods() const
427  {
428  return neighborhoods_;
429  }
430  ;
431 
434  inline std::vector<int> const &getNeighborhoodCentroids() const
435  {
436  return neighborhood_centroids_;
437  }
438  ;
439 
445  void
446  computeFeature(const Eigen::MatrixXd &samples, PointCloudOut &output);
447 
448 protected:
449 
457  void
458  computeFeature(PointCloudOut &output);
459 
460 private:
461 
467  void
468  computeFeature(const std::vector<int> &nn_indices, int index, PointCloudOut &output);
469 
477  inline void unpackQuadric(const Eigen::VectorXd &quadric_parameters, Eigen::Vector3d &quadric_centroid,
478  Eigen::Matrix3d &quadric_covariance_matrix)
479  {
480  double a = quadric_parameters(0);
481  double b = quadric_parameters(1);
482  double c = quadric_parameters(2);
483  double d = quadric_parameters(3);
484  double e = quadric_parameters(4);
485  double f = quadric_parameters(5);
486  double g = quadric_parameters(6);
487  double h = quadric_parameters(7);
488  double i = quadric_parameters(8);
489  double j = quadric_parameters(9);
490 
491  Eigen::Matrix3d parameter_matrix;
492  parameter_matrix << a, d, f, d, b, e, f, e, c;
493  Eigen::Vector3d ghi;
494  ghi << g, h, i;
495  Eigen::Matrix3d inverse_parameter_matrix = parameter_matrix.inverse();
496  quadric_centroid = -0.5 * inverse_parameter_matrix * ghi;
497  double k = j - 0.25 * ghi.transpose() * inverse_parameter_matrix * ghi;
498  quadric_covariance_matrix = -1 * parameter_matrix / k;
499  }
500 
504  inline int sign(double x)
505  {
506  if (x > 0)
507  return 1;
508  if (x < 0)
509  return -1;
510  return 0;
511  }
512 
522  inline bool solveGeneralizedEigenProblem(const Eigen::MatrixXd& A, const Eigen::MatrixXd& B, Eigen::MatrixXd& v,
523  Eigen::MatrixXd& lambda)
524  {
525  int N = A.cols(); // Number of columns of A and B. Number of rows of v.
526  if (B.cols() != N || A.rows() != N || B.rows() != N)
527  return false;
528 
529  v.resize(N, N);
530  lambda.resize(N, 3);
531 
532  int LDA = A.outerStride();
533  int LDB = B.outerStride();
534  int LDV = v.outerStride();
535 
536  double WORKDUMMY;
537  int LWORK = -1; // Request optimum work size.
538  int INFO = 0;
539 
540  double* alphar = const_cast<double*>(lambda.col(0).data());
541  double* alphai = const_cast<double*>(lambda.col(1).data());
542  double* beta = const_cast<double*>(lambda.col(2).data());
543 
544  // Get the optimum work size.
545  dggev_("N", "V", &N, A.data(), &LDA, B.data(), &LDB, alphar, alphai, beta, 0, &LDV, v.data(), &LDV, &WORKDUMMY,
546  &LWORK, &INFO);
547 
548  LWORK = int(WORKDUMMY) + 32;
549  Eigen::VectorXd WORK(LWORK);
550 
551  dggev_("N", "V", &N, A.data(), &LDA, B.data(), &LDB, alphar, alphai, beta, 0, &LDV, v.data(), &LDV, WORK.data(),
552  &LWORK, &INFO);
553 
554  return INFO == 0;
555  }
556  ;
557 
558  unsigned int num_samples_; // number of samples (neighborhoods)
559  unsigned int num_threads_; // number of threads for parallelization
560  std::vector<std::vector<int> > neighborhoods_; // list of lists of point cloud indices for each neighborhood
561  std::vector<int> neighborhood_centroids_; // list of point cloud indices corresponding to neighborhood centroids
562  double time_taubin;
564 };
565 }
566 
567 #endif // PCL_FEATURES_CURVATURE_ESTIMATION_TAUBIN_H_
d
void dggev_(const char *JOBVL, const char *JOBVR, const int *N, const double *A, const int *LDA, const double *B, const int *LDB, double *ALPHAR, double *ALPHAI, double *BETA, double *VL, const int *LDVL, double *VR, const int *LDVR, double *WORK, const int *LWORK, int *INFO)
static bool isSecondElementSmaller(const std::vector< double > &p1, const std::vector< double > &p2)
Compares two vectors by looking at their second elements.
f
double sign(double arg)
std::vector< std::vector< int > > const & getNeighborhoods() const
Get the indices of each point neighborhood.
std::vector< std::vector< int > > neighborhoods_
CurvatureEstimationTaubin estimates the curvature for a set of point neighborhoods in the cloud using...
bool solveGeneralizedEigenProblem(const Eigen::MatrixXd &A, const Eigen::MatrixXd &B, Eigen::MatrixXd &v, Eigen::MatrixXd &lambda)
Solves the generalized Eigen problem A * v(j) = lambda(j) * B * v(j), where v are the Eigen vectors...
TFSIMD_FORCE_INLINE const tfScalar & y() const
void setNumSamples(int num_samples)
Set the number of samples (point neighborhoods).
struct pcl::PointCurvatureTaubin EIGEN_ALIGN16
const int TAUBIN_MATRICES_SIZE
CurvatureEstimationTaubin(unsigned int num_threads=0)
Constructor. Set the number of threads to use.
void estimateMedianCurvature(const std::vector< int > &indices, const Eigen::VectorXd &quadric_parameters, double &median_curvature, Eigen::Vector3d &normal, Eigen::Vector3d &curvature_axis, Eigen::Vector3d &curvature_centroid, bool is_deterministic=false)
Estimate the median curvature for a given quadric, using the indices of the point neighborhood that t...
int sign(double x)
Get the sign of a given value (according to the sign function).
void fitQuadric(const std::vector< int > &indices, Eigen::VectorXd &quadric_parameters, Eigen::Vector3d &quadric_centroid, Eigen::Matrix3d &quadric_covariance_matrix)
Fit a quadric to a given set of points, using their indices, and return the parameters of the quadric...
Feature< PointInT, PointOutT >::PointCloudOut PointCloudOut
void unpackQuadric(const Eigen::VectorXd &quadric_parameters, Eigen::Vector3d &quadric_centroid, Eigen::Matrix3d &quadric_covariance_matrix)
Unpack the quadric, using its parameters, and return the centroid and the covariance matrix of the qu...
TFSIMD_FORCE_INLINE const tfScalar & x() const
TFSIMD_FORCE_INLINE const tfScalar & z() const
std::vector< int > const & getNeighborhoodCentroids() const
Get the centroid indices of each point neighborhood.
void setNumThreads(int num_threads)
Set the number of threads used for parallelizing Taubin Quadric Fitting.


handle_detector
Author(s):
autogenerated on Mon Jun 10 2019 13:29:00