23 namespace registration {
36 std::tie(
result.correspondences_, distances, counts) =
40 double num_correspondences =
43 if (num_correspondences != 0) {
45 const double squared_error =
51 result.inlier_rmse_ = std::sqrt(squared_error / num_correspondences);
55 "0 correspondence present between the pointclouds. Try "
56 "increasing the max_correspondence_distance parameter.");
73 {core::Float64, core::Float32});
79 source_transformed.
Transform(transformation);
86 "NearestNeighborSearch::HybridSearch: Index is not set.");
101 const double voxel_size,
102 const std::function<
void(
const std::unordered_map<std::string, core::Tensor>
103 &)> &callback_after_iteration) {
104 return MultiScaleICP(source, target, {voxel_size}, {criteria},
106 estimation, callback_after_iteration);
112 const std::vector<double> &voxel_sizes,
113 const std::vector<ICPConvergenceCriteria> &criterias,
114 const std::vector<double> &max_correspondence_distances,
117 const int64_t &num_iterations,
130 "Use Float32 pointcloud for best performance on CUDA device.");
132 if (!(criterias.size() == voxel_sizes.size() &&
133 criterias.size() == max_correspondence_distances.size())) {
135 "Size of criterias, voxel_size, max_correspondence_distances "
136 "vectors must be same.");
142 "TransformationEstimationPointToPlane require pre-computed "
143 "normal vectors for target PointCloud.");
151 "ColoredICP requires target pointcloud to have normals.");
155 "ColoredICP requires target pointcloud to have colors.");
159 "ColoredICP requires source pointcloud to have colors.");
169 "DopplerICP requires target pointcloud to have normals.");
173 "DopplerICP requires source pointcloud to have Doppler "
178 "DopplerICP requires source pointcloud to have "
179 "pre-computed direction vectors.");
183 if (max_correspondence_distances[0] <= 0.0) {
185 " Max correspondence distance must be greater than 0, but"
186 " got {} in scale: {}.",
187 max_correspondence_distances[0], 0);
190 for (int64_t i = 1; i < num_iterations; ++i) {
191 if (voxel_sizes[i] >= voxel_sizes[i - 1]) {
193 " [ICP] Voxel sizes must be in strictly decreasing order.");
195 if (max_correspondence_distances[i] <= 0.0) {
197 " Max correspondence distance must be greater than 0, but"
198 " got {} in scale: {}.",
199 max_correspondence_distances[i], i);
204 static std::tuple<std::vector<t::geometry::PointCloud>,
205 std::vector<t::geometry::PointCloud>>
209 const std::vector<double> &voxel_sizes,
212 const int64_t &num_iterations) {
213 std::vector<t::geometry::PointCloud> source_down_pyramid(num_iterations);
214 std::vector<t::geometry::PointCloud> target_down_pyramid(num_iterations);
216 if (voxel_sizes[num_iterations - 1] <= 0) {
217 source_down_pyramid[num_iterations - 1] = source.
Clone();
218 target_down_pyramid[num_iterations - 1] = target;
220 source_down_pyramid[num_iterations - 1] =
222 target_down_pyramid[num_iterations - 1] =
236 if (voxel_sizes[num_iterations - 1] <= 0) {
238 "Use voxel size parameter, for better performance in "
240 target_down_pyramid[num_iterations - 1].EstimateColorGradients(
243 target_down_pyramid[num_iterations - 1].EstimateColorGradients(
244 30, voxel_sizes[num_iterations - 1] * 4.0);
248 for (
int k = num_iterations - 2; k >= 0; k--) {
249 source_down_pyramid[k] =
250 source_down_pyramid[k + 1].VoxelDownSample(voxel_sizes[k]);
251 target_down_pyramid[k] =
252 target_down_pyramid[k + 1].VoxelDownSample(voxel_sizes[k]);
255 return std::make_tuple(source_down_pyramid, target_down_pyramid);
266 const int prev_iteration_count,
270 const std::function<
void(std::unordered_map<std::string, core::Tensor>
271 &)> &callback_after_iteration) {
273 double prev_fitness = current_result.
fitness_;
275 int iteration_count = 0;
276 for (iteration_count = 0; iteration_count < criteria.
max_iteration_;
285 result.converged_ =
false;
286 result.num_iterations_ = iteration_count;
287 return std::make_tuple(
result,
288 prev_iteration_count + iteration_count);
297 source, target,
result.correspondences_,
298 result.transformation_, iteration_count)
308 "ICP Scale #{:d} Iteration #{:d}: Fitness {:.4f}, RMSE "
310 scale_idx, iteration_count,
result.fitness_,
313 if (callback_after_iteration) {
316 std::unordered_map<std::string, core::Tensor> loss_attribute_map{
318 core::Tensor::Init<int64_t>(prev_iteration_count +
320 {
"scale_index", core::Tensor::Init<int64_t>(scale_idx)},
321 {
"scale_iteration_index",
322 core::Tensor::Init<int64_t>(iteration_count)},
324 core::Tensor::Init<double>(
result.inlier_rmse_)},
325 {
"fitness", core::Tensor::Init<double>(
result.fitness_)},
326 {
"transformation",
result.transformation_.To(host)}};
327 callback_after_iteration(loss_attribute_map);
331 if (iteration_count != 0 &&
339 prev_fitness =
result.fitness_;
340 prev_inlier_rmse =
result.inlier_rmse_;
342 return std::make_tuple(
result, prev_iteration_count + iteration_count);
348 const std::vector<double> &voxel_sizes,
349 const std::vector<ICPConvergenceCriteria> &criterias,
350 const std::vector<double> &max_correspondence_distances,
354 void(
const std::unordered_map<std::string, core::Tensor> &)>
355 &callback_after_iteration) {
357 {core::Float64, core::Float32});
361 const int64_t num_scales = int64_t(criterias.size());
365 max_correspondence_distances,
366 init_source_to_target, estimation, num_scales,
371 std::vector<t::geometry::PointCloud> source_down_pyramid(num_scales);
372 std::vector<t::geometry::PointCloud> target_down_pyramid(num_scales);
373 std::tie(source_down_pyramid, target_down_pyramid) =
375 source, target, voxel_sizes,
376 max_correspondence_distances[num_scales - 1], estimation,
384 int iteration_count = 0;
386 for (int64_t scale_idx = 0; scale_idx < num_scales; ++scale_idx) {
387 source_down_pyramid[scale_idx].Transform(
result.transformation_);
390 target_down_pyramid[scale_idx].GetPointPositions());
392 target_nns.
HybridIndex(max_correspondence_distances[scale_idx]);
399 source_down_pyramid[scale_idx], target_down_pyramid[scale_idx],
400 target_nns, criterias[scale_idx],
401 max_correspondence_distances[scale_idx], estimation, scale_idx,
402 iteration_count, device, dtype,
result,
403 callback_after_iteration);
407 if (scale_idx == num_scales - 1) {
408 bool preserved_converged_flag =
result.converged_;
410 source_down_pyramid[scale_idx], target_nns,
411 max_correspondence_distances[scale_idx],
413 result.converged_ = preserved_converged_flag;
418 result.converged_ =
false;
424 result.num_iterations_ = iteration_count;
438 {core::Float64, core::Float32});
445 source_transformed.
Transform(transformation);
452 std::tie(correspondences, distances, counts) =
457 int32_t num_correspondences = counts.
Sum({0}).Item<int32_t>();
459 if (num_correspondences == 0) {
461 "0 correspondence present between the pointclouds. Try "
462 "increasing the max_correspondence_distance parameter.");
#define AssertTensorDevice(tensor,...)
#define AssertTensorDtype(tensor,...)
#define AssertTensorDtypes(tensor,...)
#define AssertTensorShape(tensor,...)
bool IsCUDA() const
Returns true iff device type is CUDA.
Tensor Matmul(const Tensor &rhs) const
Tensor Sum(const SizeVector &dims, bool keepdim=false) const
int64_t GetLength() const
static Tensor Eye(int64_t n, Dtype dtype, const Device &device)
Create an identity matrix of size n x n.
Tensor To(Dtype dtype, bool copy=false) const
A Class for nearest neighbor search.
bool HybridIndex(utility::optional< double > radius={})
std::tuple< Tensor, Tensor, Tensor > HybridSearch(const Tensor &query_points, const double radius, const int max_knn) const
A point cloud contains a list of 3D points.
PointCloud VoxelDownSample(double voxel_size, const std::string &reduction="mean") const
Downsamples a point cloud with a specified voxel size.
bool HasPointPositions() const
bool HasPointAttr(const std::string &key) const
core::Tensor & GetPointPositions()
Get the value of the "positions" attribute. Convenience function.
core::Device GetDevice() const override
Returns the device attribute of this PointCloud.
bool HasPointNormals() const
PointCloud Clone() const
Returns copy of the point cloud on the same device.
PointCloud & Transform(const core::Tensor &transformation)
Transforms the PointPositions and PointNormals (if exist) of the PointCloud.
bool HasPointColors() const
Class that defines the convergence criteria of ICP.
int max_iteration_
Maximum iteration before iteration stops.
core::Tensor transformation_
The estimated transformation matrix of dtype Float64 on CPU device.
double inlier_rmse_
RMSE of all inlier correspondences. Lower is better.
__host__ __device__ int2 abs(int2 v)
Helper functions for the ml ops.
void To(const core::Tensor &src, core::Tensor &dst, double scale, double offset)
core::Tensor ComputeInformationMatrix(const core::Tensor &target_points, const core::Tensor &correspondence_indices)
Computes Information Matrix of shape {6, 6}, of dtype Float64 on device CPU:0, from the target point ...
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....
static const double max_correspondence_distance
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 void AssertInputMultiScaleICP(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 int64_t &num_iterations, const core::Device &device, const core::Dtype &dtype)
static std::tuple< std::vector< t::geometry::PointCloud >, std::vector< t::geometry::PointCloud > > InitializePointCloudPyramidForMultiScaleICP(const geometry::PointCloud &source, const geometry::PointCloud &target, const std::vector< double > &voxel_sizes, const double &max_correspondence_distance, const TransformationEstimation &estimation, const int64_t &num_iterations)
static RegistrationResult ComputeRegistrationResult(const geometry::PointCloud &source, const core::nns::NearestNeighborSearch &target_nns, const double max_correspondence_distance, const core::Tensor &transformation)
static std::tuple< RegistrationResult, int > DoSingleScaleICPIterations(geometry::PointCloud &source, const geometry::PointCloud &target, const core::nns::NearestNeighborSearch &target_nns, const ICPConvergenceCriteria &criteria, const double max_correspondence_distance, const TransformationEstimation &estimation, const int scale_idx, const int prev_iteration_count, const core::Device &device, const core::Dtype &dtype, const RegistrationResult ¤t_result, const std::function< void(std::unordered_map< std::string, core::Tensor > &)> &callback_after_iteration)
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,...
Generic file read and write utility for python interface.