Skip to content

Commit 45a5300

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 45a5300

3 files changed

Lines changed: 264 additions & 296 deletions

File tree

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

Lines changed: 53 additions & 214 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@
44
* Point Cloud Library (PCL) - www.pointclouds.org
55
* Copyright (c) 2014-, Open Perception Inc.
66
*
7-
* All rights reserved
7+
* All rights reserved.
88
*/
99

1010
#pragma once
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

20-
2121
//////////////////////////////////////////////////////////////////////////
2222
template <typename PointT> bool
2323
pcl::SampleConsensusModelEllipse3D<PointT>::isSampleGood (
@@ -30,9 +30,9 @@ pcl::SampleConsensusModelEllipse3D<PointT>::isSampleGood (
3030
}
3131

3232
// 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);
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);
3636

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

230230
// Redefinition of the x-axis of the ellipse's local reference frame
231231
x_axis = (Rot * p_th_).normalized();
@@ -241,7 +241,6 @@ pcl::SampleConsensusModelEllipse3D<PointT>::computeModelCoefficients (const Indi
241241
return (true);
242242
}
243243

244-
245244
//////////////////////////////////////////////////////////////////////////
246245
template <typename PointT> void
247246
pcl::SampleConsensusModelEllipse3D<PointT>::getDistancesToModel (const Eigen::VectorXf &model_coefficients, std::vector<double> &distances) const
@@ -298,9 +297,14 @@ pcl::SampleConsensusModelEllipse3D<PointT>::getDistancesToModel (const Eigen::Ve
298297
// Calculate the shortest distance from the point to the ellipse which is given by
299298
// the norm of a vector that is normal to the ellipse tangent calculated at the
300299
// point it intersects the tangent.
301-
const Eigen::Vector2f distanceVector = dvec2ellipse(params, p_(0), p_(1), th_opt);
300+
internal::dvec2ellipse(params, p_(0), p_(1), th_opt);
302301

303-
distances[i] = distanceVector.norm();
302+
// Retrieve the ellipse point at the tilt angle t, along the local x-axis
303+
Eigen::Vector3f k_(0.0, 0.0, 0.0);
304+
internal::get_ellipse_point(params, th_opt, k_[0], k_[1]);
305+
306+
const Eigen::Vector3f k = c + Rot * k_;
307+
distances[i] = (p - k).norm();
304308
}
305309
}
306310

@@ -357,9 +361,15 @@ pcl::SampleConsensusModelEllipse3D<PointT>::selectWithinDistance (
357361
// point it intersects the tangent.
358362
const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
359363
float th_opt;
360-
const Eigen::Vector2f distanceVector = dvec2ellipse(params, p_(0), p_(1), th_opt);
364+
internal::dvec2ellipse(params, p_(0), p_(1), th_opt);
365+
366+
// Retrieve the ellipse point at the tilt angle t, along the local x-axis
367+
Eigen::Vector3f k_(0.0, 0.0, 0.0);
368+
internal::get_ellipse_point(params, th_opt, k_[0], k_[1]);
361369

362-
const double sqr_dist = distanceVector.squaredNorm();
370+
const Eigen::Vector3f k = c + Rot * k_;
371+
372+
const double sqr_dist = (p - k).squaredNorm();
363373
if (sqr_dist < squared_threshold)
364374
{
365375
// Returns the indices of the points whose distances are smaller than the threshold
@@ -416,9 +426,15 @@ pcl::SampleConsensusModelEllipse3D<PointT>::countWithinDistance (
416426
// point it intersects the tangent.
417427
const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
418428
float th_opt;
419-
const Eigen::Vector2f distanceVector = dvec2ellipse(params, p_(0), p_(1), th_opt);
429+
internal::dvec2ellipse(params, p_(0), p_(1), th_opt);
430+
431+
// Retrieve the ellipse point at the tilt angle t, along the local x-axis
432+
Eigen::Vector3f k_(0.0, 0.0, 0.0);
433+
internal::get_ellipse_point(params, th_opt, k_[0], k_[1]);
434+
435+
const Eigen::Vector3f k = c + Rot * k_;
420436

421-
if (distanceVector.squaredNorm() < squared_threshold)
437+
if ((p - k).squaredNorm() < squared_threshold)
422438
nr_p++;
423439
}
424440
return (nr_p);
@@ -427,13 +443,9 @@ pcl::SampleConsensusModelEllipse3D<PointT>::countWithinDistance (
427443
//////////////////////////////////////////////////////////////////////////
428444
template <typename PointT> void
429445
pcl::SampleConsensusModelEllipse3D<PointT>::optimizeModelCoefficients (
430-
const Indices &inliers,
431-
const Eigen::VectorXf &model_coefficients,
432-
Eigen::VectorXf &optimized_coefficients) const
446+
const Indices &inliers, const Eigen::VectorXf &model_coefficients, Eigen::VectorXf &optimized_coefficients) const
433447
{
434-
optimized_coefficients = model_coefficients;
435-
436-
// Needs a set of valid model coefficients
448+
// Needs a valid set of model coefficients
437449
if (!isModelValid (model_coefficients))
438450
{
439451
PCL_ERROR ("[pcl::SampleConsensusModelEllipse3D::optimizeModelCoefficients] Given model is invalid!\n");
@@ -443,44 +455,20 @@ pcl::SampleConsensusModelEllipse3D<PointT>::optimizeModelCoefficients (
443455
// Need more than the minimum sample size to make a difference
444456
if (inliers.size () <= sample_size_)
445457
{
446-
PCL_ERROR ("[pcl::SampleConsensusModelEllipse3D::optimizeModelCoefficients] Not enough inliers to refine/optimize the model's coefficients (%lu)! Returning the same coefficients.\n", inliers.size ());
458+
PCL_ERROR ("[pcl::SampleConsensusModelEllipse3D::optimizeModelCoefficients] Not enough inliers found to optimize model coefficients (%lu)! Returning the same coefficients.\n", inliers.size ());
459+
optimized_coefficients = model_coefficients;
447460
return;
448461
}
449462

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]);
463+
Eigen::ArrayXf x (inliers.size ()), y (inliers.size ()), z (inliers.size ());
464+
for (std::size_t i = 0; i < inliers.size (); ++i)
465+
{
466+
const auto& pt = (*input_)[inliers[i]];
467+
x[i] = pt.x; y[i] = pt.y; z[i] = pt.z;
468+
}
469+
470+
optimized_coefficients = model_coefficients;
471+
internal::optimizeModelCoefficientsEllipse3D (optimized_coefficients, x, y, z);
484472
}
485473

486474
//////////////////////////////////////////////////////////////////////////
@@ -540,7 +528,7 @@ pcl::SampleConsensusModelEllipse3D<PointT>::projectPoints (
540528
for (std::size_t i = 0; i < inliers.size (); ++i)
541529
{
542530
// p : Sample Point
543-
const Eigen::Vector3f p((*input_)[(*indices_)[i]].x, (*input_)[(*indices_)[i]].y, (*input_)[(*indices_)[i]].z);
531+
const Eigen::Vector3f p((*input_)[inliers[i]].x, (*input_)[inliers[i]].y, (*input_)[inliers[i]].z);
544532

545533
// Local coordinates of sample point p
546534
const Eigen::Vector3f p_ = Rot_T * (p - c);
@@ -551,17 +539,17 @@ pcl::SampleConsensusModelEllipse3D<PointT>::projectPoints (
551539
// point it intersects the tangent.
552540
const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
553541
float th_opt;
554-
dvec2ellipse(params, p_(0), p_(1), th_opt);
542+
internal::dvec2ellipse(params, p_(0), p_(1), th_opt);
555543

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

560548
const Eigen::Vector3f k = c + Rot * k_;
561549

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]);
550+
projected_points[inliers[i]].x = static_cast<float> (k[0]);
551+
projected_points[inliers[i]].y = static_cast<float> (k[1]);
552+
projected_points[inliers[i]].z = static_cast<float> (k[2]);
565553
}
566554
}
567555
else
@@ -602,7 +590,7 @@ pcl::SampleConsensusModelEllipse3D<PointT>::projectPoints (
602590
for (std::size_t i = 0; i < inliers.size (); ++i)
603591
{
604592
// p : Sample Point
605-
const Eigen::Vector3f p((*input_)[(*indices_)[i]].x, (*input_)[(*indices_)[i]].y, (*input_)[(*indices_)[i]].z);
593+
const Eigen::Vector3f p(projected_points[i].x, projected_points[i].y, projected_points[i].z);
606594

607595
// Local coordinates of sample point p
608596
const Eigen::Vector3f p_ = Rot_T * (p - c);
@@ -613,12 +601,11 @@ pcl::SampleConsensusModelEllipse3D<PointT>::projectPoints (
613601
// point it intersects the tangent.
614602
const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
615603
float th_opt;
616-
dvec2ellipse(params, p_(0), p_(1), th_opt);
604+
internal::dvec2ellipse(params, p_(0), p_(1), th_opt);
617605

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

623610
const Eigen::Vector3f k = c + Rot * k_;
624611

@@ -679,7 +666,7 @@ pcl::SampleConsensusModelEllipse3D<PointT>::doSamplesVerifyModel (
679666
// point it intersects the tangent.
680667
const Eigen::VectorXf params = (Eigen::VectorXf(5) << par_a, par_b, 0.0, 0.0, 0.0).finished();
681668
float th_opt;
682-
const Eigen::Vector2f distanceVector = dvec2ellipse(params, p_(0), p_(1), th_opt);
669+
const Eigen::Vector2f distanceVector = internal::dvec2ellipse(params, p_(0), p_(1), th_opt);
683670

684671
if (distanceVector.squaredNorm() > squared_threshold)
685672
return (false);
@@ -710,152 +697,4 @@ pcl::SampleConsensusModelEllipse3D<PointT>::isModelValid (const Eigen::VectorXf
710697
return (true);
711698
}
712699

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

0 commit comments

Comments
 (0)