Point Cloud Library (PCL)  1.14.1-dev
sac_model_ellipse3d.hpp
1 /*
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Point Cloud Library (PCL) - www.pointclouds.org
5  * Copyright (c) 2014-, Open Perception Inc.
6  *
7  * All rights reserved
8  */
9 
10 #pragma once
11 
12 #include <limits>
13 
14 #include <unsupported/Eigen/NonLinearOptimization> // for LevenbergMarquardt
15 #include <pcl/sample_consensus/sac_model_ellipse3d.h>
16 #include <pcl/common/concatenate.h>
17 
18 #include <Eigen/Eigenvalues>
19 #include <complex>
20 
21 
22 //////////////////////////////////////////////////////////////////////////
23 template <typename PointT> bool
25  const Indices &samples) const
26 {
27  if (samples.size () != sample_size_)
28  {
29  PCL_ERROR ("[pcl::SampleConsensusModelEllipse3D::isSampleGood] Wrong number of samples (is %lu, should be %lu)!\n", samples.size (), sample_size_);
30  return (false);
31  }
32 
33  // Use three points out of the 6 samples
34  const Eigen::Vector3d p0 ((*input_)[samples[0]].x, (*input_)[samples[0]].y, (*input_)[samples[0]].z);
35  const Eigen::Vector3d p1 ((*input_)[samples[1]].x, (*input_)[samples[1]].y, (*input_)[samples[1]].z);
36  const Eigen::Vector3d p2 ((*input_)[samples[2]].x, (*input_)[samples[2]].y, (*input_)[samples[2]].z);
37 
38  // Check if the squared norm of the cross-product is non-zero, otherwise
39  // common_helper_vec, which plays an important role in computeModelCoefficients,
40  // would likely be ill-formed.
41  if ((p1 - p0).cross(p1 - p2).squaredNorm() < Eigen::NumTraits<float>::dummy_precision ())
42  {
43  PCL_ERROR ("[pcl::SampleConsensusModelEllipse3D::isSampleGood] Sample points too similar or collinear!\n");
44  return (false);
45  }
46 
47  return (true);
48 }
49 
50 //////////////////////////////////////////////////////////////////////////
51 template <typename PointT> bool
52 pcl::SampleConsensusModelEllipse3D<PointT>::computeModelCoefficients (const Indices &samples, Eigen::VectorXf &model_coefficients) const
53 {
54  // Uses 6 samples
55  if (samples.size () != sample_size_)
56  {
57  PCL_ERROR ("[pcl::SampleConsensusModelEllipse3D::computeModelCoefficients] Invalid set of samples given (%lu)!\n", samples.size ());
58  return (false);
59  }
60 
61  model_coefficients.resize (model_size_); // 11 coefs
62 
63  const Eigen::Vector3f p0((*input_)[samples[0]].x, (*input_)[samples[0]].y, (*input_)[samples[0]].z);
64  const Eigen::Vector3f p1((*input_)[samples[1]].x, (*input_)[samples[1]].y, (*input_)[samples[1]].z);
65  const Eigen::Vector3f p2((*input_)[samples[2]].x, (*input_)[samples[2]].y, (*input_)[samples[2]].z);
66  const Eigen::Vector3f p3((*input_)[samples[3]].x, (*input_)[samples[3]].y, (*input_)[samples[3]].z);
67  const Eigen::Vector3f p4((*input_)[samples[4]].x, (*input_)[samples[4]].y, (*input_)[samples[4]].z);
68  const Eigen::Vector3f p5((*input_)[samples[5]].x, (*input_)[samples[5]].y, (*input_)[samples[5]].z);
69 
70  const Eigen::Vector3f common_helper_vec = (p1 - p0).cross(p1 - p2);
71  const Eigen::Vector3f ellipse_normal = common_helper_vec.normalized();
72 
73  // The same check is implemented in isSampleGood, so be sure to look there too
74  // if you find the need to change something here.
75  if (common_helper_vec.squaredNorm() < Eigen::NumTraits<float>::dummy_precision ())
76  {
77  PCL_ERROR ("[pcl::SampleConsensusModelEllipse3D::computeModelCoefficients] Sample points too similar or collinear!\n");
78  return (false);
79  }
80 
81  // Definition of the local reference frame of the ellipse
82  Eigen::Vector3f x_axis = (p1 - p0).normalized();
83  const Eigen::Vector3f z_axis = ellipse_normal.normalized();
84  const Eigen::Vector3f y_axis = z_axis.cross(x_axis).normalized();
85 
86  // Compute the rotation matrix and its transpose
87  const Eigen::Matrix3f Rot = (Eigen::Matrix3f(3,3)
88  << x_axis(0), y_axis(0), z_axis(0),
89  x_axis(1), y_axis(1), z_axis(1),
90  x_axis(2), y_axis(2), z_axis(2))
91  .finished();
92  const Eigen::Matrix3f Rot_T = Rot.transpose();
93 
94  // Convert the points to local coordinates
95  const Eigen::Vector3f p0_ = Rot_T * (p0 - p0);
96  const Eigen::Vector3f p1_ = Rot_T * (p1 - p0);
97  const Eigen::Vector3f p2_ = Rot_T * (p2 - p0);
98  const Eigen::Vector3f p3_ = Rot_T * (p3 - p0);
99  const Eigen::Vector3f p4_ = Rot_T * (p4 - p0);
100  const Eigen::Vector3f p5_ = Rot_T * (p5 - p0);
101 
102 
103  // Fit an ellipse to the samples to obtain its conic equation parameters
104  // (this implementation follows the manuscript "Direct Least Square Fitting of Ellipses"
105  // A. Fitzgibbon, M. Pilu and R. Fisher, IEEE TPAMI, 21(5) : 476–480, May 1999).
106 
107  // xOy projections only
108  const Eigen::VectorXf X = (Eigen::VectorXf(6) << p0_(0), p1_(0), p2_(0), p3_(0), p4_(0), p5_(0)).finished();
109  const Eigen::VectorXf Y = (Eigen::VectorXf(6) << p0_(1), p1_(1), p2_(1), p3_(1), p4_(1), p5_(1)).finished();
110 
111  // Design matrix D
112  const Eigen::MatrixXf D = (Eigen::MatrixXf(6,6)
113  << X(0) * X(0), X(0) * Y(0), Y(0) * Y(0), X(0), Y(0), 1.0,
114  X(1) * X(1), X(1) * Y(1), Y(1) * Y(1), X(1), Y(1), 1.0,
115  X(2) * X(2), X(2) * Y(2), Y(2) * Y(2), X(2), Y(2), 1.0,
116  X(3) * X(3), X(3) * Y(3), Y(3) * Y(3), X(3), Y(3), 1.0,
117  X(4) * X(4), X(4) * Y(4), Y(4) * Y(4), X(4), Y(4), 1.0,
118  X(5) * X(5), X(5) * Y(5), Y(5) * Y(5), X(5), Y(5), 1.0)
119  .finished();
120 
121  // Scatter matrix S
122  const Eigen::MatrixXf S = D.transpose() * D;
123 
124  // Constraint matrix C
125  const Eigen::MatrixXf C = (Eigen::MatrixXf(6,6)
126  << 0.0, 0.0, -2.0, 0.0, 0.0, 0.0,
127  0.0, 1.0, 0.0, 0.0, 0.0, 0.0,
128  -2.0, 0.0, 0.0, 0.0, 0.0, 0.0,
129  0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
130  0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
131  0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
132  .finished();
133 
134  // Solve the Generalized Eigensystem: S*a = lambda*C*a
135  Eigen::GeneralizedEigenSolver<Eigen::MatrixXf> solver;
136  solver.compute(S, C);
137  const Eigen::VectorXf eigvals = solver.eigenvalues().real();
138 
139  // Find the negative eigenvalue 'neigvec' (the largest, if many exist)
140  int idx(-1);
141  float absmin(0.0);
142  for (size_t i(0); i < static_cast<size_t>(eigvals.size()); ++i) {
143  if (eigvals(i) < absmin && !std::isinf(eigvals(i))) {
144  idx = i;
145  }
146  }
147  // Return "false" in case the negative eigenvalue was not found
148  if (idx == -1) {
149  PCL_DEBUG("[pcl::SampleConsensusModelEllipse3D::computeModelCoefficients] Failed to find the negative eigenvalue in the GES.\n");
150  return (false);
151  }
152  const Eigen::VectorXf neigvec = solver.eigenvectors().real().col(idx).normalized();
153 
154 
155  // Convert the conic model parameters to parametric ones
156 
157  // Conic parameters
158  const float con_A(neigvec(0));
159  const float con_B(neigvec(1));
160  const float con_C(neigvec(2));
161  const float con_D(neigvec(3));
162  const float con_E(neigvec(4));
163  const float con_F(neigvec(5));
164 
165  // Build matrix M0
166  const Eigen::MatrixXf M0 = (Eigen::MatrixXf(3, 3)
167  << con_F, con_D/2.0, con_E/2.0,
168  con_D/2.0, con_A, con_B/2.0,
169  con_E/2.0, con_B/2.0, con_C)
170  .finished();
171 
172  // Build matrix M
173  const Eigen::MatrixXf M = (Eigen::MatrixXf(2, 2)
174  << con_A, con_B/2.0,
175  con_B/2.0, con_C)
176  .finished();
177 
178  // Calculate the eigenvalues and eigenvectors of matrix M
179  Eigen::EigenSolver<Eigen::MatrixXf> solver_M(M);
180 
181  Eigen::VectorXf eigvals_M = solver_M.eigenvalues().real();
182 
183  // Order the eigenvalues so that |lambda_0 - con_A| <= |lambda_0 - con_C|
184  float aux_eigval(0.0);
185  if (std::abs(eigvals_M(0) - con_A) > std::abs(eigvals_M(0) - con_C)) {
186  aux_eigval = eigvals_M(0);
187  eigvals_M(0) = eigvals_M(1);
188  eigvals_M(1) = aux_eigval;
189  }
190 
191  // Parametric parameters of the ellipse
192  float par_a = std::sqrt(-M0.determinant() / (M.determinant() * eigvals_M(0)));
193  float par_b = std::sqrt(-M0.determinant() / (M.determinant() * eigvals_M(1)));
194  const float par_h = (con_B * con_E - 2.0 * con_C * con_D) / (4.0 * con_A * con_C - std::pow(con_B, 2));
195  const float par_k = (con_B * con_D - 2.0 * con_A * con_E) / (4.0 * con_A * con_C - std::pow(con_B, 2));
196  const float par_t = (M_PI / 2.0 - std::atan((con_A - con_C) / con_B)) / 2.0; // equivalent to: acot((con_A - con_C) / con_B) / 2.0;
197 
198  // Convert the center point of the ellipse to global coordinates
199  // (the if statement ensures that 'par_a' always refers to the semi-major axis length)
200  Eigen::Vector3f p_ctr;
201  float aux_par(0.0);
202  if (par_a > par_b) {
203  p_ctr = p0 + Rot * Eigen::Vector3f(par_h, par_k, 0.0);
204  } else {
205  aux_par = par_a;
206  par_a = par_b;
207  par_b = aux_par;
208  p_ctr = p0 + Rot * Eigen::Vector3f(par_k, par_h, 0.0);
209  }
210 
211  // Center (x, y, z)
212  model_coefficients[0] = static_cast<float>(p_ctr(0));
213  model_coefficients[1] = static_cast<float>(p_ctr(1));
214  model_coefficients[2] = static_cast<float>(p_ctr(2));
215 
216  // Semi-major axis length 'a' (along the local x-axis)
217  model_coefficients[3] = static_cast<float>(par_a);
218  // Semi-minor axis length 'b' (along the local y-axis)
219  model_coefficients[4] = static_cast<float>(par_b);
220 
221  // Ellipse normal
222  model_coefficients[5] = static_cast<float>(ellipse_normal[0]);
223  model_coefficients[6] = static_cast<float>(ellipse_normal[1]);
224  model_coefficients[7] = static_cast<float>(ellipse_normal[2]);
225 
226  // Retrieve the ellipse point at the tilt angle t (par_t), along the local x-axis
227  const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, par_h, par_k, par_t).finished();
228  Eigen::Vector3f p_th_(0.0, 0.0, 0.0);
229  get_ellipse_point(params, par_t, p_th_(0), p_th_(1));
230 
231  // Redefinition of the x-axis of the ellipse's local reference frame
232  x_axis = (Rot * p_th_).normalized();
233  model_coefficients[8] = static_cast<float>(x_axis[0]);
234  model_coefficients[9] = static_cast<float>(x_axis[1]);
235  model_coefficients[10] = static_cast<float>(x_axis[2]);
236 
237 
238  PCL_DEBUG ("[pcl::SampleConsensusModelEllipse3D::computeModelCoefficients] Model is (%g,%g,%g,%g,%g,%g,%g,%g,%g,%g,%g).\n",
239  model_coefficients[0], model_coefficients[1], model_coefficients[2], model_coefficients[3],
240  model_coefficients[4], model_coefficients[5], model_coefficients[6], model_coefficients[7],
241  model_coefficients[8], model_coefficients[9], model_coefficients[10]);
242  return (true);
243 }
244 
245 
246 //////////////////////////////////////////////////////////////////////////
247 template <typename PointT> void
248 pcl::SampleConsensusModelEllipse3D<PointT>::getDistancesToModel (const Eigen::VectorXf &model_coefficients, std::vector<double> &distances) const
249 {
250  // Check if the model is valid given the user constraints
251  if (!isModelValid (model_coefficients))
252  {
253  distances.clear ();
254  return;
255  }
256  distances.resize (indices_->size ());
257 
258  // c : Ellipse Center
259  const Eigen::Vector3f c(model_coefficients[0], model_coefficients[1], model_coefficients[2]);
260  // n : Ellipse (Plane) Normal
261  const Eigen::Vector3f n_axis(model_coefficients[5], model_coefficients[6], model_coefficients[7]);
262  // x : Ellipse (Plane) X-Axis
263  const Eigen::Vector3f x_axis(model_coefficients[8], model_coefficients[9], model_coefficients[10]);
264  // y : Ellipse (Plane) Y-Axis
265  const Eigen::Vector3f y_axis = n_axis.cross(x_axis).normalized();
266  // a : Ellipse semi-major axis (X) length
267  const float par_a(model_coefficients[3]);
268  // b : Ellipse semi-minor axis (Y) length
269  const float par_b(model_coefficients[4]);
270 
271  // Compute the rotation matrix and its transpose
272  const Eigen::Matrix3f Rot = (Eigen::Matrix3f(3,3)
273  << x_axis(0), y_axis(0), n_axis(0),
274  x_axis(1), y_axis(1), n_axis(1),
275  x_axis(2), y_axis(2), n_axis(2))
276  .finished();
277  const Eigen::Matrix3f Rot_T = Rot.transpose();
278 
279  // Ellipse parameters
280  const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
281  float th_opt;
282 
283  // Iterate through the 3D points and calculate the distances from them to the ellipse
284  for (std::size_t i = 0; i < indices_->size (); ++i)
285  // Calculate the distance from the point to the ellipse:
286  // 1. calculate intersection point of the plane in which the ellipse lies and the
287  // line from the sample point with the direction of the plane normal (projected point)
288  // 2. calculate the intersection point of the line from the ellipse center to the projected point
289  // with the ellipse
290  // 3. calculate distance from corresponding point on the ellipse to the sample point
291  {
292  // p : Sample Point
293  const Eigen::Vector3f p((*input_)[(*indices_)[i]].x, (*input_)[(*indices_)[i]].y, (*input_)[(*indices_)[i]].z);
294 
295  // Local coordinates of sample point p
296  const Eigen::Vector3f p_ = Rot_T * (p - c);
297 
298  // k : Point on Ellipse
299  // Calculate the shortest distance from the point to the ellipse which is given by
300  // the norm of a vector that is normal to the ellipse tangent calculated at the
301  // point it intersects the tangent.
302  const Eigen::Vector2f distanceVector = dvec2ellipse(params, p_(0), p_(1), th_opt);
303 
304  distances[i] = distanceVector.norm();
305  }
306 }
307 
308 //////////////////////////////////////////////////////////////////////////
309 template <typename PointT> void
311  const Eigen::VectorXf &model_coefficients, const double threshold,
312  Indices &inliers)
313 {
314  inliers.clear();
315  error_sqr_dists_.clear();
316  // Check if the model is valid given the user constraints
317  if (!isModelValid (model_coefficients))
318  {
319  return;
320  }
321  inliers.reserve (indices_->size ());
322  error_sqr_dists_.reserve (indices_->size ());
323 
324  // c : Ellipse Center
325  const Eigen::Vector3f c(model_coefficients[0], model_coefficients[1], model_coefficients[2]);
326  // n : Ellipse (Plane) Normal
327  const Eigen::Vector3f n_axis(model_coefficients[5], model_coefficients[6], model_coefficients[7]);
328  // x : Ellipse (Plane) X-Axis
329  const Eigen::Vector3f x_axis(model_coefficients[8], model_coefficients[9], model_coefficients[10]);
330  // y : Ellipse (Plane) Y-Axis
331  const Eigen::Vector3f y_axis = n_axis.cross(x_axis).normalized();
332  // a : Ellipse semi-major axis (X) length
333  const float par_a(model_coefficients[3]);
334  // b : Ellipse semi-minor axis (Y) length
335  const float par_b(model_coefficients[4]);
336 
337  // Compute the rotation matrix and its transpose
338  const Eigen::Matrix3f Rot = (Eigen::Matrix3f(3,3)
339  << x_axis(0), y_axis(0), n_axis(0),
340  x_axis(1), y_axis(1), n_axis(1),
341  x_axis(2), y_axis(2), n_axis(2))
342  .finished();
343  const Eigen::Matrix3f Rot_T = Rot.transpose();
344 
345  const auto squared_threshold = threshold * threshold;
346  // Iterate through the 3d points and calculate the distances from them to the ellipse
347  for (std::size_t i = 0; i < indices_->size (); ++i)
348  {
349  // p : Sample Point
350  const Eigen::Vector3f p((*input_)[(*indices_)[i]].x, (*input_)[(*indices_)[i]].y, (*input_)[(*indices_)[i]].z);
351 
352  // Local coordinates of sample point p
353  const Eigen::Vector3f p_ = Rot_T * (p - c);
354 
355  // k : Point on Ellipse
356  // Calculate the shortest distance from the point to the ellipse which is given by
357  // the norm of a vector that is normal to the ellipse tangent calculated at the
358  // point it intersects the tangent.
359  const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
360  float th_opt;
361  const Eigen::Vector2f distanceVector = dvec2ellipse(params, p_(0), p_(1), th_opt);
362 
363  const double sqr_dist = distanceVector.squaredNorm();
364  if (sqr_dist < squared_threshold)
365  {
366  // Returns the indices of the points whose distances are smaller than the threshold
367  inliers.push_back ((*indices_)[i]);
368  error_sqr_dists_.push_back (sqr_dist);
369  }
370  }
371 }
372 
373 //////////////////////////////////////////////////////////////////////////
374 template <typename PointT> std::size_t
376  const Eigen::VectorXf &model_coefficients, const double threshold) const
377 {
378  // Check if the model is valid given the user constraints
379  if (!isModelValid (model_coefficients))
380  return (0);
381  std::size_t nr_p = 0;
382 
383  // c : Ellipse Center
384  const Eigen::Vector3f c(model_coefficients[0], model_coefficients[1], model_coefficients[2]);
385  // n : Ellipse (Plane) Normal
386  const Eigen::Vector3f n_axis(model_coefficients[5], model_coefficients[6], model_coefficients[7]);
387  // x : Ellipse (Plane) X-Axis
388  const Eigen::Vector3f x_axis(model_coefficients[8], model_coefficients[9], model_coefficients[10]);
389  // y : Ellipse (Plane) Y-Axis
390  const Eigen::Vector3f y_axis = n_axis.cross(x_axis).normalized();
391  // a : Ellipse semi-major axis (X) length
392  const float par_a(model_coefficients[3]);
393  // b : Ellipse semi-minor axis (Y) length
394  const float par_b(model_coefficients[4]);
395 
396  // Compute the rotation matrix and its transpose
397  const Eigen::Matrix3f Rot = (Eigen::Matrix3f(3,3)
398  << x_axis(0), y_axis(0), n_axis(0),
399  x_axis(1), y_axis(1), n_axis(1),
400  x_axis(2), y_axis(2), n_axis(2))
401  .finished();
402  const Eigen::Matrix3f Rot_T = Rot.transpose();
403 
404  const auto squared_threshold = threshold * threshold;
405  // Iterate through the 3d points and calculate the distances from them to the ellipse
406  for (std::size_t i = 0; i < indices_->size (); ++i)
407  {
408  // p : Sample Point
409  const Eigen::Vector3f p((*input_)[(*indices_)[i]].x, (*input_)[(*indices_)[i]].y, (*input_)[(*indices_)[i]].z);
410 
411  // Local coordinates of sample point p
412  const Eigen::Vector3f p_ = Rot_T * (p - c);
413 
414  // k : Point on Ellipse
415  // Calculate the shortest distance from the point to the ellipse which is given by
416  // the norm of a vector that is normal to the ellipse tangent calculated at the
417  // point it intersects the tangent.
418  const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
419  float th_opt;
420  const Eigen::Vector2f distanceVector = dvec2ellipse(params, p_(0), p_(1), th_opt);
421 
422  if (distanceVector.squaredNorm() < squared_threshold)
423  nr_p++;
424  }
425  return (nr_p);
426 }
427 
428 //////////////////////////////////////////////////////////////////////////
429 template <typename PointT> void
431  const Indices &inliers,
432  const Eigen::VectorXf &model_coefficients,
433  Eigen::VectorXf &optimized_coefficients) const
434 {
435  optimized_coefficients = model_coefficients;
436 
437  // Needs a set of valid model coefficients
438  if (!isModelValid (model_coefficients))
439  {
440  PCL_ERROR ("[pcl::SampleConsensusModelEllipse3D::optimizeModelCoefficients] Given model is invalid!\n");
441  return;
442  }
443 
444  // Need more than the minimum sample size to make a difference
445  if (inliers.size () <= sample_size_)
446  {
447  PCL_ERROR ("[pcl::SampleConsensusModelEllipse3D::optimizeModelCoefficients] Not enough inliers to refine/optimize the model's coefficients (%lu)! Returning the same coefficients.\n", inliers.size ());
448  return;
449  }
450 
451  OptimizationFunctor functor(this, inliers);
452  Eigen::NumericalDiff<OptimizationFunctor> num_diff(functor);
453  Eigen::LevenbergMarquardt<Eigen::NumericalDiff<OptimizationFunctor>, double> lm(num_diff);
454  Eigen::VectorXd coeff;
455  int info = lm.minimize(coeff);
456  for (Eigen::Index i = 0; i < coeff.size (); ++i)
457  optimized_coefficients[i] = static_cast<float> (coeff[i]);
458 
459  // Compute the L2 norm of the residuals
460  PCL_DEBUG ("[pcl::SampleConsensusModelEllipse3D::optimizeModelCoefficients] LM solver finished with exit code %i, having a residual norm of %g. \nInitial solution: %g %g %g %g %g %g %g %g %g %g %g\nFinal solution: %g %g %g %g %g %g %g %g %g %g %g\n",
461  info, lm.fvec.norm (),
462 
463  model_coefficients[0],
464  model_coefficients[1],
465  model_coefficients[2],
466  model_coefficients[3],
467  model_coefficients[4],
468  model_coefficients[5],
469  model_coefficients[6],
470  model_coefficients[7],
471  model_coefficients[8],
472  model_coefficients[9],
473  model_coefficients[10],
474 
475  optimized_coefficients[0],
476  optimized_coefficients[1],
477  optimized_coefficients[2],
478  optimized_coefficients[3],
479  optimized_coefficients[4],
480  optimized_coefficients[5],
481  optimized_coefficients[6],
482  optimized_coefficients[7],
483  optimized_coefficients[8],
484  optimized_coefficients[9],
485  optimized_coefficients[10]);
486 }
487 
488 //////////////////////////////////////////////////////////////////////////
489 template <typename PointT> void
491  const Indices &inliers, const Eigen::VectorXf &model_coefficients,
492  PointCloud &projected_points, bool copy_data_fields) const
493 {
494  // Needs a valid set of model coefficients
495  if (!isModelValid (model_coefficients))
496  {
497  PCL_ERROR ("[pcl::SampleConsensusModelEllipse3D::projectPoints] Given model is invalid!\n");
498  return;
499  }
500 
501  projected_points.header = input_->header;
502  projected_points.is_dense = input_->is_dense;
503 
504  // Copy all the data fields from the input cloud to the projected one?
505  if (copy_data_fields)
506  {
507  // Allocate enough space and copy the basics
508  projected_points.resize (input_->size ());
509  projected_points.width = input_->width;
510  projected_points.height = input_->height;
511 
512  using FieldList = typename pcl::traits::fieldList<PointT>::type;
513  // Iterate over each point
514  for (std::size_t i = 0; i < projected_points.size(); ++i)
515  {
516  // Iterate over each dimension
517  pcl::for_each_type<FieldList>(NdConcatenateFunctor<PointT, PointT>((*input_)[i], projected_points[i]));
518  }
519 
520  // c : Ellipse Center
521  const Eigen::Vector3f c(model_coefficients[0], model_coefficients[1], model_coefficients[2]);
522  // n : Ellipse (Plane) Normal
523  const Eigen::Vector3f n_axis(model_coefficients[5], model_coefficients[6], model_coefficients[7]);
524  // x : Ellipse (Plane) X-Axis
525  const Eigen::Vector3f x_axis(model_coefficients[8], model_coefficients[9], model_coefficients[10]);
526  // y : Ellipse (Plane) Y-Axis
527  const Eigen::Vector3f y_axis = n_axis.cross(x_axis).normalized();
528  // a : Ellipse semi-major axis (X) length
529  const float par_a(model_coefficients[3]);
530  // b : Ellipse semi-minor axis (Y) length
531  const float par_b(model_coefficients[4]);
532 
533  // Compute the rotation matrix and its transpose
534  const Eigen::Matrix3f Rot = (Eigen::Matrix3f(3,3)
535  << x_axis(0), y_axis(0), n_axis(0),
536  x_axis(1), y_axis(1), n_axis(1),
537  x_axis(2), y_axis(2), n_axis(2))
538  .finished();
539  const Eigen::Matrix3f Rot_T = Rot.transpose();
540 
541  // Iterate through the 3d points and calculate the distances from them to the plane
542  for (std::size_t i = 0; i < inliers.size (); ++i)
543  {
544  // p : Sample Point
545  const Eigen::Vector3f p((*input_)[(*indices_)[i]].x, (*input_)[(*indices_)[i]].y, (*input_)[(*indices_)[i]].z);
546 
547  // Local coordinates of sample point p
548  const Eigen::Vector3f p_ = Rot_T * (p - c);
549 
550  // k : Point on Ellipse
551  // Calculate the shortest distance from the point to the ellipse which is given by
552  // the norm of a vector that is normal to the ellipse tangent calculated at the
553  // point it intersects the tangent.
554  const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
555  float th_opt;
556  dvec2ellipse(params, p_(0), p_(1), th_opt);
557 
558  // Retrieve the ellipse point at the tilt angle t, along the local x-axis
559  Eigen::Vector3f k_(0.0, 0.0, 0.0);
560  get_ellipse_point(params, th_opt, k_[0], k_[1]);
561 
562  const Eigen::Vector3f k = c + Rot * k_;
563 
564  projected_points[i].x = static_cast<float> (k[0]);
565  projected_points[i].y = static_cast<float> (k[1]);
566  projected_points[i].z = static_cast<float> (k[2]);
567  }
568  }
569  else
570  {
571  // Allocate enough space and copy the basics
572  projected_points.resize (inliers.size ());
573  projected_points.width = inliers.size ();
574  projected_points.height = 1;
575 
576  using FieldList = typename pcl::traits::fieldList<PointT>::type;
577  // Iterate over each point
578  for (std::size_t i = 0; i < inliers.size (); ++i)
579  // Iterate over each dimension
580  pcl::for_each_type <FieldList> (NdConcatenateFunctor <PointT, PointT> ((*input_)[inliers[i]], projected_points[i]));
581 
582  // c : Ellipse Center
583  const Eigen::Vector3f c(model_coefficients[0], model_coefficients[1], model_coefficients[2]);
584  // n : Ellipse (Plane) Normal
585  const Eigen::Vector3f n_axis(model_coefficients[5], model_coefficients[6], model_coefficients[7]);
586  // x : Ellipse (Plane) X-Axis
587  const Eigen::Vector3f x_axis(model_coefficients[8], model_coefficients[9], model_coefficients[10]);
588  // y : Ellipse (Plane) Y-Axis
589  const Eigen::Vector3f y_axis = n_axis.cross(x_axis).normalized();
590  // a : Ellipse semi-major axis (X) length
591  const float par_a(model_coefficients[3]);
592  // b : Ellipse semi-minor axis (Y) length
593  const float par_b(model_coefficients[4]);
594 
595  // Compute the rotation matrix and its transpose
596  const Eigen::Matrix3f Rot = (Eigen::Matrix3f(3,3)
597  << x_axis(0), y_axis(0), n_axis(0),
598  x_axis(1), y_axis(1), n_axis(1),
599  x_axis(2), y_axis(2), n_axis(2))
600  .finished();
601  const Eigen::Matrix3f Rot_T = Rot.transpose();
602 
603  // Iterate through the 3d points and calculate the distances from them to the plane
604  for (std::size_t i = 0; i < inliers.size (); ++i)
605  {
606  // p : Sample Point
607  const Eigen::Vector3f p((*input_)[(*indices_)[i]].x, (*input_)[(*indices_)[i]].y, (*input_)[(*indices_)[i]].z);
608 
609  // Local coordinates of sample point p
610  const Eigen::Vector3f p_ = Rot_T * (p - c);
611 
612  // k : Point on Ellipse
613  // Calculate the shortest distance from the point to the ellipse which is given by
614  // the norm of a vector that is normal to the ellipse tangent calculated at the
615  // point it intersects the tangent.
616  const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
617  float th_opt;
618  dvec2ellipse(params, p_(0), p_(1), th_opt);
619 
620  // Retrieve the ellipse point at the tilt angle t, along the local x-axis
621  //// model_coefficients[5] = static_cast<float>(par_t);
622  Eigen::Vector3f k_(0.0, 0.0, 0.0);
623  get_ellipse_point(params, th_opt, k_[0], k_[1]);
624 
625  const Eigen::Vector3f k = c + Rot * k_;
626 
627  projected_points[i].x = static_cast<float> (k[0]);
628  projected_points[i].y = static_cast<float> (k[1]);
629  projected_points[i].z = static_cast<float> (k[2]);
630  }
631  }
632 }
633 
634 //////////////////////////////////////////////////////////////////////////
635 template <typename PointT> bool
637  const std::set<index_t> &indices,
638  const Eigen::VectorXf &model_coefficients,
639  const double threshold) const
640 {
641  // Needs a valid model coefficients
642  if (!isModelValid (model_coefficients))
643  {
644  PCL_ERROR ("[pcl::SampleConsensusModelEllipse3D::doSamplesVerifyModel] Given model is invalid!\n");
645  return (false);
646  }
647 
648  // c : Ellipse Center
649  const Eigen::Vector3f c(model_coefficients[0], model_coefficients[1], model_coefficients[2]);
650  // n : Ellipse (Plane) Normal
651  const Eigen::Vector3f n_axis(model_coefficients[5], model_coefficients[6], model_coefficients[7]);
652  // x : Ellipse (Plane) X-Axis
653  const Eigen::Vector3f x_axis(model_coefficients[8], model_coefficients[9], model_coefficients[10]);
654  // y : Ellipse (Plane) Y-Axis
655  const Eigen::Vector3f y_axis = n_axis.cross(x_axis).normalized();
656  // a : Ellipse semi-major axis (X) length
657  const float par_a(model_coefficients[3]);
658  // b : Ellipse semi-minor axis (Y) length
659  const float par_b(model_coefficients[4]);
660 
661  // Compute the rotation matrix and its transpose
662  const Eigen::Matrix3f Rot = (Eigen::Matrix3f(3,3)
663  << x_axis(0), y_axis(0), n_axis(0),
664  x_axis(1), y_axis(1), n_axis(1),
665  x_axis(2), y_axis(2), n_axis(2))
666  .finished();
667  const Eigen::Matrix3f Rot_T = Rot.transpose();
668 
669  const auto squared_threshold = threshold * threshold;
670  for (const auto &index : indices)
671  {
672  // p : Sample Point
673  const Eigen::Vector3f p((*input_)[index].x, (*input_)[index].y, (*input_)[index].z);
674 
675  // Local coordinates of sample point p
676  const Eigen::Vector3f p_ = Rot_T * (p - c);
677 
678  // k : Point on Ellipse
679  // Calculate the shortest distance from the point to the ellipse which is given by
680  // the norm of a vector that is normal to the ellipse tangent calculated at the
681  // point it intersects the tangent.
682  const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
683  float th_opt;
684  const Eigen::Vector2f distanceVector = dvec2ellipse(params, p_(0), p_(1), th_opt);
685 
686  if (distanceVector.squaredNorm() > squared_threshold)
687  return (false);
688  }
689  return (true);
690 }
691 
692 //////////////////////////////////////////////////////////////////////////
693 template <typename PointT> bool
694 pcl::SampleConsensusModelEllipse3D<PointT>::isModelValid (const Eigen::VectorXf &model_coefficients) const
695 {
696  if (!SampleConsensusModel<PointT>::isModelValid (model_coefficients))
697  return (false);
698 
699  if (radius_min_ != std::numeric_limits<double>::lowest() && (model_coefficients[3] < radius_min_ || model_coefficients[4] < radius_min_))
700  {
701  PCL_DEBUG ("[pcl::SampleConsensusModelEllipse3D::isModelValid] Semi-minor axis OR semi-major axis (radii) of ellipse is/are too small: should be larger than %g, but are {%g, %g}.\n",
702  radius_min_, model_coefficients[3], model_coefficients[4]);
703  return (false);
704  }
705  if (radius_max_ != std::numeric_limits<double>::max() && (model_coefficients[3] > radius_max_ || model_coefficients[4] > radius_max_))
706  {
707  PCL_DEBUG ("[pcl::SampleConsensusModelEllipse3D::isModelValid] Semi-minor axis OR semi-major axis (radii) of ellipse is/are too big: should be smaller than %g, but are {%g, %g}.\n",
708  radius_max_, model_coefficients[3], model_coefficients[4]);
709  return (false);
710  }
711 
712  return (true);
713 }
714 
715 
716 
717 //////////////////////////////////////////////////////////////////////////
718 template <typename PointT>
720  const Eigen::VectorXf& par, float th, float& x, float& y)
721 {
722  /*
723  * Calculates a point on the ellipse model 'par' using the angle 'th'.
724  */
725 
726  // Parametric eq.params
727  const float par_a(par[0]);
728  const float par_b(par[1]);
729  const float par_h(par[2]);
730  const float par_k(par[3]);
731  const float par_t(par[4]);
732 
733  x = par_h + std::cos(par_t) * par_a * std::cos(th) -
734  std::sin(par_t) * par_b * std::sin(th);
735  y = par_k + std::sin(par_t) * par_a * std::cos(th) +
736  std::cos(par_t) * par_b * std::sin(th);
737 
738  return;
739 }
740 
741 //////////////////////////////////////////////////////////////////////////
742 template <typename PointT>
744  const Eigen::VectorXf& par, float u, float v, float& th_opt)
745 {
746  /*
747  * Minimum distance vector from point p=(u,v) to the ellipse model 'par'.
748  */
749 
750  // Parametric eq.params
751  // (par_a, par_b, and par_t do not need to be declared)
752  const float par_h = par[2];
753  const float par_k = par[3];
754 
755  const Eigen::Vector2f center(par_h, par_k);
756  Eigen::Vector2f p(u, v);
757  p -= center;
758 
759  // Local x-axis of the ellipse
760  Eigen::Vector2f x_axis(0.0, 0.0);
761  get_ellipse_point(par, 0.0, x_axis(0), x_axis(1));
762  x_axis -= center;
763 
764  // Local y-axis of the ellipse
765  Eigen::Vector2f y_axis(0.0, 0.0);
766  get_ellipse_point(par, M_PI / 2.0, y_axis(0), y_axis(1));
767  y_axis -= center;
768 
769  // Convert the point p=(u,v) to local ellipse coordinates
770  const float x_proj = p.dot(x_axis) / x_axis.norm();
771  const float y_proj = p.dot(y_axis) / y_axis.norm();
772 
773  // Find the ellipse quandrant to where the point p=(u,v) belongs,
774  // and limit the search interval to 'th_min' and 'th_max'.
775  float th_min(0.0), th_max(0.0);
776  const float th = std::atan2(y_proj, x_proj);
777 
778  if (-M_PI <= th && th < -M_PI / 2.0) {
779  // Q3
780  th_min = -M_PI;
781  th_max = -M_PI / 2.0;
782  }
783  if (-M_PI / 2.0 <= th && th < 0.0) {
784  // Q4
785  th_min = -M_PI / 2.0;
786  th_max = 0.0;
787  }
788  if (0.0 <= th && th < M_PI / 2.0) {
789  // Q1
790  th_min = 0.0;
791  th_max = M_PI / 2.0;
792  }
793  if (M_PI / 2.0 <= th && th <= M_PI) {
794  // Q2
795  th_min = M_PI / 2.0;
796  th_max = M_PI;
797  }
798 
799  // Use an unconstrained line search optimizer to find the optimal th_opt
800  th_opt = golden_section_search(par, u, v, th_min, th_max, 1.e-3);
801 
802  // Distance vector from a point (u,v) to a given point in the ellipse model 'par' at an angle 'th_opt'.
803  float x(0.0), y(0.0);
804  get_ellipse_point(par, th_opt, x, y);
805  Eigen::Vector2f distanceVector(u - x, v - y);
806  return distanceVector;
807 }
808 
809 //////////////////////////////////////////////////////////////////////////
810 template <typename PointT>
812  const Eigen::VectorXf& par,
813  float u,
814  float v,
815  float th_min,
816  float th_max,
817  float epsilon)
818 {
819  /*
820  * Golden section search
821  */
822 
823  constexpr float phi(1.61803398874989484820f); // Golden ratio
824 
825  // tl (theta lower bound), tu (theta upper bound)
826  float tl(th_min), tu(th_max);
827  float ta = tl + (tu - tl) * (1 - 1 / phi);
828  float tb = tl + (tu - tl) * 1 / phi;
829 
830  while ((tu - tl) > epsilon) {
831 
832  // theta a
833  float x_a(0.0), y_a(0.0);
834  get_ellipse_point(par, ta, x_a, y_a);
835  float squared_dist_ta = (u - x_a) * (u - x_a) + (v - y_a) * (v - y_a);
836 
837  // theta b
838  float x_b(0.0), y_b(0.0);
839  get_ellipse_point(par, tb, x_b, y_b);
840  float squared_dist_tb = (u - x_b) * (u - x_b) + (v - y_b) * (v - y_b);
841 
842  if (squared_dist_ta < squared_dist_tb) {
843  tu = tb;
844  tb = ta;
845  ta = tl + (tu - tl) * (1 - 1 / phi);
846  }
847  else if (squared_dist_ta > squared_dist_tb) {
848  tl = ta;
849  ta = tb;
850  tb = tl + (tu - tl) * 1 / phi;
851  }
852  else {
853  tl = ta;
854  tu = tb;
855  ta = tl + (tu - tl) * (1 - 1 / phi);
856  tb = tl + (tu - tl) * 1 / phi;
857  }
858  }
859  return (tl + tu) / 2.0;
860 }
861 
862 
863 #define PCL_INSTANTIATE_SampleConsensusModelEllipse3D(T) template class PCL_EXPORTS pcl::SampleConsensusModelEllipse3D<T>;
PointCloud represents the base class in PCL for storing collections of 3D points.
Definition: point_cloud.h:173
bool is_dense
True if no points are invalid (e.g., have NaN or Inf values in any of their floating point fields).
Definition: point_cloud.h:403
void resize(std::size_t count)
Resizes the container to contain count elements.
Definition: point_cloud.h:462
std::uint32_t width
The point cloud width (if organized as an image-structure).
Definition: point_cloud.h:398
pcl::PCLHeader header
The point cloud header.
Definition: point_cloud.h:392
std::uint32_t height
The point cloud height (if organized as an image-structure).
Definition: point_cloud.h:400
std::size_t size() const
Definition: point_cloud.h:443
SampleConsensusModelEllipse3D defines a model for 3D ellipse segmentation.
void optimizeModelCoefficients(const Indices &inliers, const Eigen::VectorXf &model_coefficients, Eigen::VectorXf &optimized_coefficients) const override
Recompute the 3d ellipse coefficients using the given inlier set and return them to the user.
std::size_t countWithinDistance(const Eigen::VectorXf &model_coefficients, const double threshold) const override
Count all the points which respect the given model coefficients as inliers.
void projectPoints(const Indices &inliers, const Eigen::VectorXf &model_coefficients, PointCloud &projected_points, bool copy_data_fields=true) const override
Create a new point cloud with inliers projected onto the 3d ellipse model.
bool computeModelCoefficients(const Indices &samples, Eigen::VectorXf &model_coefficients) const override
Check whether the given index samples can form a valid 3D ellipse model, compute the model coefficien...
bool doSamplesVerifyModel(const std::set< index_t > &indices, const Eigen::VectorXf &model_coefficients, const double threshold) const override
Verify whether a subset of indices verifies the given 3d ellipse model coefficients.
void selectWithinDistance(const Eigen::VectorXf &model_coefficients, const double threshold, Indices &inliers) override
Compute all distances from the cloud data to a given 3D ellipse model.
bool isSampleGood(const Indices &samples) const override
Check if a sample of indices results in a good sample of points indices.
bool isModelValid(const Eigen::VectorXf &model_coefficients) const override
Check whether a model is valid given the user constraints.
void getDistancesToModel(const Eigen::VectorXf &model_coefficients, std::vector< double > &distances) const override
Compute all distances from the cloud data to a given 3D ellipse model.
SampleConsensusModel represents the base model class.
Definition: sac_model.h:71
IndicesAllocator<> Indices
Type used for indices in PCL.
Definition: types.h:133
#define M_PI
Definition: pcl_macros.h:201
Helper functor structure for concatenate.
Definition: concatenate.h:50