13#include <pcl/common/transforms.h>
16#include <Eigen/Eigenvalues>
18#include <unsupported/Eigen/MatrixFunctions>
29template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
33 this->reg_name_ =
"FastRobustIterativeClosestPoint";
34 this->max_iterations_ = 50;
35 this->transformation_epsilon_ =
static_cast<Scalar
>(1e-6);
36 this->min_number_correspondences_ = 4;
39template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
47template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
50 Scalar>::RobustFunction
54 return robust_function_;
57template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
62 use_anderson_ = enabled;
65template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
73template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
78 anderson_history_ = std::max<std::size_t>(1, history);
81template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
86 return anderson_history_;
89template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
94 nu_begin_ratio_ = std::max(ratio, same_threshold_);
97template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
102 nu_end_ratio_ = std::max(ratio, same_threshold_);
105template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
110 nu_decay_ratio_ = std::max(0.0, std::min(1.0, ratio));
111 if (nu_decay_ratio_ < same_threshold_)
112 nu_decay_ratio_ = 0.5;
115template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
122 auto convergence_criteria = this->getConvergeCriteria();
123 if (convergence_criteria) {
124 convergence_criteria->setMaximumIterations(this->max_iterations_);
125 convergence_criteria->setRelativeMSE(this->euclidean_fitness_epsilon_);
126 convergence_criteria->setTranslationThreshold(this->transformation_epsilon_);
127 if (this->transformation_rotation_epsilon_ > 0) {
128 convergence_criteria->setRotationThreshold(
129 this->transformation_rotation_epsilon_);
131 convergence_criteria->setConvergenceState(
133 Scalar>::CONVERGENCE_CRITERIA_NOT_CONVERGED);
136 if (!this->input_ || !this->target_) {
137 if (convergence_criteria) {
138 convergence_criteria->setConvergenceState(
140 Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
142 PCL_ERROR(
"[pcl::%s::computeTransformation] Invalid input clouds.\n",
143 this->getClassName().c_str());
147 if (this->input_->empty() || this->target_->empty()) {
148 if (convergence_criteria) {
149 convergence_criteria->setConvergenceState(
151 Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
153 PCL_ERROR(
"[pcl::%s::computeTransformation] Empty input point clouds.\n",
154 this->getClassName().c_str());
158 std::vector<pcl::uindex_t> source_indices;
159 if (this->indices_ && !this->indices_->empty())
160 source_indices.assign(this->indices_->begin(), this->indices_->end());
162 source_indices.resize(this->input_->size());
163 std::iota(source_indices.begin(), source_indices.end(), 0);
166 if (source_indices.size() <
167 static_cast<std::size_t
>(this->min_number_correspondences_)) {
168 PCL_ERROR(
"[pcl::%s::computeTransformation] Not enough source points (%zu).\n",
169 this->getClassName().c_str(),
170 source_indices.size());
171 if (convergence_criteria) {
172 convergence_criteria->setConvergenceState(
174 Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
179 const std::size_t source_size = source_indices.size();
180 const std::size_t target_size = this->target_->size();
182 if (target_size <
static_cast<std::size_t
>(this->min_number_correspondences_)) {
183 PCL_ERROR(
"[pcl::%s::computeTransformation] Not enough target points (%zu).\n",
184 this->getClassName().c_str(),
186 if (convergence_criteria) {
187 convergence_criteria->setConvergenceState(
189 Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
194 Matrix3Xd source_mat(3, source_size);
195 for (std::size_t i = 0; i < source_size; ++i) {
196 const auto& pt = (*this->input_)[source_indices[i]];
197 assert(
pcl::isFinite(pt) &&
"FRICP requires finite source points (no NaN/Inf)");
198 source_mat.col(i) = pt.getVector3fMap().template cast<double>();
200 Vector3d source_mean = source_mat.rowwise().mean();
201 source_mat.colwise() -= source_mean;
203 Matrix3Xd target_mat(3, target_size);
204 for (std::size_t i = 0; i < target_size; ++i) {
205 const auto& pt = (*this->target_)[i];
206 assert(
pcl::isFinite(pt) &&
"FRICP requires finite target points (no NaN/Inf)");
207 target_mat.col(i) = pt.getVector3fMap().template cast<double>();
209 Vector3d target_mean = target_mat.rowwise().mean();
210 target_mat.colwise() -= target_mean;
214 target_centered->
resize(target_size);
215 for (std::size_t i = 0; i < target_size; ++i) {
216 (*target_centered)[i].x =
static_cast<float>(target_mat(0, i));
217 (*target_centered)[i].y =
static_cast<float>(target_mat(1, i));
218 (*target_centered)[i].z =
static_cast<float>(target_mat(2, i));
225 Matrix4d transform_centered = convertGuessToCentered(guess, source_mean, target_mean);
226 Matrix4d svd_transform = transform_centered;
227 Matrix4d previous_transform = transform_centered;
229 Matrix3Xd matched_targets(3, source_size);
230 VectorXd residuals(source_size);
231 if (!updateCorrespondences(transform_centered,
237 this->correspondences_.get())) {
239 "[pcl::%s::computeTransformation] Failed to initialize correspondences.\n",
240 this->getClassName().c_str());
241 if (convergence_criteria) {
242 convergence_criteria->setConvergenceState(
244 Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
249 const bool use_welsch = (robust_function_ == RobustFunction::WELSCH);
250 double nu_limit = 1.0;
251 double nu_current = 1.0;
253 const double neighbor_med = findKNearestMedian(*target_centered, tree_data, 7);
254 std::vector<double> residual_values(
static_cast<std::size_t
>(residuals.size()));
255 for (Eigen::Index i = 0; i < residuals.size(); ++i)
256 residual_values[
static_cast<std::size_t
>(i)] = std::sqrt(residuals(i));
257 const double residual_med =
259 nu_limit = std::max(nu_end_ratio_ * neighbor_med, same_threshold_);
260 nu_current = std::max(nu_begin_ratio_ * residual_med, nu_limit);
263 this->nr_iterations_ = 0;
264 this->converged_ =
false;
266 double last_energy = std::numeric_limits<double>::max();
268 const Matrix4d log_state = matrixLog(transform_centered);
269 anderson_.init(anderson_history_, 16, log_state.data());
272 bool outer_done =
false;
273 bool converged =
false;
275 while (!outer_done) {
276 for (
int iter = 0; iter < this->max_iterations_; ++iter) {
278 use_welsch ? computeEnergy(residuals, nu_current) : residuals.sum();
280 if (energy <= last_energy) {
281 last_energy = energy;
284 transform_centered = svd_transform;
285 const Matrix4d log_state = matrixLog(transform_centered);
286 anderson_.replace(log_state.data());
287 if (!updateCorrespondences(transform_centered,
293 this->correspondences_.get())) {
294 PCL_ERROR(
"[pcl::%s::computeTransformation] Unable to recompute "
295 "correspondences during fallback.\n",
296 this->getClassName().c_str());
297 if (convergence_criteria) {
298 convergence_criteria->setConvergenceState(
300 Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
304 energy = use_welsch ? computeEnergy(residuals, nu_current) : residuals.sum();
305 last_energy = energy;
309 last_energy = energy;
312 VectorXd weights = use_welsch ? computeWeights(residuals, nu_current)
313 : VectorXd::Ones(residuals.size());
315 computeWeightedRigidTransform(source_mat, matched_targets, weights);
316 svd_transform = candidate;
317 transform_centered = candidate;
320 const Matrix4d log_matrix = matrixLog(transform_centered);
321 const Eigen::VectorXd& accelerated = anderson_.compute(log_matrix.data());
322 transform_centered = Eigen::Map<const Matrix4d>(accelerated.data()).exp();
325 const Matrix4d delta_transform =
326 transform_centered * previous_transform.inverse();
327 this->transformation_ = delta_transform.template cast<Scalar>();
329 if (!updateCorrespondences(transform_centered,
335 this->correspondences_.get())) {
336 PCL_ERROR(
"[pcl::%s::computeTransformation] Failed to update "
337 "correspondences.\n",
338 this->getClassName().c_str());
339 if (convergence_criteria) {
340 convergence_criteria->setConvergenceState(
342 Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
347 previous_transform = transform_centered;
348 ++this->nr_iterations_;
350 if (convergence_criteria &&
static_cast<bool>(*convergence_criteria)) {
356 if (!use_welsch || (std::abs(nu_current - nu_limit) < same_threshold_)) {
360 nu_current = std::max(nu_current * nu_decay_ratio_, nu_limit);
361 last_energy = std::numeric_limits<double>::max();
363 const Matrix4d log_state = matrixLog(transform_centered);
364 anderson_.reset(log_state.data());
369 this->converged_ = converged;
370 if (!converged && convergence_criteria &&
371 convergence_criteria->getConvergenceState() ==
373 Scalar>::CONVERGENCE_CRITERIA_NOT_CONVERGED) {
374 convergence_criteria->setConvergenceState(
376 Scalar>::CONVERGENCE_CRITERIA_ITERATIONS);
379 const Matrix4d final_transform =
380 convertCenteredToActual(transform_centered, source_mean, target_mean);
381 this->final_transformation_ = final_transform.template cast<Scalar>();
382 this->transformation_ = this->final_transformation_;
383 this->previous_transformation_ = this->final_transformation_;
388template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
389typename FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::Matrix4d
392 const Vector3d& source_mean,
393 const Vector3d& target_mean)
const
395 Matrix4d centered = Matrix4d::Identity();
396 centered.block<3, 3>(0, 0) = guess.template block<3, 3>(0, 0).template cast<double>();
397 Vector3d translation = guess.template block<3, 1>(0, 3).template cast<double>();
398 centered.block<3, 1>(0, 3) =
399 centered.block<3, 3>(0, 0) * source_mean + translation - target_mean;
403template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
404typename FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::Matrix4d
405FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::
406 convertCenteredToActual(
const Matrix4d& transform,
407 const Vector3d& source_mean,
408 const Vector3d& target_mean)
const
410 Matrix4d actual = transform;
411 actual.block<3, 1>(0, 3) = transform.block<3, 1>(0, 3) -
412 transform.block<3, 3>(0, 0) * source_mean + target_mean;
416template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
418FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::
419 updateCorrespondences(
const Matrix4d& transform,
420 const Matrix3Xd& source,
421 const Matrix3Xd& target,
423 Matrix3Xd& matched_targets,
427 const Eigen::Matrix3d R =
transform.block<3, 3>(0, 0);
428 const Eigen::Vector3d t =
transform.block<3, 1>(0, 3);
431 std::vector<float> nn_sqr_dists(1);
433 if (correspondences) {
434 correspondences->clear();
435 correspondences->reserve(
static_cast<std::size_t
>(source.cols()));
438 for (Eigen::Index i = 0; i < source.cols(); ++i) {
439 const Eigen::Vector3d current = R * source.col(i) + t;
440 query.x =
static_cast<float>(current.x());
441 query.y =
static_cast<float>(current.y());
442 query.z =
static_cast<float>(current.z());
445 const auto idx = nn_indices[0];
446 matched_targets.col(i) = target.col(
static_cast<int>(idx));
449 residuals(i) =
static_cast<double>(nn_sqr_dists[0]);
450 if (correspondences) {
455 correspondences->push_back(corr);
461template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
463FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::computeEnergy(
464 const VectorXd& residuals,
double nu)
const
466 if (nu < same_threshold_)
467 nu = same_threshold_;
468 const double denom = 2.0 * nu * nu;
470 const Eigen::ArrayXd
dist2 = residuals.array();
471 return (1.0 - (-dist2 / denom).exp()).sum();
474template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
475typename FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::VectorXd
476FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::computeWeights(
477 const VectorXd& residuals,
double nu)
const
479 if (nu < same_threshold_)
480 nu = same_threshold_;
481 const double denom = 2.0 * nu * nu;
483 return (-residuals.array() / denom).exp().matrix();
486template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
487typename FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::Matrix4d
488FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::
489 computeWeightedRigidTransform(
const Matrix3Xd& source,
490 const Matrix3Xd& target,
491 const VectorXd& weights)
const
493 Matrix4d
transform = Matrix4d::Identity();
496 if (sum <= same_threshold_) {
504 const Vector3d source_mean = source *
normalized;
505 const Vector3d target_mean = target *
normalized;
506 const Matrix3Xd source_centered = source.colwise() - source_mean;
507 const Matrix3Xd target_centered = target.colwise() - target_mean;
508 Eigen::Matrix3d sigma =
509 source_centered *
normalized.asDiagonal() * target_centered.transpose();
510 Eigen::JacobiSVD<Eigen::Matrix3d> svd(sigma,
511 Eigen::ComputeFullU | Eigen::ComputeFullV);
512 Eigen::Matrix3d R = svd.matrixV() * svd.matrixU().transpose();
513 if (R.determinant() < 0.0) {
514 Eigen::Matrix3d V = svd.matrixV();
516 R = V * svd.matrixU().transpose();
519 transform.block<3, 1>(0, 3) = target_mean - R * source_mean;
523template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
525FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::findKNearestMedian(
530 if (cloud.
empty() || neighbors < 2)
533 const int k = std::min<int>(neighbors,
static_cast<int>(cloud.
size()));
537 std::vector<double> local_medians;
539 std::vector<float> nn_sqr_dists(k);
540 std::vector<double> dists;
541 dists.reserve(k - 1);
543 for (
const auto& point : cloud) {
547 for (
int j = 1; j < k; ++j)
548 dists.push_back(std::sqrt(nn_sqr_dists[j]));
549 if (!dists.empty()) {
554 if (local_medians.empty())
559template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
560typename FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::Matrix4d
561FastRobustIterativeClosestPoint<PointSource, PointTarget, Scalar>::matrixLog(
562 const Matrix4d& transform)
const
564 Eigen::RealSchur<Matrix4d> schur(transform);
565 const Matrix4d U = schur.matrixU();
566 const Matrix4d R = schur.matrixT();
567 std::array<bool, 3> selected{{
true,
true,
true}};
568 Eigen::Matrix3d
B = Eigen::Matrix3d::Zero();
569 Eigen::Matrix3d V = Eigen::Matrix3d::Identity();
571 for (
int i = 0; i < 3; ++i) {
574 if (std::abs(R(i, i) - 1.0) <= same_threshold_)
577 for (
int j = i + 1; j < 3; ++j) {
578 if (std::abs(R(j, j) - R(i, i)) < same_threshold_) {
586 const double diag = std::max(-1.0, std::min(1.0,
static_cast<double>(R(i, i))));
587 double theta = std::acos(diag);
588 if (R(i, partner) < 0.0)
590 B(i, partner) += theta;
591 B(partner, i) -= theta;
592 V(i, partner) -= theta / 2.0;
593 V(partner, i) += theta / 2.0;
594 const double denom = 2.0 * (1.0 - R(i, i));
595 if (std::abs(denom) > same_threshold_) {
596 const double coeff = 1.0 - (theta * R(i, partner)) / denom;
598 V(partner, partner) -= coeff;
603 Matrix4d trimmed = Matrix4d::Zero();
604 trimmed.block<3, 3>(0, 0) =
B;
605 trimmed.block<3, 1>(0, 3) = V * R.block<3, 1>(0, 3);
606 return U * trimmed * U.transpose();
FastRobustIterativeClosestPoint implements the FRICP variant described in "Fast and Robust Iterative ...
bool getUseAndersonAcceleration() const
void computeTransformation(PointCloudSource &output, const Matrix4 &guess) override
Abstract transformation computation method with initial guess.
void setDynamicWelschEndRatio(double ratio)
Set the final Welsch scale ratio used in dynamic robust weighting.
void setRobustFunction(RobustFunction f)
void setDynamicWelschDecay(double ratio)
Set the multiplicative decay applied to the dynamic Welsch scale.
void setUseAndersonAcceleration(bool enabled)
Enable or disable Anderson acceleration in the FRICP optimization loop.
FastRobustIterativeClosestPoint()
void setAndersonHistorySize(std::size_t history)
Set the history size used by Anderson acceleration.
std::size_t getAndersonHistorySize() const
RobustFunction getRobustFunction() const
void setDynamicWelschBeginRatio(double ratio)
Set the initial Welsch scale ratio used in dynamic robust weighting.
typename Registration< PointSource, PointTarget, Scalar >::Matrix4 Matrix4
typename Registration< PointSource, PointTarget, Scalar >::PointCloudSource PointCloudSource
PointCloud represents the base class in PCL for storing collections of 3D points.
void resize(std::size_t count)
Resizes the container to contain count elements.
shared_ptr< PointCloud< PointT > > Ptr
bool initComputeReciprocal()
Internal computation when reciprocal lookup is needed.
DefaultConvergenceCriteria represents an instantiation of ConvergenceCriteria, and implements the fol...
search::KdTree is a wrapper class which inherits the pcl::KdTree class for performing search function...
virtual bool setInputCloud(const PointCloudConstPtr &cloud, const IndicesConstPtr &indices=IndicesConstPtr())
Pass the input dataset that the search will be performed on.
virtual int nearestKSearch(const PointT &point, int k, Indices &k_indices, std::vector< float > &k_sqr_distances) const =0
Search for the k-nearest neighbors for the given query point.
Define standard C methods and C++ classes that are common to all methods.
auto computeMedian(IteratorT begin, IteratorT end, Functor f) noexcept -> std::result_of_t< Functor(decltype(*begin))>
Compute the median of a list of values (fast).
void transformPointCloud(const pcl::PointCloud< PointT > &cloud_in, pcl::PointCloud< PointT > &cloud_out, const Eigen::Matrix< Scalar, 4, 4 > &transform, bool copy_all_fields)
Apply a rigid transform defined by a 4x4 matrix.
int dist2(const cv::Vec4b &lhs, const cv::Vec4b &rhs)
__device__ __forceinline__ float3 normalized(const float3 &v)
void transform(const T t[12], const T p[3], T out[3])
The first 9 elements of 't' are treated as a 3x3 matrix (row major order) and the last 3 as a transla...
bool isFinite(const PointT &pt)
Tests if the 3D components of a point are all finite param[in] pt point to be tested return true if f...
std::vector< pcl::Correspondence, Eigen::aligned_allocator< pcl::Correspondence > > Correspondences
IndicesAllocator<> Indices
Type used for indices in PCL.
Correspondence represents a match between two entities (e.g., points, descriptors,...
index_t index_query
Index of the query (source) point.
index_t index_match
Index of the matching (target) point.
A point structure representing Euclidean xyz coordinates.
Defines basic non-point types used by PCL.