13#include <pcl/common/transforms.h>
14#include <pcl/search/auto.h>
17#include <Eigen/Eigenvalues>
19#include <unsupported/Eigen/MatrixFunctions>
30template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
34 this->reg_name_ =
"FastRobustIterativeClosestPoint";
35 this->max_iterations_ = 50;
36 this->transformation_epsilon_ =
static_cast<Scalar
>(1e-6);
37 this->min_number_correspondences_ = 4;
40template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
48template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
51 Scalar>::RobustFunction
55 return robust_function_;
58template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
63 use_anderson_ = enabled;
66template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
74template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
79 anderson_history_ = std::max<std::size_t>(1, history);
82template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
87 return anderson_history_;
90template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
95 nu_begin_ratio_ = std::max(ratio, same_threshold_);
98template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
103 nu_end_ratio_ = std::max(ratio, same_threshold_);
106template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
111 nu_decay_ratio_ = std::max(0.0, std::min(1.0, ratio));
112 if (nu_decay_ratio_ < same_threshold_)
113 nu_decay_ratio_ = 0.5;
116template <
typename Po
intSource,
typename Po
intTarget,
typename Scalar>
123 auto convergence_criteria = this->getConvergeCriteria();
124 if (convergence_criteria) {
125 convergence_criteria->setMaximumIterations(this->max_iterations_);
126 convergence_criteria->setRelativeMSE(this->euclidean_fitness_epsilon_);
127 convergence_criteria->setTranslationThreshold(this->transformation_epsilon_);
128 if (this->transformation_rotation_epsilon_ > 0) {
129 convergence_criteria->setRotationThreshold(
130 this->transformation_rotation_epsilon_);
132 convergence_criteria->setConvergenceState(
134 Scalar>::CONVERGENCE_CRITERIA_NOT_CONVERGED);
137 if (!this->input_ || !this->target_) {
138 if (convergence_criteria) {
139 convergence_criteria->setConvergenceState(
141 Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
143 PCL_ERROR(
"[pcl::%s::computeTransformation] Invalid input clouds.\n",
144 this->getClassName().c_str());
148 if (this->input_->empty() || this->target_->empty()) {
149 if (convergence_criteria) {
150 convergence_criteria->setConvergenceState(
152 Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
154 PCL_ERROR(
"[pcl::%s::computeTransformation] Empty input point clouds.\n",
155 this->getClassName().c_str());
159 std::vector<pcl::uindex_t> source_indices;
160 if (this->indices_ && !this->indices_->empty())
161 source_indices.assign(this->indices_->begin(), this->indices_->end());
163 source_indices.resize(this->input_->size());
164 std::iota(source_indices.begin(), source_indices.end(), 0);
167 if (source_indices.size() <
168 static_cast<std::size_t
>(this->min_number_correspondences_)) {
169 PCL_ERROR(
"[pcl::%s::computeTransformation] Not enough source points (%zu).\n",
170 this->getClassName().c_str(),
171 source_indices.size());
172 if (convergence_criteria) {
173 convergence_criteria->setConvergenceState(
175 Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
180 const std::size_t source_size = source_indices.size();
181 const std::size_t target_size = this->target_->size();
183 if (target_size <
static_cast<std::size_t
>(this->min_number_correspondences_)) {
184 PCL_ERROR(
"[pcl::%s::computeTransformation] Not enough target points (%zu).\n",
185 this->getClassName().c_str(),
187 if (convergence_criteria) {
188 convergence_criteria->setConvergenceState(
190 Scalar>::CONVERGENCE_CRITERIA_NO_CORRESPONDENCES);
195 Matrix3Xd source_mat(3, source_size);
196 for (std::size_t i = 0; i < source_size; ++i) {
197 const auto& pt = (*this->input_)[source_indices[i]];
198 assert(
pcl::isFinite(pt) &&
"FRICP requires finite source points (no NaN/Inf)");
199 source_mat.col(i) = pt.getVector3fMap().template cast<double>();
201 Vector3d source_mean = source_mat.rowwise().mean();
202 source_mat.colwise() -= source_mean;
204 Matrix3Xd target_mat(3, target_size);
205 for (std::size_t i = 0; i < target_size; ++i) {
206 const auto& pt = (*this->target_)[i];
207 assert(
pcl::isFinite(pt) &&
"FRICP requires finite target points (no NaN/Inf)");
208 target_mat.col(i) = pt.getVector3fMap().template cast<double>();
210 Vector3d target_mean = target_mat.rowwise().mean();
211 target_mat.colwise() -= target_mean;
215 target_centered->
resize(target_size);
216 for (std::size_t i = 0; i < target_size; ++i) {
217 (*target_centered)[i].x =
static_cast<float>(target_mat(0, i));
218 (*target_centered)[i].y =
static_cast<float>(target_mat(1, i));
219 (*target_centered)[i].z =
static_cast<float>(target_mat(2, i));
223 pcl::search::autoSelectMethod<pcl::PointXYZ>(target_centered,
true));
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, 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...
shared_ptr< pcl::search::Search< PointT > > Ptr
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.