ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
Registration.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "pipelines/registration/Registration.h"
9 
10 #include <ecvFeature.h>
11 #include <ecvKDTreeFlann.h>
12 #include <ecvPointCloud.h>
13 
14 // CV_CORE_LIB
15 #include <Helper.h>
16 #include <Logging.h>
17 #include <Parallel.h>
18 
19 namespace cloudViewer {
20 namespace pipelines {
21 namespace registration {
22 
24  const ccPointCloud &source,
25  const ccPointCloud &target,
26  const geometry::KDTreeFlann &target_kdtree,
28  const Eigen::Matrix4d &transformation) {
29  RegistrationResult result(transformation);
30  if (max_correspondence_distance <= 0.0) {
31  return result;
32  }
33 
34  double error2 = 0.0;
35 
36 #ifdef _OPENMP
37 #pragma omp parallel
38  {
39 #endif
40  double error2_private = 0.0;
41  CorrespondenceSet correspondence_set_private;
42 #ifdef _OPENMP
43 #pragma omp for nowait
44 #endif
45  for (int i = 0; i < (int)source.size(); i++) {
46  std::vector<int> indices(1);
47  std::vector<double> dists(1);
48  const auto &point = source.getEigenPoint(i);
50  1, indices, dists) > 0) {
51  error2_private += dists[0];
52  correspondence_set_private.push_back(
53  Eigen::Vector2i(i, indices[0]));
54  }
55  }
56 #ifdef _OPENMP
57 #pragma omp critical(GetRegistrationResultAndCorrespondences)
58 #endif
59  {
60  for (int i = 0; i < (int)correspondence_set_private.size(); i++) {
61  result.correspondence_set_.push_back(
62  correspondence_set_private[i]);
63  }
64  error2 += error2_private;
65  }
66 #ifdef _OPENMP
67  }
68 #endif
69 
70  if (result.correspondence_set_.empty()) {
71  result.fitness_ = 0.0;
72  result.inlier_rmse_ = 0.0;
73  } else {
74  size_t corres_number = result.correspondence_set_.size();
75  result.fitness_ = (double)corres_number / (double)source.size();
76  result.inlier_rmse_ = std::sqrt(error2 / (double)corres_number);
77  }
78  return result;
79 }
80 
82  const ccPointCloud &source,
83  const ccPointCloud &target,
84  const CorrespondenceSet &corres,
86  const Eigen::Matrix4d &transformation) {
87  RegistrationResult result(transformation);
88  double error2 = 0.0;
89  int good = 0;
91  for (const auto &c : corres) {
92  double dis2 = (source.getEigenPoint(c[0]) - target.getEigenPoint(c[1]))
93  .squaredNorm();
94  if (dis2 < max_dis2) {
95  good++;
96  error2 += dis2;
97  result.correspondence_set_.push_back(c);
98  }
99  }
100  if (good == 0) {
101  result.fitness_ = 0.0;
102  result.inlier_rmse_ = 0.0;
103  } else {
104  result.fitness_ = (double)good / (double)corres.size();
105  result.inlier_rmse_ = std::sqrt(error2 / (double)good);
106  }
107  return result;
108 }
109 
111  const ccPointCloud &target,
113  const Eigen::Matrix4d &transformation
114  /* = Eigen::Matrix4d::Identity()*/) {
115  geometry::KDTreeFlann kdtree;
116  kdtree.SetGeometry(target);
117  ccPointCloud pcd = source;
118  if (!transformation.isIdentity()) {
119  pcd.Transform(transformation);
120  }
121 
123  pcd, target, kdtree, max_correspondence_distance, transformation);
124 }
125 
127  const ccPointCloud &source,
128  const ccPointCloud &target,
130  const Eigen::Matrix4d &init /* = Eigen::Matrix4d::Identity()*/,
131  const TransformationEstimation &estimation
132  /* = TransformationEstimationPointToPoint(false)*/,
134  &criteria /* = ICPConvergenceCriteria()*/) {
135  if (max_correspondence_distance <= 0.0) {
136  utility::LogError("Invalid max_correspondence_distance.");
137  }
138  if ((estimation.GetTransformationEstimationType() ==
140  estimation.GetTransformationEstimationType() ==
142  (!source.hasNormals() || !target.hasNormals())) {
144  "TransformationEstimationPointToPlane and "
145  "TransformationEstimationColoredICP "
146  "require pre-computed normal vectors.");
147  }
148 
149  Eigen::Matrix4d transformation = init;
150  geometry::KDTreeFlann kdtree;
151  kdtree.SetGeometry(target);
152  ccPointCloud pcd = source;
153  if (!init.isIdentity()) {
154  pcd.Transform(init);
155  }
158  pcd, target, kdtree, max_correspondence_distance, transformation);
159  for (int i = 0; i < criteria.max_iteration_; i++) {
160  utility::LogDebug("ICP Iteration #{:d}: Fitness {:.4f}, RMSE {:.4f}", i,
161  result.fitness_, result.inlier_rmse_);
162  Eigen::Matrix4d update = estimation.ComputeTransformation(
163  pcd, target, result.correspondence_set_);
164  transformation = update * transformation;
165  pcd.Transform(update);
166  RegistrationResult backup = result;
168  pcd, target, kdtree, max_correspondence_distance,
169  transformation);
170  if (std::abs(backup.fitness_ - result.fitness_) <
171  criteria.relative_fitness_ &&
172  std::abs(backup.inlier_rmse_ - result.inlier_rmse_) <
173  criteria.relative_rmse_) {
174  break;
175  }
176  }
177  return result;
178 }
179 
181  const ccPointCloud &source,
182  const ccPointCloud &target,
183  const CorrespondenceSet &corres,
185  const TransformationEstimation &estimation
186  /* = TransformationEstimationPointToPoint(false)*/,
187  int ransac_n /* = 3*/,
188  const std::vector<std::reference_wrapper<const CorrespondenceChecker>>
189  &checkers /* = {}*/,
190  const RANSACConvergenceCriteria &criteria
191  /* = RANSACConvergenceCriteria()*/,
192  utility::optional<unsigned int> seed /* = utility::nullopt*/) {
193  if (ransac_n < 3 || (int)corres.size() < ransac_n ||
195  return RegistrationResult();
196  }
197 
198  RegistrationResult best_result;
199  int exit_itr = -1;
200 #ifdef _OPENMP
201 #pragma omp parallel
202 #endif
203  {
204  CorrespondenceSet ransac_corres(ransac_n);
205  RegistrationResult best_result_local;
206  int exit_itr_local = criteria.max_iteration_;
207  unsigned int seed_val =
208  seed.has_value() ? seed.value() : std::random_device{}();
209  utility::UniformRandIntGenerator rand_generator(
210  0, static_cast<int>(corres.size()) - 1, seed_val);
211 
212 #ifdef _OPENMP
213 #pragma omp for nowait
214 #endif
215  for (int itr = 0; itr < criteria.max_iteration_; itr++) {
216  if (itr < exit_itr_local) {
217  for (int j = 0; j < ransac_n; j++) {
218  ransac_corres[j] = corres[rand_generator()];
219  }
220 
221  Eigen::Matrix4d transformation =
222  estimation.ComputeTransformation(source, target,
223  ransac_corres);
224 
225  // Check transformation: inexpensive
226  bool check = true;
227  for (const auto &checker : checkers) {
228  if (!checker.get().Check(source, target, ransac_corres,
229  transformation)) {
230  check = false;
231  break;
232  }
233  }
234  if (!check) continue;
235 
236  ccPointCloud pcd = source;
237  pcd.Transform(transformation);
239  pcd, target, corres, max_correspondence_distance,
240  transformation);
241 
242  if (result.IsBetterRANSACThan(best_result_local)) {
243  best_result_local = result;
244 
245  // Update exit condition if necessary
246  double exit_itr_d =
247  std::log(1.0 - criteria.confidence_) /
248  std::log(1.0 - std::pow(result.fitness_, ransac_n));
249  exit_itr_local =
250  exit_itr_d < double(criteria.max_iteration_)
251  ? static_cast<int>(std::ceil(exit_itr_d))
252  : exit_itr_local;
253  }
254  } // if < exit_itr_local
255  } // for loop
256 
257 #ifdef _OPENMP
258 #pragma omp critical(RegistrationRANSACBasedOnCorrespondence)
259 #endif
260  {
261  if (best_result_local.IsBetterRANSACThan(best_result)) {
262  best_result = best_result_local;
263  }
264  if (exit_itr_local > exit_itr) {
265  exit_itr = exit_itr_local;
266  }
267  }
268  }
270  "RANSAC exits at {:d}-th iteration: inlier ratio {:e}, "
271  "RMSE {:e}",
272  exit_itr, best_result.fitness_, best_result.inlier_rmse_);
273  return best_result;
274 }
275 
277  const ccPointCloud &source,
278  const ccPointCloud &target,
279  const utility::Feature &source_feature,
280  const utility::Feature &target_feature,
281  bool mutual_filter,
283  const TransformationEstimation &estimation
284  /* = TransformationEstimationPointToPoint(false)*/,
285  int ransac_n /* = 3*/,
286  const std::vector<std::reference_wrapper<const CorrespondenceChecker>>
287  &checkers /* = {}*/,
288  const RANSACConvergenceCriteria &criteria
289  /* = RANSACConvergenceCriteria()*/,
290  utility::optional<unsigned int> seed /* = utility::nullopt*/) {
291  if (ransac_n < 3 || max_correspondence_distance <= 0.0) {
292  return RegistrationResult();
293  }
294 
295  int num_src_pts = int(source.size());
296  int num_tgt_pts = int(target.size());
297 
298  geometry::KDTreeFlann kdtree_target(target_feature);
299  pipelines::registration::CorrespondenceSet corres_ij(num_src_pts);
300 
301 #ifdef _OPENMP
302 #pragma omp parallel for num_threads(utility::EstimateMaxThreads())
303 #endif
304  for (int i = 0; i < num_src_pts; i++) {
305  std::vector<int> corres_tmp(1);
306  std::vector<double> dist_tmp(1);
307 
308  kdtree_target.SearchKNN(Eigen::VectorXd(source_feature.data_.col(i)), 1,
309  corres_tmp, dist_tmp);
310  int j = corres_tmp[0];
311  corres_ij[i] = Eigen::Vector2i(i, j);
312  }
313 
314  // Do reverse check if mutual_filter is enabled
315  if (mutual_filter) {
316  geometry::KDTreeFlann kdtree_source(source_feature);
317  pipelines::registration::CorrespondenceSet corres_ji(num_tgt_pts);
318 
319 #ifdef _OPENMP
320 #pragma omp parallel for num_threads(utility::EstimateMaxThreads())
321 #endif
322  for (int j = 0; j < num_tgt_pts; ++j) {
323  std::vector<int> corres_tmp(1);
324  std::vector<double> dist_tmp(1);
325  kdtree_source.SearchKNN(
326  Eigen::VectorXd(target_feature.data_.col(j)), 1, corres_tmp,
327  dist_tmp);
328  int i = corres_tmp[0];
329  corres_ji[j] = Eigen::Vector2i(i, j);
330  }
331 
333  for (int i = 0; i < num_src_pts; ++i) {
334  int j = corres_ij[i](1);
335  if (corres_ji[j](0) == i) {
336  corres_mutual.emplace_back(i, j);
337  }
338  }
339 
340  // Empirically mutual correspondence set should not be too small
341  if (int(corres_mutual.size()) >= ransac_n * 3) {
342  utility::LogDebug("{:d} correspondences remain after mutual filter",
343  corres_mutual.size());
345  source, target, corres_mutual, max_correspondence_distance,
346  estimation, ransac_n, checkers, criteria, seed);
347  }
349  "Too few correspondences after mutual filter, fall back to "
350  "original correspondences.");
351  }
352 
354  source, target, corres_ij, max_correspondence_distance, estimation,
355  ransac_n, checkers, criteria, seed);
356 }
357 
359  const ccPointCloud &source,
360  const ccPointCloud &target,
362  const Eigen::Matrix4d &transformation) {
363  ccPointCloud pcd = source;
364  if (transformation.isIdentity() == false) {
365  pcd.Transform(transformation);
366  }
368  geometry::KDTreeFlann target_kdtree(target);
370  pcd, target, target_kdtree, max_correspondence_distance,
371  transformation);
372 
373  // write q^*
374  // see http://redwood-data.org/indoor/registration.html
375  // note: I comes first in this implementation
376  Eigen::Matrix6d GTG = Eigen::Matrix6d::Zero();
377 #ifdef _OPENMP
378 #pragma omp parallel
379  {
380 #endif
381  Eigen::Matrix6d GTG_private = Eigen::Matrix6d::Zero();
382  Eigen::Vector6d G_r_private = Eigen::Vector6d::Zero();
383 #ifdef _OPENMP
384 #pragma omp for nowait
385 #endif
386  for (int c = 0; c < int(result.correspondence_set_.size()); c++) {
387  int t = result.correspondence_set_[c](1);
388  double x = target.getEigenPoint(t)(0);
389  double y = target.getEigenPoint(t)(1);
390  double z = target.getEigenPoint(t)(2);
391  G_r_private.setZero();
392  G_r_private(1) = z;
393  G_r_private(2) = -y;
394  G_r_private(3) = 1.0;
395  GTG_private.noalias() += G_r_private * G_r_private.transpose();
396  G_r_private.setZero();
397  G_r_private(0) = -z;
398  G_r_private(2) = x;
399  G_r_private(4) = 1.0;
400  GTG_private.noalias() += G_r_private * G_r_private.transpose();
401  G_r_private.setZero();
402  G_r_private(0) = y;
403  G_r_private(1) = -x;
404  G_r_private(5) = 1.0;
405  GTG_private.noalias() += G_r_private * G_r_private.transpose();
406  }
407 #ifdef _OPENMP
408 #pragma omp critical(GetInformationMatrixFromPointClouds)
409 #endif
410  { GTG += GTG_private; }
411 #ifdef _OPENMP
412  }
413 #endif
414  return GTG;
415 }
416 
417 } // namespace registration
418 } // namespace pipelines
419 } // namespace cloudViewer
core::Tensor result
Definition: VtkUtils.cpp:76
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
virtual ccPointCloud & Transform(const Eigen::Matrix4d &trans) override
Apply transformation (4x4 matrix) to the geometry coordinates.
bool hasNormals() const override
Returns whether normals are enabled or not.
unsigned size() const override
Definition: PointCloudTpl.h:38
Eigen::Vector3d getEigenPoint(size_t index) const
KDTree with FLANN for nearest neighbor search.
int SearchKNN(const T &query, int knn, std::vector< int > &indices, std::vector< double > &distance2) const
int SearchHybrid(const T &query, double radius, int max_nn, std::vector< int > &indices, std::vector< double > &distance2) const
bool SetGeometry(const ccHObject &geometry)
Class that defines the convergence criteria of ICP.
Definition: Registration.h:36
int max_iteration_
Maximum iteration before iteration stops.
Definition: Registration.h:61
Class that defines the convergence criteria of RANSAC.
Definition: Registration.h:77
int max_iteration_
Maximum iteration before iteration stops.
Definition: Registration.h:92
double inlier_rmse_
RMSE of all inlier correspondences. Lower is better.
Definition: Registration.h:120
bool IsBetterRANSACThan(const RegistrationResult &other) const
Definition: Registration.h:109
virtual TransformationEstimationType GetTransformationEstimationType() const =0
virtual Eigen::Matrix4d ComputeTransformation(const ccPointCloud &source, const ccPointCloud &target, const CorrespondenceSet &corres) const =0
Class to store featrues for registration.
Definition: ecvFeature.h:29
Eigen::MatrixXd data_
Data buffer storing features.
Definition: ecvFeature.h:54
Draw pseudo-random integers bounded by min and max (inclusive) from a uniform distribution.
Definition: Helper.h:219
constexpr bool has_value() const noexcept
Definition: Optional.h:440
constexpr T const & value() const &
Definition: Optional.h:465
#define LogError(...)
Definition: Logging.h:60
#define LogDebug(...)
Definition: Logging.h:90
__host__ __device__ int2 abs(int2 v)
Definition: cutil_math.h:1267
Helper functions for the ml ops.
Eigen::Matrix< double, 6, 1 > Vector6d
Definition: Eigen.h:107
Eigen::Matrix< double, 6, 6 > Matrix6d
Extending Eigen namespace by adding frequently used matrix type.
Definition: Eigen.h:106
RegistrationResult RegistrationRANSACBasedOnCorrespondence(const ccPointCloud &source, const ccPointCloud &target, const CorrespondenceSet &corres, double max_correspondence_distance, const TransformationEstimation &estimation, int ransac_n, const std::vector< std::reference_wrapper< const CorrespondenceChecker >> &checkers, const RANSACConvergenceCriteria &criteria, utility::optional< unsigned int > seed)
Function for global RANSAC registration based on a given set of correspondences.
Eigen::Matrix6d GetInformationMatrixFromPointClouds(const ccPointCloud &source, const ccPointCloud &target, double max_correspondence_distance, const Eigen::Matrix4d &transformation)
std::vector< Eigen::Vector2i > CorrespondenceSet
RegistrationResult RegistrationICP(const ccPointCloud &source, const ccPointCloud &target, double max_correspondence_distance, const Eigen::Matrix4d &init, const TransformationEstimation &estimation, const ICPConvergenceCriteria &criteria)
Functions for ICP registration.
static RegistrationResult EvaluateRANSACBasedOnCorrespondence(const ccPointCloud &source, const ccPointCloud &target, const CorrespondenceSet &corres, double max_correspondence_distance, const Eigen::Matrix4d &transformation)
static RegistrationResult GetRegistrationResultAndCorrespondences(const ccPointCloud &source, const ccPointCloud &target, const geometry::KDTreeFlann &target_kdtree, double max_correspondence_distance, const Eigen::Matrix4d &transformation)
static const double max_correspondence_distance
RegistrationResult RegistrationRANSACBasedOnFeatureMatching(const ccPointCloud &source, const ccPointCloud &target, const utility::Feature &source_feature, const utility::Feature &target_feature, bool mutual_filter, double max_correspondence_distance, const TransformationEstimation &estimation, int ransac_n, const std::vector< std::reference_wrapper< const CorrespondenceChecker >> &checkers, const RANSACConvergenceCriteria &criteria, utility::optional< unsigned int > seed)
Function for global RANSAC registration based on feature matching.
RegistrationResult EvaluateRegistration(const ccPointCloud &source, const ccPointCloud &target, double max_correspondence_distance, const Eigen::Matrix4d &transformation)
Function for evaluating registration between point clouds.
MiniVec< float, N > ceil(const MiniVec< float, N > &a)
Definition: MiniVec.h:89
Generic file read and write utility for python interface.
Eigen::Matrix< Index, 2, 1 > Vector2i
Definition: knncpp.h:29
Definition: lsd.c:149