Skip to content

Commit eb78352

Browse files
committed
Integrate DefaultConvergenceCriteria into FRICP
- Add ConvergenceTrigger enum to track why FRICP stopped - Add getLastConvergenceTrigger() for diagnostic queries - Integrate DefaultConvergenceCriteria as an additional convergence check alongside the existing transform delta - Populate pcl::Correspondences in updateCorrespondences so getFitnessScore() works correctly - Set transformation_ delta each iteration for criteria checks - Track convergence state on all error/early-return paths Based on larshg's proposal in PR discussion.
1 parent 9ffa011 commit eb78352

2 files changed

Lines changed: 116 additions & 6 deletions

File tree

registration/include/pcl/registration/fricp.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ namespace pcl {
3232
*
3333
* \note FRICP uses its own robust correspondence mechanism based on the Welsch
3434
* function. The following inherited ICP settings have no effect:
35-
* - setMaxCorrespondenceDistance() ?FRICP controls outlier rejection via
35+
* - setMaxCorrespondenceDistance() ?FRICP controls outlier rejection via
3636
* the Welsch scale parameter instead.
3737
* - Correspondence estimators, rejectors, and reciprocal correspondences
3838
* are not used by this class.
@@ -72,6 +72,13 @@ class FastRobustIterativeClosestPoint
7272
using typename Base::PointCloudTarget;
7373

7474
enum class RobustFunction { NONE, WELSCH };
75+
enum class ConvergenceTrigger {
76+
NONE = 0,
77+
DEFAULT_CRITERIA,
78+
FRICP_STOP_THRESHOLD,
79+
ITERATION_LIMIT,
80+
NO_CORRESPONDENCES
81+
};
7582

7683
FastRobustIterativeClosestPoint();
7784

@@ -129,6 +136,9 @@ class FastRobustIterativeClosestPoint
129136
void
130137
setDynamicWelschDecay(double ratio);
131138

139+
[[nodiscard]] ConvergenceTrigger
140+
getLastConvergenceTrigger() const;
141+
132142
protected:
133143
void
134144
computeTransformation(PointCloudSource& output, const Matrix4& guess) override;
@@ -156,7 +166,8 @@ class FastRobustIterativeClosestPoint
156166
const Matrix3Xd& target,
157167
pcl::search::Search<pcl::PointXYZ>& tree,
158168
Matrix3Xd& matched_targets,
159-
VectorXd& residuals) const;
169+
VectorXd& residuals,
170+
pcl::Correspondences* correspondences = nullptr) const;
160171

161172
double
162173
computeEnergy(const VectorXd& residuals, double nu) const;
@@ -183,6 +194,7 @@ class FastRobustIterativeClosestPoint
183194
double nu_begin_ratio_ = 3.0;
184195
double nu_end_ratio_ = 1.0 / (3.0 * std::sqrt(3.0));
185196
double nu_decay_ratio_ = 0.5;
197+
ConvergenceTrigger last_convergence_trigger_ = ConvergenceTrigger::NONE;
186198

187199
static constexpr double same_threshold_ = 1e-6;
188200

registration/include/pcl/registration/impl/fricp.hpp

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,20 +112,47 @@ FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::
112112
nu_decay_ratio_ = 0.5;
113113
}
114114

115+
template <typename PointSource, typename PointTarget, typename Scalar>
116+
typename FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::
117+
ConvergenceTrigger
118+
FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::
119+
getLastConvergenceTrigger() const
120+
{
121+
return last_convergence_trigger_;
122+
}
123+
115124
template <typename PointSource, typename PointTarget, typename Scalar>
116125
void
117126
FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::
118127
computeTransformation(PointCloudSource& output, const Matrix4& guess)
119128
{
120129
pcl::IterativeClosestPoint<PointSource, PointTarget, Scalar>::initComputeReciprocal();
121130

131+
last_convergence_trigger_ = ConvergenceTrigger::NONE;
132+
133+
auto convergence_criteria = this->getConvergeCriteria();
134+
if (convergence_criteria) {
135+
convergence_criteria->setMaximumIterations(this->max_iterations_);
136+
convergence_criteria->setRelativeMSE(this->euclidean_fitness_epsilon_);
137+
convergence_criteria->setTranslationThreshold(this->transformation_epsilon_);
138+
if (this->transformation_rotation_epsilon_ > 0) {
139+
convergence_criteria->setRotationThreshold(
140+
this->transformation_rotation_epsilon_);
141+
}
142+
convergence_criteria->setConvergenceState(
143+
pcl::registration::DefaultConvergenceCriteria<
144+
Scalar>::CONVERGENCE_CRITERIA_NOT_CONVERGED);
145+
}
146+
122147
if (!this->input_ || !this->target_) {
148+
last_convergence_trigger_ = ConvergenceTrigger::NO_CORRESPONDENCES;
123149
PCL_ERROR("[pcl::%s::computeTransformation] Invalid input clouds.\n",
124150
this->getClassName().c_str());
125151
return;
126152
}
127153

128154
if (this->input_->empty() || this->target_->empty()) {
155+
last_convergence_trigger_ = ConvergenceTrigger::NO_CORRESPONDENCES;
129156
PCL_ERROR("[pcl::%s::computeTransformation] Empty input point clouds.\n",
130157
this->getClassName().c_str());
131158
return;
@@ -144,6 +171,12 @@ FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::
144171
PCL_ERROR("[pcl::%s::computeTransformation] Not enough source points (%zu).\n",
145172
this->getClassName().c_str(),
146173
source_indices.size());
174+
if (convergence_criteria) {
175+
convergence_criteria->setConvergenceState(
176+
pcl::registration::DefaultConvergenceCriteria<
177+
Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
178+
}
179+
last_convergence_trigger_ = ConvergenceTrigger::NO_CORRESPONDENCES;
147180
return;
148181
}
149182

@@ -154,6 +187,12 @@ FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::
154187
PCL_ERROR("[pcl::%s::computeTransformation] Not enough target points (%zu).\n",
155188
this->getClassName().c_str(),
156189
target_size);
190+
if (convergence_criteria) {
191+
convergence_criteria->setConvergenceState(
192+
pcl::registration::DefaultConvergenceCriteria<
193+
Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
194+
}
195+
last_convergence_trigger_ = ConvergenceTrigger::NO_CORRESPONDENCES;
157196
return;
158197
}
159198

@@ -199,10 +238,17 @@ FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::
199238
target_mat,
200239
tree,
201240
matched_targets,
202-
residuals)) {
241+
residuals,
242+
this->correspondences_.get())) {
203243
PCL_ERROR(
204244
"[pcl::%s::computeTransformation] Failed to initialize correspondences.\n",
205245
this->getClassName().c_str());
246+
if (convergence_criteria) {
247+
convergence_criteria->setConvergenceState(
248+
pcl::registration::DefaultConvergenceCriteria<
249+
Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
250+
}
251+
last_convergence_trigger_ = ConvergenceTrigger::NO_CORRESPONDENCES;
206252
return;
207253
}
208254

@@ -253,10 +299,17 @@ FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::
253299
target_mat,
254300
tree,
255301
matched_targets,
256-
residuals)) {
302+
residuals,
303+
this->correspondences_.get())) {
257304
PCL_ERROR("[pcl::%s::computeTransformation] Unable to recompute "
258305
"correspondences during fallback.\n",
259306
this->getClassName().c_str());
307+
if (convergence_criteria) {
308+
convergence_criteria->setConvergenceState(
309+
pcl::registration::DefaultConvergenceCriteria<
310+
Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
311+
}
312+
last_convergence_trigger_ = ConvergenceTrigger::NO_CORRESPONDENCES;
260313
return;
261314
}
262315
energy = use_welsch ? computeEnergy(residuals, nu_current) : residuals.sum();
@@ -274,6 +327,10 @@ FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::
274327
svd_transform = candidate;
275328
transform_centered = candidate;
276329

330+
const Matrix4d delta_transform =
331+
transform_centered * previous_transform.inverse();
332+
this->transformation_ = delta_transform.template cast<Scalar>();
333+
277334
if (use_anderson_) {
278335
const Matrix4d log_matrix = matrixLog(transform_centered);
279336
const Eigen::VectorXd& accelerated = anderson_.compute(log_matrix.data());
@@ -285,19 +342,38 @@ FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::
285342
target_mat,
286343
tree,
287344
matched_targets,
288-
residuals)) {
345+
residuals,
346+
this->correspondences_.get())) {
289347
PCL_ERROR("[pcl::%s::computeTransformation] Failed to update "
290348
"correspondences.\n",
291349
this->getClassName().c_str());
350+
if (convergence_criteria) {
351+
convergence_criteria->setConvergenceState(
352+
pcl::registration::DefaultConvergenceCriteria<
353+
Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
354+
}
355+
last_convergence_trigger_ = ConvergenceTrigger::NO_CORRESPONDENCES;
292356
return;
293357
}
294358

295359
const double delta = (transform_centered - previous_transform).norm();
296360
previous_transform = transform_centered;
297361
++this->nr_iterations_;
298362

363+
if (convergence_criteria && static_cast<bool>(*convergence_criteria)) {
364+
converged = true;
365+
last_convergence_trigger_ = ConvergenceTrigger::DEFAULT_CRITERIA;
366+
break;
367+
}
368+
299369
if (delta < stop_threshold) {
300370
converged = true;
371+
if (convergence_criteria) {
372+
convergence_criteria->setConvergenceState(
373+
pcl::registration::DefaultConvergenceCriteria<
374+
Scalar>::CONVERGENCE_CRITERIA_TRANSFORM);
375+
}
376+
last_convergence_trigger_ = ConvergenceTrigger::FRICP_STOP_THRESHOLD;
301377
break;
302378
}
303379
}
@@ -316,6 +392,15 @@ FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::
316392
}
317393

318394
this->converged_ = converged;
395+
if (!converged && convergence_criteria &&
396+
convergence_criteria->getConvergenceState() ==
397+
pcl::registration::DefaultConvergenceCriteria<
398+
Scalar>::CONVERGENCE_CRITERIA_NOT_CONVERGED) {
399+
convergence_criteria->setConvergenceState(
400+
pcl::registration::DefaultConvergenceCriteria<
401+
Scalar>::CONVERGENCE_CRITERIA_ITERATIONS);
402+
last_convergence_trigger_ = ConvergenceTrigger::ITERATION_LIMIT;
403+
}
319404

320405
const Matrix4d final_transform =
321406
convertCenteredToActual(transform_centered, source_mean, target_mean);
@@ -362,14 +447,20 @@ FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::
362447
const Matrix3Xd& target,
363448
pcl::search::Search<pcl::PointXYZ>& tree,
364449
Matrix3Xd& matched_targets,
365-
VectorXd& residuals) const
450+
VectorXd& residuals,
451+
pcl::Correspondences* correspondences) const
366452
{
367453
const Eigen::Matrix3d R = transform.block<3, 3>(0, 0);
368454
const Eigen::Vector3d t = transform.block<3, 1>(0, 3);
369455
pcl::PointXYZ query;
370456
pcl::Indices nn_indices(1);
371457
std::vector<float> nn_sqr_dists(1);
372458

459+
if (correspondences) {
460+
correspondences->clear();
461+
correspondences->reserve(static_cast<std::size_t>(source.cols()));
462+
}
463+
373464
for (Eigen::Index i = 0; i < source.cols(); ++i) {
374465
const Eigen::Vector3d current = R * source.col(i) + t;
375466
query.x = static_cast<float>(current.x());
@@ -382,6 +473,13 @@ FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::
382473
// Store squared distance reported by the search (avoid recomputing
383474
// the difference). Promote to double for internal computations.
384475
residuals(i) = static_cast<double>(nn_sqr_dists[0]);
476+
if (correspondences) {
477+
pcl::Correspondence corr;
478+
corr.index_query = static_cast<int>(i);
479+
corr.index_match = idx;
480+
corr.distance = nn_sqr_dists[0];
481+
correspondences->push_back(corr);
482+
}
385483
}
386484
return true;
387485
}

0 commit comments

Comments
 (0)