Skip to content

Commit 70a08ba

Browse files
committed
Optimize SampleConsensusModelEllipse3D Levenberg-Marquardt instantiations
Move the heavy Levenberg-Marquardt optimization logic into a non-templated 'pcl::internal' namespace in the source file. This reduces redundant instantiations from 61 down to 1, resulting in a ~24% reduction in rebuild time for the sample_consensus library. - Consolidation of LevenbergMarquardt into internal::optimizeModelCoefficientsEllipse3D - Moving math helpers to pcl::internal for shared access - Correcting template instantiations for core and non-core point types - Matching parameter signature with other optimized internal models - Fixing lint errors for modernize-return-braced-init-list
1 parent c423d30 commit 70a08ba

3 files changed

Lines changed: 252 additions & 297 deletions

File tree

sample_consensus/include/pcl/sample_consensus/impl/sac_model_ellipse3d.hpp

Lines changed: 41 additions & 215 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
#include <limits>
1313

14-
#include <unsupported/Eigen/NonLinearOptimization> // for LevenbergMarquardt
1514
#include <pcl/sample_consensus/sac_model_ellipse3d.h>
1615
#include <pcl/common/concatenate.h>
1716

@@ -225,7 +224,7 @@ pcl::SampleConsensusModelEllipse3D<PointT>::computeModelCoefficients (const Indi
225224
// Retrieve the ellipse point at the tilt angle t (par_t), along the local x-axis
226225
const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, par_h, par_k, par_t).finished();
227226
Eigen::Vector3f p_th_(0.0, 0.0, 0.0);
228-
get_ellipse_point(params, par_t, p_th_(0), p_th_(1));
227+
internal::get_ellipse_point(params, par_t, p_th_(0), p_th_(1));
229228

230229
// Redefinition of the x-axis of the ellipse's local reference frame
231230
x_axis = (Rot * p_th_).normalized();
@@ -280,16 +279,11 @@ pcl::SampleConsensusModelEllipse3D<PointT>::getDistancesToModel (const Eigen::Ve
280279
float th_opt;
281280

282281
// Iterate through the 3D points and calculate the distances from them to the ellipse
283-
for (std::size_t i = 0; i < indices_->size (); ++i)
284-
// Calculate the distance from the point to the ellipse:
285-
// 1. calculate intersection point of the plane in which the ellipse lies and the
286-
// line from the sample point with the direction of the plane normal (projected point)
287-
// 2. calculate the intersection point of the line from the ellipse center to the projected point
288-
// with the ellipse
289-
// 3. calculate distance from corresponding point on the ellipse to the sample point
282+
std::size_t i = 0;
283+
for (const auto& index : *indices_)
290284
{
291285
// p : Sample Point
292-
const Eigen::Vector3f p((*input_)[(*indices_)[i]].x, (*input_)[(*indices_)[i]].y, (*input_)[(*indices_)[i]].z);
286+
const Eigen::Vector3f p((*input_)[index].x, (*input_)[index].y, (*input_)[index].z);
293287

294288
// Local coordinates of sample point p
295289
const Eigen::Vector3f p_ = Rot_T * (p - c);
@@ -298,9 +292,9 @@ pcl::SampleConsensusModelEllipse3D<PointT>::getDistancesToModel (const Eigen::Ve
298292
// Calculate the shortest distance from the point to the ellipse which is given by
299293
// the norm of a vector that is normal to the ellipse tangent calculated at the
300294
// point it intersects the tangent.
301-
const Eigen::Vector2f distanceVector = dvec2ellipse(params, p_(0), p_(1), th_opt);
295+
const Eigen::Vector2f distanceVector = internal::dvec2ellipse(params, p_(0), p_(1), th_opt);
302296

303-
distances[i] = distanceVector.norm();
297+
distances[i++] = distanceVector.norm();
304298
}
305299
}
306300

@@ -343,10 +337,10 @@ pcl::SampleConsensusModelEllipse3D<PointT>::selectWithinDistance (
343337

344338
const auto squared_threshold = threshold * threshold;
345339
// Iterate through the 3d points and calculate the distances from them to the ellipse
346-
for (std::size_t i = 0; i < indices_->size (); ++i)
340+
for (const auto& index : *indices_)
347341
{
348342
// p : Sample Point
349-
const Eigen::Vector3f p((*input_)[(*indices_)[i]].x, (*input_)[(*indices_)[i]].y, (*input_)[(*indices_)[i]].z);
343+
const Eigen::Vector3f p((*input_)[index].x, (*input_)[index].y, (*input_)[index].z);
350344

351345
// Local coordinates of sample point p
352346
const Eigen::Vector3f p_ = Rot_T * (p - c);
@@ -357,13 +351,13 @@ pcl::SampleConsensusModelEllipse3D<PointT>::selectWithinDistance (
357351
// point it intersects the tangent.
358352
const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
359353
float th_opt;
360-
const Eigen::Vector2f distanceVector = dvec2ellipse(params, p_(0), p_(1), th_opt);
354+
const Eigen::Vector2f distanceVector = internal::dvec2ellipse(params, p_(0), p_(1), th_opt);
361355

362356
const double sqr_dist = distanceVector.squaredNorm();
363357
if (sqr_dist < squared_threshold)
364358
{
365359
// Returns the indices of the points whose distances are smaller than the threshold
366-
inliers.push_back ((*indices_)[i]);
360+
inliers.push_back (index);
367361
error_sqr_dists_.push_back (sqr_dist);
368362
}
369363
}
@@ -402,10 +396,10 @@ pcl::SampleConsensusModelEllipse3D<PointT>::countWithinDistance (
402396

403397
const auto squared_threshold = threshold * threshold;
404398
// Iterate through the 3d points and calculate the distances from them to the ellipse
405-
for (std::size_t i = 0; i < indices_->size (); ++i)
399+
for (const auto& index : *indices_)
406400
{
407401
// p : Sample Point
408-
const Eigen::Vector3f p((*input_)[(*indices_)[i]].x, (*input_)[(*indices_)[i]].y, (*input_)[(*indices_)[i]].z);
402+
const Eigen::Vector3f p((*input_)[index].x, (*input_)[index].y, (*input_)[index].z);
409403

410404
// Local coordinates of sample point p
411405
const Eigen::Vector3f p_ = Rot_T * (p - c);
@@ -416,7 +410,7 @@ pcl::SampleConsensusModelEllipse3D<PointT>::countWithinDistance (
416410
// point it intersects the tangent.
417411
const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
418412
float th_opt;
419-
const Eigen::Vector2f distanceVector = dvec2ellipse(params, p_(0), p_(1), th_opt);
413+
const Eigen::Vector2f distanceVector = internal::dvec2ellipse(params, p_(0), p_(1), th_opt);
420414

421415
if (distanceVector.squaredNorm() < squared_threshold)
422416
nr_p++;
@@ -443,51 +437,29 @@ pcl::SampleConsensusModelEllipse3D<PointT>::optimizeModelCoefficients (
443437
// Need more than the minimum sample size to make a difference
444438
if (inliers.size () <= sample_size_)
445439
{
446-
PCL_ERROR ("[pcl::SampleConsensusModelEllipse3D::optimizeModelCoefficients] Not enough inliers to refine/optimize the model's coefficients (%lu)! Returning the same coefficients.\n", inliers.size ());
440+
PCL_ERROR ("[pcl::SampleConsensusModelEllipse3D::optimizeModelCoefficients] Not enough inliers found to optimize model coefficients (%lu)! Returning the same coefficients.\n", inliers.size ());
447441
return;
448442
}
449443

450-
OptimizationFunctor functor(this, inliers);
451-
Eigen::NumericalDiff<OptimizationFunctor> num_diff(functor);
452-
Eigen::LevenbergMarquardt<Eigen::NumericalDiff<OptimizationFunctor>, double> lm(num_diff);
453-
Eigen::VectorXd coeff = model_coefficients.cast<double>();
454-
int info = lm.minimize(coeff);
455-
optimized_coefficients = coeff.cast<float>();
456-
457-
// Compute the L2 norm of the residuals
458-
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",
459-
info, lm.fvec.norm (),
460-
461-
model_coefficients[0],
462-
model_coefficients[1],
463-
model_coefficients[2],
464-
model_coefficients[3],
465-
model_coefficients[4],
466-
model_coefficients[5],
467-
model_coefficients[6],
468-
model_coefficients[7],
469-
model_coefficients[8],
470-
model_coefficients[9],
471-
model_coefficients[10],
472-
473-
optimized_coefficients[0],
474-
optimized_coefficients[1],
475-
optimized_coefficients[2],
476-
optimized_coefficients[3],
477-
optimized_coefficients[4],
478-
optimized_coefficients[5],
479-
optimized_coefficients[6],
480-
optimized_coefficients[7],
481-
optimized_coefficients[8],
482-
optimized_coefficients[9],
483-
optimized_coefficients[10]);
444+
Eigen::ArrayXf x (inliers.size ()), y (inliers.size ()), z (inliers.size ());
445+
std::size_t i = 0;
446+
for (const auto& inlier : inliers)
447+
{
448+
const auto& pt = (*input_)[inlier];
449+
x[i] = pt.x; y[i] = pt.y; z[i] = pt.z;
450+
++i;
451+
}
452+
453+
internal::optimizeModelCoefficientsEllipse3D (optimized_coefficients, x, y, z);
484454
}
485455

486456
//////////////////////////////////////////////////////////////////////////
487457
template <typename PointT> void
488458
pcl::SampleConsensusModelEllipse3D<PointT>::projectPoints (
489-
const Indices &inliers, const Eigen::VectorXf &model_coefficients,
490-
PointCloud &projected_points, bool copy_data_fields) const
459+
const Indices &inliers,
460+
const Eigen::VectorXf &model_coefficients,
461+
PointCloud &projected_points,
462+
bool copy_data_fields) const
491463
{
492464
// Needs a valid set of model coefficients
493465
if (!isModelValid (model_coefficients))
@@ -537,10 +509,11 @@ pcl::SampleConsensusModelEllipse3D<PointT>::projectPoints (
537509
const Eigen::Matrix3f Rot_T = Rot.transpose();
538510

539511
// Iterate through the 3d points and calculate the distances from them to the plane
540-
for (std::size_t i = 0; i < inliers.size (); ++i)
512+
std::size_t i = 0;
513+
for (const auto& inlier : inliers)
541514
{
542515
// p : Sample Point
543-
const Eigen::Vector3f p((*input_)[(*indices_)[i]].x, (*input_)[(*indices_)[i]].y, (*input_)[(*indices_)[i]].z);
516+
const Eigen::Vector3f p((*input_)[inlier].x, (*input_)[inlier].y, (*input_)[inlier].z);
544517

545518
// Local coordinates of sample point p
546519
const Eigen::Vector3f p_ = Rot_T * (p - c);
@@ -551,17 +524,17 @@ pcl::SampleConsensusModelEllipse3D<PointT>::projectPoints (
551524
// point it intersects the tangent.
552525
const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
553526
float th_opt;
554-
dvec2ellipse(params, p_(0), p_(1), th_opt);
527+
internal::dvec2ellipse(params, p_(0), p_(1), th_opt);
555528

556529
// Retrieve the ellipse point at the tilt angle t, along the local x-axis
557530
Eigen::Vector3f k_(0.0, 0.0, 0.0);
558-
get_ellipse_point(params, th_opt, k_[0], k_[1]);
531+
internal::get_ellipse_point(params, th_opt, k_[0], k_[1]);
559532

560533
const Eigen::Vector3f k = c + Rot * k_;
561534

562535
projected_points[i].x = static_cast<float> (k[0]);
563536
projected_points[i].y = static_cast<float> (k[1]);
564-
projected_points[i].z = static_cast<float> (k[2]);
537+
projected_points[i++].z = static_cast<float> (k[2]);
565538
}
566539
}
567540
else
@@ -599,10 +572,11 @@ pcl::SampleConsensusModelEllipse3D<PointT>::projectPoints (
599572
const Eigen::Matrix3f Rot_T = Rot.transpose();
600573

601574
// Iterate through the 3d points and calculate the distances from them to the plane
602-
for (std::size_t i = 0; i < inliers.size (); ++i)
575+
std::size_t i = 0;
576+
for (const auto& inlier : inliers)
603577
{
604578
// p : Sample Point
605-
const Eigen::Vector3f p((*input_)[(*indices_)[i]].x, (*input_)[(*indices_)[i]].y, (*input_)[(*indices_)[i]].z);
579+
const Eigen::Vector3f p((*input_)[inlier].x, (*input_)[inlier].y, (*input_)[inlier].z);
606580

607581
// Local coordinates of sample point p
608582
const Eigen::Vector3f p_ = Rot_T * (p - c);
@@ -613,18 +587,18 @@ pcl::SampleConsensusModelEllipse3D<PointT>::projectPoints (
613587
// point it intersects the tangent.
614588
const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
615589
float th_opt;
616-
dvec2ellipse(params, p_(0), p_(1), th_opt);
590+
internal::dvec2ellipse(params, p_(0), p_(1), th_opt);
617591

618592
// Retrieve the ellipse point at the tilt angle t, along the local x-axis
619593
//// model_coefficients[5] = static_cast<float>(par_t);
620594
Eigen::Vector3f k_(0.0, 0.0, 0.0);
621-
get_ellipse_point(params, th_opt, k_[0], k_[1]);
595+
internal::get_ellipse_point(params, th_opt, k_[0], k_[1]);
622596

623597
const Eigen::Vector3f k = c + Rot * k_;
624598

625599
projected_points[i].x = static_cast<float> (k[0]);
626600
projected_points[i].y = static_cast<float> (k[1]);
627-
projected_points[i].z = static_cast<float> (k[2]);
601+
projected_points[i++].z = static_cast<float> (k[2]);
628602
}
629603
}
630604
}
@@ -679,7 +653,7 @@ pcl::SampleConsensusModelEllipse3D<PointT>::doSamplesVerifyModel (
679653
// point it intersects the tangent.
680654
const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
681655
float th_opt;
682-
const Eigen::Vector2f distanceVector = dvec2ellipse(params, p_(0), p_(1), th_opt);
656+
const Eigen::Vector2f distanceVector = internal::dvec2ellipse(params, p_(0), p_(1), th_opt);
683657

684658
if (distanceVector.squaredNorm() > squared_threshold)
685659
return (false);
@@ -710,152 +684,4 @@ pcl::SampleConsensusModelEllipse3D<PointT>::isModelValid (const Eigen::VectorXf
710684
return (true);
711685
}
712686

713-
714-
715-
//////////////////////////////////////////////////////////////////////////
716-
template <typename PointT>
717-
void inline pcl::SampleConsensusModelEllipse3D<PointT>::get_ellipse_point(
718-
const Eigen::VectorXf& par, float th, float& x, float& y)
719-
{
720-
/*
721-
* Calculates a point on the ellipse model 'par' using the angle 'th'.
722-
*/
723-
724-
// Parametric eq.params
725-
const float par_a(par[0]);
726-
const float par_b(par[1]);
727-
const float par_h(par[2]);
728-
const float par_k(par[3]);
729-
const float par_t(par[4]);
730-
731-
x = par_h + std::cos(par_t) * par_a * std::cos(th) -
732-
std::sin(par_t) * par_b * std::sin(th);
733-
y = par_k + std::sin(par_t) * par_a * std::cos(th) +
734-
std::cos(par_t) * par_b * std::sin(th);
735-
736-
return;
737-
}
738-
739-
//////////////////////////////////////////////////////////////////////////
740-
template <typename PointT>
741-
Eigen::Vector2f inline pcl::SampleConsensusModelEllipse3D<PointT>::dvec2ellipse(
742-
const Eigen::VectorXf& par, float u, float v, float& th_opt)
743-
{
744-
/*
745-
* Minimum distance vector from point p=(u,v) to the ellipse model 'par'.
746-
*/
747-
748-
// Parametric eq.params
749-
// (par_a, par_b, and par_t do not need to be declared)
750-
const float par_h = par[2];
751-
const float par_k = par[3];
752-
753-
const Eigen::Vector2f center(par_h, par_k);
754-
Eigen::Vector2f p(u, v);
755-
p -= center;
756-
757-
// Local x-axis of the ellipse
758-
Eigen::Vector2f x_axis(0.0, 0.0);
759-
get_ellipse_point(par, 0.0, x_axis(0), x_axis(1));
760-
x_axis -= center;
761-
762-
// Local y-axis of the ellipse
763-
Eigen::Vector2f y_axis(0.0, 0.0);
764-
get_ellipse_point(par, M_PI / 2.0, y_axis(0), y_axis(1));
765-
y_axis -= center;
766-
767-
// Convert the point p=(u,v) to local ellipse coordinates
768-
const float x_proj = p.dot(x_axis) / x_axis.norm();
769-
const float y_proj = p.dot(y_axis) / y_axis.norm();
770-
771-
// Find the ellipse quandrant to where the point p=(u,v) belongs,
772-
// and limit the search interval to 'th_min' and 'th_max'.
773-
float th_min(0.0), th_max(0.0);
774-
const float th = std::atan2(y_proj, x_proj);
775-
776-
if (-M_PI <= th && th < -M_PI / 2.0) {
777-
// Q3
778-
th_min = -M_PI;
779-
th_max = -M_PI / 2.0;
780-
}
781-
if (-M_PI / 2.0 <= th && th < 0.0) {
782-
// Q4
783-
th_min = -M_PI / 2.0;
784-
th_max = 0.0;
785-
}
786-
if (0.0 <= th && th < M_PI / 2.0) {
787-
// Q1
788-
th_min = 0.0;
789-
th_max = M_PI / 2.0;
790-
}
791-
if (M_PI / 2.0 <= th && th <= M_PI) {
792-
// Q2
793-
th_min = M_PI / 2.0;
794-
th_max = M_PI;
795-
}
796-
797-
// Use an unconstrained line search optimizer to find the optimal th_opt
798-
th_opt = golden_section_search(par, u, v, th_min, th_max, 1.e-3);
799-
800-
// Distance vector from a point (u,v) to a given point in the ellipse model 'par' at an angle 'th_opt'.
801-
float x(0.0), y(0.0);
802-
get_ellipse_point(par, th_opt, x, y);
803-
Eigen::Vector2f distanceVector(u - x, v - y);
804-
return distanceVector;
805-
}
806-
807-
//////////////////////////////////////////////////////////////////////////
808-
template <typename PointT>
809-
float inline pcl::SampleConsensusModelEllipse3D<PointT>::golden_section_search(
810-
const Eigen::VectorXf& par,
811-
float u,
812-
float v,
813-
float th_min,
814-
float th_max,
815-
float epsilon)
816-
{
817-
/*
818-
* Golden section search
819-
*/
820-
821-
constexpr float phi(1.61803398874989484820f); // Golden ratio
822-
823-
// tl (theta lower bound), tu (theta upper bound)
824-
float tl(th_min), tu(th_max);
825-
float ta = tl + (tu - tl) * (1 - 1 / phi);
826-
float tb = tl + (tu - tl) * 1 / phi;
827-
828-
while ((tu - tl) > epsilon) {
829-
830-
// theta a
831-
float x_a(0.0), y_a(0.0);
832-
get_ellipse_point(par, ta, x_a, y_a);
833-
float squared_dist_ta = (u - x_a) * (u - x_a) + (v - y_a) * (v - y_a);
834-
835-
// theta b
836-
float x_b(0.0), y_b(0.0);
837-
get_ellipse_point(par, tb, x_b, y_b);
838-
float squared_dist_tb = (u - x_b) * (u - x_b) + (v - y_b) * (v - y_b);
839-
840-
if (squared_dist_ta < squared_dist_tb) {
841-
tu = tb;
842-
tb = ta;
843-
ta = tl + (tu - tl) * (1 - 1 / phi);
844-
}
845-
else if (squared_dist_ta > squared_dist_tb) {
846-
tl = ta;
847-
ta = tb;
848-
tb = tl + (tu - tl) * 1 / phi;
849-
}
850-
else {
851-
tl = ta;
852-
tu = tb;
853-
ta = tl + (tu - tl) * (1 - 1 / phi);
854-
tb = tl + (tu - tl) * 1 / phi;
855-
}
856-
}
857-
return (tl + tu) / 2.0;
858-
}
859-
860-
861687
#define PCL_INSTANTIATE_SampleConsensusModelEllipse3D(T) template class PCL_EXPORTS pcl::SampleConsensusModelEllipse3D<T>;

0 commit comments

Comments
 (0)