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 
9 
10 #include <Logging.h>
11 
12 #include <memory>
13 #include <utility>
14 
15 #include "pybind/docstring.h"
17 #include "t/geometry/PointCloud.h"
19 
20 namespace cloudViewer {
21 namespace t {
22 namespace pipelines {
23 namespace registration {
24 
25 template <class TransformationEstimationBase = TransformationEstimation>
26 class PyTransformationEstimation : public TransformationEstimationBase {
27 public:
28  using TransformationEstimationBase::TransformationEstimationBase;
30  PYBIND11_OVERLOAD_PURE(TransformationEstimationType,
31  TransformationEstimationBase, void);
32  }
33  double ComputeRMSE(const t::geometry::PointCloud &source,
34  const t::geometry::PointCloud &target,
35  const core::Tensor &correspondences) const {
36  PYBIND11_OVERLOAD_PURE(double, TransformationEstimationBase, source,
37  target, correspondences);
38  }
40  const t::geometry::PointCloud &target,
41  const core::Tensor &correspondences,
42  const core::Tensor &current_transform,
43  const std::size_t iteration) const {
44  PYBIND11_OVERLOAD_PURE(core::Tensor, TransformationEstimationBase,
45  source, target, correspondences,
46  current_transform, iteration);
47  }
48 };
49 
50 // Registration functions have similar arguments, sharing arg docstrings.
51 static const std::unordered_map<std::string, std::string>
53  {"correspondences",
54  "Tensor of type Int64 containing indices of corresponding "
55  "target points, where the value is the target index and the "
56  "index of the value itself is the source index. It contains "
57  "-1 as value at index with no correspondence."},
58  {"criteria", "Convergence criteria"},
59  {"criteria_list",
60  "List of Convergence criteria for each scale of multi-scale "
61  "icp."},
62  {"estimation_method",
63  "Estimation method. One of "
64  "(``TransformationEstimationPointToPoint``, "
65  "``TransformationEstimationPointToPlane``, "
66  "``TransformationEstimationForColoredICP``, "
67  "``TransformationEstimationForGeneralizedICP``)"},
68  {"init_source_to_target", "Initial transformation estimation"},
69  {"max_correspondence_distance",
70  "Maximum correspondence points-pair distance."},
71  {"max_correspondence_distances",
72  "o3d.utility.DoubleVector of maximum correspondence "
73  "points-pair distances for multi-scale icp."},
74  {"option", "Registration option"},
75  {"source", "The source point cloud."},
76  {"target", "The target point cloud."},
77  {"transformation",
78  "The 4x4 transformation matrix of type Float64 "
79  "to transform ``source`` to ``target``"},
80  {"voxel_size",
81  "The input pointclouds will be down-sampled to this "
82  "`voxel_size` scale. If `voxel_size` < 0, original scale will "
83  "be used. However it is highly recommended to down-sample the "
84  "point-cloud for performance. By default original scale of "
85  "the point-cloud will be used."},
86  {"voxel_sizes",
87  "o3d.utility.DoubleVector of voxel sizes in strictly "
88  "decreasing order, for multi-scale icp."},
89  {"callback_after_iteration",
90  "Optional lambda function, saves string to tensor map of "
91  "attributes such as iteration_index, scale_index, "
92  "scale_iteration_index, inlier_rmse, fitness, transformation, "
93  "on CPU device, updated after each iteration."}};
94 void pybind_registration_class(py::module &m_registration) {
95  py::class_<ICPConvergenceCriteria> convergence_criteria(
96  m_registration, "ICPConvergenceCriteria",
97  "Convergence criteria of ICP. "
98  "ICP algorithm stops if the relative change of fitness and rmse "
99  "hit ``relative_fitness`` and ``relative_rmse`` individually, "
100  "or the iteration number exceeds ``max_iteration``.");
101  py::class_<RegistrationResult> registration_result(
102  m_registration, "RegistrationResult", "Registration results.");
103  py::class_<TransformationEstimation,
105  te(m_registration, "TransformationEstimation",
106  "Base class that estimates a transformation between two "
107  "point clouds. The virtual function ComputeTransformation() "
108  "must be implemented in subclasses.");
112  te_p2p(m_registration, "TransformationEstimationPointToPoint",
113  "Class to estimate a transformation for point to "
114  "point distance.");
118  te_p2l(m_registration, "TransformationEstimationPointToPlane",
119  "Class to estimate a transformation for point to "
120  "plane distance.");
121  py::class_<
125  te_col(m_registration, "TransformationEstimationForColoredICP",
126  "Class to estimate a transformation between two point "
127  "clouds using color information");
128  py::class_<
132  te_dop(m_registration, "TransformationEstimationForDopplerICP",
133  "Class to estimate a transformation between two point "
134  "clouds using color information");
135 
136  py::detail::bind_copy_functions<ICPConvergenceCriteria>(
137  convergence_criteria);
138  convergence_criteria
139  .def(py::init<double, double, int>(), "relative_fitness"_a = 1e-6,
140  "relative_rmse"_a = 1e-6, "max_iteration"_a = 30)
141  .def_readwrite(
142  "relative_fitness",
144  "If relative change (difference) of fitness score is lower "
145  "than ``relative_fitness``, the iteration stops.")
146  .def_readwrite(
147  "relative_rmse", &ICPConvergenceCriteria::relative_rmse_,
148  "If relative change (difference) of inlier RMSE score is "
149  "lower than ``relative_rmse``, the iteration stops.")
150  .def_readwrite("max_iteration",
152  "Maximum iteration before iteration stops.")
153  .def("__repr__", [](const ICPConvergenceCriteria &c) {
154  return fmt::format(
155  "ICPConvergenceCriteria[relative_fitness_={:e}, "
156  "relative_rmse={:e}, max_iteration_={:d}].",
158  c.max_iteration_);
159  });
160 
161  // cloudViewer.t.pipelines.registration.RegistrationResult
162  py::detail::bind_default_constructor<RegistrationResult>(
163  registration_result);
164  py::detail::bind_copy_functions<RegistrationResult>(registration_result);
165  registration_result
166  .def_readwrite("transformation",
168  "``4 x 4`` float64 tensor on CPU: The estimated "
169  "transformation matrix.")
170  .def_readwrite("correspondence_set",
172  "Tensor of type Int64 containing indices of "
173  "corresponding target points, where the value is "
174  "the target index and the index of the value itself "
175  "is the source index. It contains -1 as value at "
176  "index with no correspondence.")
177  .def_readwrite("inlier_rmse", &RegistrationResult::inlier_rmse_,
178  "float: RMSE of all inlier correspondences. Lower "
179  "is better.")
180  .def_readwrite("fitness", &RegistrationResult::fitness_,
181  "float: The overlapping area (# of inlier "
182  "correspondences "
183  "/ # of points in source). Higher is better.")
184  .def_readwrite(
185  "converged", &RegistrationResult::converged_,
186  "bool: Specifies whether the algorithm converged or not.")
187  .def_readwrite(
188  "num_iterations", &RegistrationResult::num_iterations_,
189  "int: Number of iterations the algorithm took to converge.")
190  .def("__repr__", [](const RegistrationResult &rr) {
191  return fmt::format(
192  "RegistrationResult["
193  "converged={}"
194  ", num_iteration={:d}"
195  ", fitness_={:e}"
196  ", inlier_rmse={:e}"
197  ", correspondences={:d}]."
198  "\nAccess transformation to get result.",
201  });
202 
203  // cloudViewer.t.pipelines.registration.TransformationEstimation
204  te.def("compute_rmse", &TransformationEstimation::ComputeRMSE, "source"_a,
205  "target"_a, "correspondences"_a,
206  "Compute RMSE between source and target points cloud given "
207  "correspondences.");
208  te.def("compute_transformation",
210  "target"_a, "correspondences"_a,
211  "current_transform"_a =
213  "iteration"_a = 0,
214  "Compute transformation from source to target point cloud given "
215  "correspondences.");
216  docstring::ClassMethodDocInject(m_registration, "TransformationEstimation",
217  "compute_rmse",
218  {{"source", "Source point cloud."},
219  {"target", "Target point cloud."},
220  {"correspondences",
221  "Tensor of type Int64 containing "
222  "indices of corresponding target "
223  "points, where the value is the "
224  "target index and the index of "
225  "the value itself is the source "
226  "index. It contains -1 as value "
227  "at index with no correspondence."}});
229  m_registration, "TransformationEstimation",
230  "compute_transformation",
231  {{"source", "Source point cloud."},
232  {"target", "Target point cloud."},
233  {"correspondences",
234  "Tensor of type Int64 containing indices of corresponding target "
235  "points, where the value is the target index and the index of "
236  "the value itself is the source index. It contains -1 as value "
237  "at index with no correspondence."},
238  {"current_transform", "The current pose estimate of ICP."},
239  {"iteration",
240  "The current iteration number of the ICP algorithm."}});
241 
242  // cloudViewer.t.pipelines.registration.TransformationEstimationPointToPoint
243  // TransformationEstimation
244  py::detail::bind_copy_functions<TransformationEstimationPointToPoint>(
245  te_p2p);
246  te_p2p.def(py::init())
247  .def("__repr__",
249  return std::string("TransformationEstimationPointToPoint");
250  });
251 
252  // cloudViewer.t.pipelines.registration.TransformationEstimationPointToPlane
253  // TransformationEstimation
254  py::detail::bind_default_constructor<TransformationEstimationPointToPlane>(
255  te_p2l);
256  py::detail::bind_copy_functions<TransformationEstimationPointToPlane>(
257  te_p2l);
258  te_p2l.def(py::init([](const RobustKernel &kernel) {
259  return new TransformationEstimationPointToPlane(kernel);
260  }),
261  "kernel"_a)
262  .def("__repr__",
264  return std::string("TransformationEstimationPointToPlane");
265  })
266  .def_readwrite("kernel",
268  "Robust Kernel used in the Optimization");
269 
270  // cloudViewer.t.pipelines.registration.TransformationEstimationForColoredICP
271  // TransformationEstimation
272  py::detail::bind_default_constructor<TransformationEstimationForColoredICP>(
273  te_col);
274  py::detail::bind_copy_functions<TransformationEstimationForColoredICP>(
275  te_col);
276  te_col.def(py::init([](double lambda_geometric, RobustKernel &kernel) {
278  lambda_geometric, kernel);
279  }),
280  "lambda_geometric"_a, "kernel"_a)
281  .def(py::init([](const double lambda_geometric) {
283  lambda_geometric);
284  }),
285  "lambda_geometric"_a)
286  .def(py::init([](const RobustKernel kernel) {
288  te.kernel_ = kernel;
289  return te;
290  }),
291  "kernel"_a)
292  .def("__repr__",
294  return std::string(
295  "TransformationEstimationForColoredICP "
296  "with lambda_geometric: ") +
297  std::to_string(te.lambda_geometric_);
298  })
299  .def_readwrite(
300  "lambda_geometric",
302  "lambda_geometric")
303  .def_readwrite("kernel",
305  "Robust Kernel used in the Optimization");
306 
307  // cloudViewer.t.pipelines.registration.TransformationEstimationForDopplerICP
308  // TransformationEstimation
309  py::detail::bind_default_constructor<TransformationEstimationForDopplerICP>(
310  te_dop);
311  py::detail::bind_copy_functions<TransformationEstimationForDopplerICP>(
312  te_dop);
313  te_dop.def(py::init([](double period, double lambda_doppler,
314  bool reject_dynamic_outliers,
315  double doppler_outlier_threshold,
316  std::size_t outlier_rejection_min_iteration,
317  std::size_t geometric_robust_loss_min_iteration,
318  std::size_t doppler_robust_loss_min_iteration,
319  RobustKernel &geometric_kernel,
320  RobustKernel &doppler_kernel,
321  core::Tensor &transform_vehicle_to_sensor) {
323  period, lambda_doppler, reject_dynamic_outliers,
324  doppler_outlier_threshold,
325  outlier_rejection_min_iteration,
326  geometric_robust_loss_min_iteration,
327  doppler_robust_loss_min_iteration, geometric_kernel,
328  doppler_kernel, transform_vehicle_to_sensor);
329  }),
330  "period"_a, "lambda_doppler"_a, "reject_dynamic_outliers"_a,
331  "doppler_outlier_threshold"_a,
332  "outlier_rejection_min_iteration"_a,
333  "geometric_robust_loss_min_iteration"_a,
334  "doppler_robust_loss_min_iteration"_a, "goemetric_kernel"_a,
335  "doppler_kernel"_a, "transform_vehicle_to_sensor"_a)
336  .def(py::init([](const double lambda_doppler) {
338  lambda_doppler);
339  }),
340  "lambda_doppler"_a)
341  .def("compute_transformation",
342  py::overload_cast<const t::geometry::PointCloud &,
343  const t::geometry::PointCloud &,
344  const core::Tensor &, const core::Tensor &,
345  const std::size_t>(
347  ComputeTransformation,
348  py::const_),
349  "Compute transformation from source to target point cloud "
350  "given correspondences")
351  .def("__repr__",
353  return std::string(
354  "TransformationEstimationForDopplerICP "
355  "with lambda_doppler: ") +
356  std::to_string(te.lambda_doppler_);
357  })
358  .def_readwrite("period",
360  "Time period (in seconds) between the source and "
361  "the target point clouds.")
362  .def_readwrite(
363  "lambda_doppler",
365  "`λ ∈ [0, 1]` in the overall energy `(1−λ)EG + λED`. Refer "
366  "the documentation of DopplerICP for more information.")
367  .def_readwrite("reject_dynamic_outliers",
369  reject_dynamic_outliers_,
370  "Whether or not to reject dynamic point outlier "
371  "correspondences.")
372  .def_readwrite("doppler_outlier_threshold",
374  doppler_outlier_threshold_,
375  "Correspondences with Doppler error greater than "
376  "this threshold are rejected from optimization.")
377  .def_readwrite("outlier_rejection_min_iteration",
379  outlier_rejection_min_iteration_,
380  "Number of iterations of ICP after which outlier "
381  "rejection is enabled.")
382  .def_readwrite("geometric_robust_loss_min_iteration",
384  geometric_robust_loss_min_iteration_,
385  "Minimum iterations after which Robust Kernel is "
386  "used for the Geometric error")
387  .def_readwrite("doppler_robust_loss_min_iteration",
389  doppler_robust_loss_min_iteration_,
390  "Minimum iterations after which Robust Kernel is "
391  "used for the Doppler error")
392  .def_readwrite(
393  "geometric_kernel",
395  "Robust Kernel used in the Geometric Error Optimization")
396  .def_readwrite(
397  "doppler_kernel",
399  "Robust Kernel used in the Doppler Error Optimization")
400  .def_readwrite("transform_vehicle_to_sensor",
402  transform_vehicle_to_sensor_,
403  "The 4x4 extrinsic transformation matrix between "
404  "the vehicle and the sensor frames.");
405  m_registration.def(
406  "evaluate_registration", &EvaluateRegistration,
407  py::call_guard<py::gil_scoped_release>(),
408  "Function for evaluating registration between point clouds",
409  "source"_a, "target"_a, "max_correspondence_distance"_a,
410  "transformation"_a =
412  docstring::FunctionDocInject(m_registration, "evaluate_registration",
414  m_registration.def(
415  "icp", &ICP, py::call_guard<py::gil_scoped_release>(),
416  "Function for ICP registration", "source"_a, "target"_a,
417  "max_correspondence_distance"_a,
418  "init_source_to_target"_a =
420  "estimation_method"_a = TransformationEstimationPointToPoint(),
421  "criteria"_a = ICPConvergenceCriteria(), "voxel_size"_a = -1.0,
422  "callback_after_iteration"_a = py::none());
423  docstring::FunctionDocInject(m_registration, "icp",
425 
426  m_registration.def(
427  "multi_scale_icp", &MultiScaleICP,
428  py::call_guard<py::gil_scoped_release>(),
429  "Function for Multi-Scale ICP registration", "source"_a, "target"_a,
430  "voxel_sizes"_a, "criteria_list"_a,
431  "max_correspondence_distances"_a,
432  "init_source_to_target"_a =
434  "estimation_method"_a = TransformationEstimationPointToPoint(),
435  "callback_after_iteration"_a = py::none());
436  docstring::FunctionDocInject(m_registration, "multi_scale_icp",
438 
439  m_registration.def(
440  "get_information_matrix", &GetInformationMatrix,
441  py::call_guard<py::gil_scoped_release>(),
442  "Function for computing information matrix from transformation "
443  "matrix. Information matrix is tensor of shape {6, 6}, dtype "
444  "Float64 "
445  "on CPU device.",
446  "source"_a, "target"_a, "max_correspondence_distance"_a,
447  "transformation"_a);
448  docstring::FunctionDocInject(m_registration, "get_information_matrix",
450 }
451 
452 void pybind_registration(py::module &m) {
453  py::module m_registration = m.def_submodule(
454  "registration", "Tensor-based registration pipeline.");
455  pybind_registration_class(m_registration);
456 
457  pybind_feature(m_registration);
458  pybind_robust_kernels(m_registration);
459 }
460 
461 } // namespace registration
462 } // namespace pipelines
463 } // namespace t
464 } // namespace cloudViewer
filament::Texture::InternalFormat format
int64_t GetLength() const
Definition: Tensor.h:1125
static Tensor Eye(int64_t n, Dtype dtype, const Device &device)
Create an identity matrix of size n x n.
Definition: Tensor.cpp:418
A point cloud contains a list of 3D points.
Definition: PointCloud.h:82
Class that defines the convergence criteria of ICP.
Definition: Registration.h:31
int max_iteration_
Maximum iteration before iteration stops.
Definition: Registration.h:59
core::Tensor ComputeTransformation(const t::geometry::PointCloud &source, const t::geometry::PointCloud &target, const core::Tensor &correspondences, const core::Tensor &current_transform, const std::size_t iteration) const
TransformationEstimationType GetTransformationEstimationType() const
double ComputeRMSE(const t::geometry::PointCloud &source, const t::geometry::PointCloud &target, const core::Tensor &correspondences) const
core::Tensor transformation_
The estimated transformation matrix of dtype Float64 on CPU device.
Definition: Registration.h:84
bool converged_
Specifies whether the algorithm converged or not.
Definition: Registration.h:95
size_t num_iterations_
Number of iterations the algorithm took to converge.
Definition: Registration.h:97
double inlier_rmse_
RMSE of all inlier correspondences. Lower is better.
Definition: Registration.h:90
double period_
Time period (in seconds) between the source and the target point clouds.
double lambda_doppler_
Factor that weighs the Doppler residual term in DICP objective.
virtual double ComputeRMSE(const geometry::PointCloud &source, const geometry::PointCloud &target, const core::Tensor &correspondences) const =0
virtual core::Tensor ComputeTransformation(const geometry::PointCloud &source, const geometry::PointCloud &target, const core::Tensor &correspondences, const core::Tensor &current_transform=core::Tensor::Eye(4, core::Float64, core::Device("CPU:0")), const std::size_t iteration=0) const =0
const Dtype Float64
Definition: Dtype.cpp:43
void ClassMethodDocInject(py::module &pybind_module, const std::string &class_name, const std::string &function_name, const std::unordered_map< std::string, std::string > &map_parameter_body_docs)
Definition: docstring.cpp:27
void FunctionDocInject(py::module &pybind_module, const std::string &function_name, const std::unordered_map< std::string, std::string > &map_parameter_body_docs)
Definition: docstring.cpp:76
core::Tensor GetInformationMatrix(const geometry::PointCloud &source, const geometry::PointCloud &target, const double max_correspondence_distance, const core::Tensor &transformation)
Computes Information Matrix, from the transfromation between source and target pointcloud....
RegistrationResult ICP(const geometry::PointCloud &source, const geometry::PointCloud &target, const double max_correspondence_distance, const core::Tensor &init_source_to_target, const TransformationEstimation &estimation, const ICPConvergenceCriteria &criteria, const double voxel_size, const std::function< void(const std::unordered_map< std::string, core::Tensor > &)> &callback_after_iteration)
Functions for ICP registration.
RegistrationResult EvaluateRegistration(const geometry::PointCloud &source, const geometry::PointCloud &target, double max_correspondence_distance, const core::Tensor &transformation)
Function for evaluating registration between point clouds.
static const std::unordered_map< std::string, std::string > map_shared_argument_docstrings
RegistrationResult MultiScaleICP(const geometry::PointCloud &source, const geometry::PointCloud &target, const std::vector< double > &voxel_sizes, const std::vector< ICPConvergenceCriteria > &criterias, const std::vector< double > &max_correspondence_distances, const core::Tensor &init_source_to_target, const TransformationEstimation &estimation, const std::function< void(const std::unordered_map< std::string, core::Tensor > &)> &callback_after_iteration)
Functions for Multi-Scale ICP registration. It will run ICP on different voxel level,...
void pybind_registration_class(py::module &m_registration)
void pybind_feature(py::module &m_registration)
Definition: feature.cpp:20
Generic file read and write utility for python interface.
std::string to_string(const T &n)
Definition: Common.h:20