Skip to content

Commit ea89929

Browse files
committed
Optimize SampleConsensusModelEllipse3D build performance
Move the heavy LevenbergMarquardt optimization logic into a non-templated 'pcl::internal' namespace in the source file. This reduces the number of redundant Eigen::LevenbergMarquardt instantiations from 18+ to 1, significantly improving build times while preserving runtime performance by keeping hot-path helpers inline in the header.
1 parent c423d30 commit ea89929

3 files changed

Lines changed: 242 additions & 298 deletions

File tree

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

Lines changed: 29 additions & 212 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@
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>
16+
#include <pcl/common/io.h>
17+
#include <pcl/common/copy_point.h>
1718

1819
#include <Eigen/Eigenvalues>
1920

@@ -30,9 +31,9 @@ pcl::SampleConsensusModelEllipse3D<PointT>::isSampleGood (
3031
}
3132

3233
// Use three points out of the 6 samples
33-
const Eigen::Vector3d p0 ((*input_)[samples[0]].x, (*input_)[samples[0]].y, (*input_)[samples[0]].z);
34-
const Eigen::Vector3d p1 ((*input_)[samples[1]].x, (*input_)[samples[1]].y, (*input_)[samples[1]].z);
35-
const Eigen::Vector3d p2 ((*input_)[samples[2]].x, (*input_)[samples[2]].y, (*input_)[samples[2]].z);
34+
const Eigen::Vector3f p0 = (*input_)[samples[0]].getVector3fMap();
35+
const Eigen::Vector3f p1 = (*input_)[samples[1]].getVector3fMap();
36+
const Eigen::Vector3f p2 = (*input_)[samples[2]].getVector3fMap();
3637

3738
// Check if the squared norm of the cross-product is non-zero, otherwise
3839
// common_helper_vec, which plays an important role in computeModelCoefficients,
@@ -225,7 +226,7 @@ pcl::SampleConsensusModelEllipse3D<PointT>::computeModelCoefficients (const Indi
225226
// Retrieve the ellipse point at the tilt angle t (par_t), along the local x-axis
226227
const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, par_h, par_k, par_t).finished();
227228
Eigen::Vector3f p_th_(0.0, 0.0, 0.0);
228-
get_ellipse_point(params, par_t, p_th_(0), p_th_(1));
229+
internal::getEllipsePoint(params, par_t, p_th_(0), p_th_(1));
229230

230231
// Redefinition of the x-axis of the ellipse's local reference frame
231232
x_axis = (Rot * p_th_).normalized();
@@ -298,7 +299,7 @@ pcl::SampleConsensusModelEllipse3D<PointT>::getDistancesToModel (const Eigen::Ve
298299
// Calculate the shortest distance from the point to the ellipse which is given by
299300
// the norm of a vector that is normal to the ellipse tangent calculated at the
300301
// point it intersects the tangent.
301-
const Eigen::Vector2f distanceVector = dvec2ellipse(params, p_(0), p_(1), th_opt);
302+
const Eigen::Vector2f distanceVector = internal::dVec2Ellipse(params, p_(0), p_(1), th_opt);
302303

303304
distances[i] = distanceVector.norm();
304305
}
@@ -357,7 +358,7 @@ pcl::SampleConsensusModelEllipse3D<PointT>::selectWithinDistance (
357358
// point it intersects the tangent.
358359
const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
359360
float th_opt;
360-
const Eigen::Vector2f distanceVector = dvec2ellipse(params, p_(0), p_(1), th_opt);
361+
const Eigen::Vector2f distanceVector = internal::dVec2Ellipse(params, p_(0), p_(1), th_opt);
361362

362363
const double sqr_dist = distanceVector.squaredNorm();
363364
if (sqr_dist < squared_threshold)
@@ -416,7 +417,7 @@ pcl::SampleConsensusModelEllipse3D<PointT>::countWithinDistance (
416417
// point it intersects the tangent.
417418
const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
418419
float th_opt;
419-
const Eigen::Vector2f distanceVector = dvec2ellipse(params, p_(0), p_(1), th_opt);
420+
const Eigen::Vector2f distanceVector = internal::dVec2Ellipse(params, p_(0), p_(1), th_opt);
420421

421422
if (distanceVector.squaredNorm() < squared_threshold)
422423
nr_p++;
@@ -447,40 +448,14 @@ pcl::SampleConsensusModelEllipse3D<PointT>::optimizeModelCoefficients (
447448
return;
448449
}
449450

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]);
451+
Eigen::ArrayXf x (inliers.size ()), y (inliers.size ()), z (inliers.size ());
452+
for (std::size_t i = 0; i < inliers.size (); ++i)
453+
{
454+
const auto& pt = (*input_)[inliers[i]];
455+
x[i] = pt.x; y[i] = pt.y; z[i] = pt.z;
456+
}
457+
458+
internal::optimizeModelCoefficientsEllipse3D (x, y, z, model_coefficients, optimized_coefficients);
484459
}
485460

486461
//////////////////////////////////////////////////////////////////////////
@@ -507,13 +482,7 @@ pcl::SampleConsensusModelEllipse3D<PointT>::projectPoints (
507482
projected_points.width = input_->width;
508483
projected_points.height = input_->height;
509484

510-
using FieldList = typename pcl::traits::fieldList<PointT>::type;
511-
// Iterate over each point
512-
for (std::size_t i = 0; i < projected_points.size(); ++i)
513-
{
514-
// Iterate over each dimension
515-
pcl::for_each_type<FieldList>(NdConcatenateFunctor<PointT, PointT>((*input_)[i], projected_points[i]));
516-
}
485+
pcl::copyPointCloud (*input_, projected_points);
517486

518487
// c : Ellipse Center
519488
const Eigen::Vector3f c(model_coefficients[0], model_coefficients[1], model_coefficients[2]);
@@ -540,7 +509,7 @@ pcl::SampleConsensusModelEllipse3D<PointT>::projectPoints (
540509
for (std::size_t i = 0; i < inliers.size (); ++i)
541510
{
542511
// p : Sample Point
543-
const Eigen::Vector3f p((*input_)[(*indices_)[i]].x, (*input_)[(*indices_)[i]].y, (*input_)[(*indices_)[i]].z);
512+
const Eigen::Vector3f p = (*input_)[inliers[i]].getVector3fMap();
544513

545514
// Local coordinates of sample point p
546515
const Eigen::Vector3f p_ = Rot_T * (p - c);
@@ -551,17 +520,17 @@ pcl::SampleConsensusModelEllipse3D<PointT>::projectPoints (
551520
// point it intersects the tangent.
552521
const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
553522
float th_opt;
554-
dvec2ellipse(params, p_(0), p_(1), th_opt);
523+
internal::dVec2Ellipse(params, p_(0), p_(1), th_opt);
555524

556525
// Retrieve the ellipse point at the tilt angle t, along the local x-axis
557526
Eigen::Vector3f k_(0.0, 0.0, 0.0);
558-
get_ellipse_point(params, th_opt, k_[0], k_[1]);
527+
internal::getEllipsePoint(params, th_opt, k_[0], k_[1]);
559528

560529
const Eigen::Vector3f k = c + Rot * k_;
561530

562-
projected_points[i].x = static_cast<float> (k[0]);
563-
projected_points[i].y = static_cast<float> (k[1]);
564-
projected_points[i].z = static_cast<float> (k[2]);
531+
projected_points[inliers[i]].x = static_cast<float> (k[0]);
532+
projected_points[inliers[i]].y = static_cast<float> (k[1]);
533+
projected_points[inliers[i]].z = static_cast<float> (k[2]);
565534
}
566535
}
567536
else
@@ -571,11 +540,7 @@ pcl::SampleConsensusModelEllipse3D<PointT>::projectPoints (
571540
projected_points.width = inliers.size ();
572541
projected_points.height = 1;
573542

574-
using FieldList = typename pcl::traits::fieldList<PointT>::type;
575-
// Iterate over each point
576-
for (std::size_t i = 0; i < inliers.size (); ++i)
577-
// Iterate over each dimension
578-
pcl::for_each_type <FieldList> (NdConcatenateFunctor <PointT, PointT> ((*input_)[inliers[i]], projected_points[i]));
543+
pcl::copyPointCloud (*input_, inliers, projected_points);
579544

580545
// c : Ellipse Center
581546
const Eigen::Vector3f c(model_coefficients[0], model_coefficients[1], model_coefficients[2]);
@@ -602,7 +567,7 @@ pcl::SampleConsensusModelEllipse3D<PointT>::projectPoints (
602567
for (std::size_t i = 0; i < inliers.size (); ++i)
603568
{
604569
// p : Sample Point
605-
const Eigen::Vector3f p((*input_)[(*indices_)[i]].x, (*input_)[(*indices_)[i]].y, (*input_)[(*indices_)[i]].z);
570+
const Eigen::Vector3f p = projected_points[i].getVector3fMap();
606571

607572
// Local coordinates of sample point p
608573
const Eigen::Vector3f p_ = Rot_T * (p - c);
@@ -613,12 +578,11 @@ pcl::SampleConsensusModelEllipse3D<PointT>::projectPoints (
613578
// point it intersects the tangent.
614579
const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
615580
float th_opt;
616-
dvec2ellipse(params, p_(0), p_(1), th_opt);
581+
internal::dVec2Ellipse(params, p_(0), p_(1), th_opt);
617582

618583
// Retrieve the ellipse point at the tilt angle t, along the local x-axis
619-
//// model_coefficients[5] = static_cast<float>(par_t);
620584
Eigen::Vector3f k_(0.0, 0.0, 0.0);
621-
get_ellipse_point(params, th_opt, k_[0], k_[1]);
585+
internal::getEllipsePoint(params, th_opt, k_[0], k_[1]);
622586

623587
const Eigen::Vector3f k = c + Rot * k_;
624588

@@ -679,7 +643,7 @@ pcl::SampleConsensusModelEllipse3D<PointT>::doSamplesVerifyModel (
679643
// point it intersects the tangent.
680644
const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
681645
float th_opt;
682-
const Eigen::Vector2f distanceVector = dvec2ellipse(params, p_(0), p_(1), th_opt);
646+
const Eigen::Vector2f distanceVector = internal::dVec2Ellipse(params, p_(0), p_(1), th_opt);
683647

684648
if (distanceVector.squaredNorm() > squared_threshold)
685649
return (false);
@@ -711,151 +675,4 @@ pcl::SampleConsensusModelEllipse3D<PointT>::isModelValid (const Eigen::VectorXf
711675
}
712676

713677

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-
861678
#define PCL_INSTANTIATE_SampleConsensusModelEllipse3D(T) template class PCL_EXPORTS pcl::SampleConsensusModelEllipse3D<T>;

0 commit comments

Comments
 (0)