ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
TransformationEstimation.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 
13 
14 namespace cloudViewer {
15 namespace t {
16 namespace pipelines {
17 namespace registration {
18 
20  const core::Tensor &correspondence_indices,
21  const core::Tensor &source_points) {
22  core::AssertTensorDtype(correspondence_indices, core::Int64);
23  core::AssertTensorDevice(correspondence_indices, source_points.GetDevice());
24 
25  if (correspondence_indices.GetShape() !=
26  core::SizeVector({source_points.GetLength(), 1}) &&
27  correspondence_indices.GetShape() !=
28  core::SizeVector({source_points.GetLength()})) {
30  "Correspondences must be of same length as source point-cloud "
31  "positions. Expected correspondences of shape {} or {}, but "
32  "got {}.",
33  core::SizeVector({source_points.GetLength()}).ToString(),
34  core::SizeVector({source_points.GetLength(), 1}).ToString(),
35  correspondence_indices.GetShape().ToString());
36  }
37 }
38 
40  const geometry::PointCloud &source,
41  const geometry::PointCloud &target,
42  const core::Tensor &correspondences) const {
43  if (!target.HasPointPositions() || !source.HasPointPositions()) {
44  utility::LogError("Source and/or Target pointcloud is empty.");
45  }
46 
48  {core::Float64, core::Float32});
50  source.GetPointPositions().GetDtype());
52 
53  AssertValidCorrespondences(correspondences, source.GetPointPositions());
54 
55  core::Tensor valid = correspondences.Ne(-1).Reshape({-1});
56  core::Tensor neighbour_indices =
57  correspondences.IndexGet({valid}).Reshape({-1});
58  core::Tensor source_points_indexed =
59  source.GetPointPositions().IndexGet({valid});
60  core::Tensor target_points_indexed =
61  target.GetPointPositions().IndexGet({neighbour_indices});
62 
63  core::Tensor error_t = (source_points_indexed - target_points_indexed);
64  error_t.Mul_(error_t);
65  double error = error_t.Sum({0, 1}).To(core::Float64).Item<double>();
66  return std::sqrt(error /
67  static_cast<double>(neighbour_indices.GetLength()));
68 }
69 
71  const geometry::PointCloud &source,
72  const geometry::PointCloud &target,
73  const core::Tensor &correspondences,
74  const core::Tensor &current_transform,
75  const std::size_t iteration) const {
76  if (!target.HasPointPositions() || !source.HasPointPositions()) {
77  utility::LogError("Source and/or Target pointcloud is empty.");
78  }
79 
81  {core::Float64, core::Float32});
83  source.GetPointPositions().GetDtype());
85 
86  AssertValidCorrespondences(correspondences, source.GetPointPositions());
87 
88  core::Tensor R, t;
89  // Get tuple of Rotation {3, 3} and Translation {3} of type Float64.
91  source.GetPointPositions(), target.GetPointPositions(),
92  correspondences);
93 
94  // Get rigid transformation tensor of {4, 4} of type Float64 on CPU:0
95  // device, from rotation {3, 3} and translation {3}.
97 }
98 
100  const geometry::PointCloud &source,
101  const geometry::PointCloud &target,
102  const core::Tensor &correspondences) const {
103  if (!target.HasPointPositions() || !source.HasPointPositions()) {
104  utility::LogError("Source and/or Target pointcloud is empty.");
105  }
106  if (!target.HasPointNormals()) {
107  utility::LogError("Target pointcloud missing normals attribute.");
108  }
109 
111  source.GetPointPositions().GetDtype());
113 
114  AssertValidCorrespondences(correspondences, source.GetPointPositions());
115 
116  core::Tensor valid = correspondences.Ne(-1).Reshape({-1});
117  core::Tensor neighbour_indices =
118  correspondences.IndexGet({valid}).Reshape({-1});
119  core::Tensor source_points_indexed =
120  source.GetPointPositions().IndexGet({valid});
121  core::Tensor target_points_indexed =
122  target.GetPointPositions().IndexGet({neighbour_indices});
123  core::Tensor target_normals_indexed =
124  target.GetPointNormals().IndexGet({neighbour_indices});
125 
126  core::Tensor error_t = (source_points_indexed - target_points_indexed)
127  .Mul_(target_normals_indexed);
128  error_t.Mul_(error_t);
129  double error = error_t.Sum({0, 1}).To(core::Float64).Item<double>();
130  return std::sqrt(error /
131  static_cast<double>(neighbour_indices.GetLength()));
132 }
133 
135  const geometry::PointCloud &source,
136  const geometry::PointCloud &target,
137  const core::Tensor &correspondences,
138  const core::Tensor &current_transform,
139  const std::size_t iteration) const {
140  if (!target.HasPointPositions() || !source.HasPointPositions()) {
141  utility::LogError("Source and/or Target pointcloud is empty.");
142  }
143  if (!target.HasPointNormals()) {
144  utility::LogError("Target pointcloud missing normals attribute.");
145  }
146 
148  {core::Float64, core::Float32});
150  source.GetPointPositions().GetDtype());
152  source.GetPointPositions().GetDtype());
154 
155  AssertValidCorrespondences(correspondences, source.GetPointPositions());
156 
157  // Get pose {6} of type Float64.
159  source.GetPointPositions(), target.GetPointPositions(),
160  target.GetPointNormals(), correspondences, this->kernel_);
161 
162  // Get rigid transformation tensor of {4, 4} of type Float64 on CPU:0
163  // device, from pose {6}.
165 }
166 
168  const geometry::PointCloud &source,
169  const geometry::PointCloud &target,
170  const core::Tensor &correspondences) const {
171  if (!target.HasPointPositions() || !source.HasPointPositions()) {
172  utility::LogError("Source and/or Target pointcloud is empty.");
173  }
174  if (!target.HasPointColors() || !source.HasPointColors()) {
176  "Source and/or Target pointcloud missing colors attribute.");
177  }
178  if (!target.HasPointNormals()) {
179  utility::LogError("Target pointcloud missing normals attribute.");
180  }
181  if (!target.HasPointAttr("color_gradients")) {
183  "Target pointcloud missing color_gradients attribute.");
184  }
185 
186  const core::Device device = source.GetPointPositions().GetDevice();
188 
189  const core::Dtype dtype = source.GetPointPositions().GetDtype();
190  core::AssertTensorDtype(source.GetPointColors(), dtype);
192  core::AssertTensorDtype(target.GetPointNormals(), dtype);
193  core::AssertTensorDtype(target.GetPointAttr("color_gradients"), dtype);
194  core::AssertTensorDtype(correspondences, core::Int64);
195  core::AssertTensorDevice(correspondences,
196  source.GetPointPositions().GetDevice());
197  core::AssertTensorShape(correspondences,
198  {source.GetPointPositions().GetLength()});
199 
200  double sqrt_lambda_geometric = sqrt(lambda_geometric_);
201  double lambda_photometric = 1.0 - lambda_geometric_;
202  double sqrt_lambda_photometric = sqrt(lambda_photometric);
203 
204  core::Tensor valid = correspondences.Ne(-1).Reshape({-1});
205  core::Tensor neighbour_indices =
206  correspondences.IndexGet({valid}).Reshape({-1});
207 
208  // vs - source points (or vertices)
209  // vt - target points
210  // nt - target normals
211  // cs - source colors
212  // ct - target colors
213  // dit - target color gradients
214  // is - source intensity
215  // it - target intensity
216  // vs_proj - source points projection
217  // is_proj - source intensity projection
218 
219  core::Tensor vs = source.GetPointPositions().IndexGet({valid});
220  core::Tensor cs = source.GetPointColors().IndexGet({valid});
221 
222  core::Tensor vt = target.GetPointPositions().IndexGet({neighbour_indices});
223  core::Tensor nt = target.GetPointNormals().IndexGet({neighbour_indices});
224  core::Tensor ct = target.GetPointColors().IndexGet({neighbour_indices});
225  core::Tensor dit = target.GetPointAttr("color_gradients")
226  .IndexGet({neighbour_indices});
227 
228  // vs_proj = vs - (vs - vt).dot(nt) * nt
229  // d = (vs - vt).dot(nt)
230  const core::Tensor d = (vs - vt).Mul(nt).Sum({1});
231  core::Tensor vs_proj = vs - d.Mul(nt);
232 
233  core::Tensor is = cs.Mean({1});
234  core::Tensor it = ct.Mean({1});
235 
236  // is_proj = (dit.dot(vs_proj - vt)) + it
237  core::Tensor is_proj = (dit.Mul(vs_proj - vt)).Sum({1}).Add(it);
238 
239  core::Tensor residual_geometric = d.Mul(sqrt_lambda_geometric).Sum({1});
240  core::Tensor sq_residual_geometric =
241  residual_geometric.Mul(residual_geometric);
242  core::Tensor residual_photometric =
243  (is - is_proj).Mul(sqrt_lambda_photometric).Sum({1});
244  core::Tensor sq_residual_photometric =
245  residual_photometric.Mul(residual_photometric);
246 
247  double residual = sq_residual_geometric.Add_(sq_residual_photometric)
248  .Sum({0})
249  .To(core::Float64)
250  .Item<double>();
251 
252  return residual;
253 }
254 
256  const geometry::PointCloud &source,
257  const geometry::PointCloud &target,
258  const core::Tensor &correspondences,
259  const core::Tensor &current_transform,
260  const std::size_t iteration) const {
261  if (!target.HasPointPositions() || !source.HasPointPositions()) {
262  utility::LogError("Source and/or Target pointcloud is empty.");
263  }
264  if (!target.HasPointPositions() || !source.HasPointPositions()) {
265  utility::LogError("Source and/or Target pointcloud is empty.");
266  }
267  if (!target.HasPointColors() || !source.HasPointColors()) {
269  "Source and/or Target pointcloud missing colors attribute.");
270  }
271  if (!target.HasPointNormals()) {
272  utility::LogError("Target pointcloud missing normals attribute.");
273  }
274  if (!target.HasPointAttr("color_gradients")) {
276  "Target pointcloud missing color_gradients attribute.");
277  }
278 
280  {core::Float64, core::Float32});
281  const core::Dtype dtype = source.GetPointPositions().GetDtype();
282 
283  core::AssertTensorDtype(source.GetPointColors(), dtype);
285  core::AssertTensorDtype(target.GetPointNormals(), dtype);
286  core::AssertTensorDtype(target.GetPointColors(), dtype);
287  core::AssertTensorDtype(target.GetPointAttr("color_gradients"), dtype);
288 
290 
291  AssertValidCorrespondences(correspondences, source.GetPointPositions());
292 
293  // Get pose {6} of type Float64 from correspondences indexed source and
294  // target point cloud.
296  source.GetPointPositions(), source.GetPointColors(),
297  target.GetPointPositions(), target.GetPointNormals(),
298  target.GetPointColors(), target.GetPointAttr("color_gradients"),
299  correspondences, this->kernel_, this->lambda_geometric_);
300 
301  // Get transformation {4,4} of type Float64 from pose {6}.
303 
304  return transform;
305 }
306 
308  const geometry::PointCloud &source,
309  const geometry::PointCloud &target,
310  const core::Tensor &correspondences) const {
311  if (!target.HasPointPositions() || !source.HasPointPositions()) {
312  utility::LogError("Source and/or Target pointcloud is empty.");
313  }
314  if (!target.HasPointNormals()) {
315  utility::LogError("Target pointcloud missing normals attribute.");
316  }
317 
319  source.GetPointPositions().GetDtype());
321 
322  AssertValidCorrespondences(correspondences, source.GetPointPositions());
323 
324  core::Tensor valid = correspondences.Ne(-1).Reshape({-1});
325  core::Tensor neighbour_indices =
326  correspondences.IndexGet({valid}).Reshape({-1});
327  core::Tensor source_points_indexed =
328  source.GetPointPositions().IndexGet({valid});
329  core::Tensor target_points_indexed =
330  target.GetPointPositions().IndexGet({neighbour_indices});
331  core::Tensor target_normals_indexed =
332  target.GetPointNormals().IndexGet({neighbour_indices});
333 
334  core::Tensor error_t = (source_points_indexed - target_points_indexed)
335  .Mul_(target_normals_indexed);
336  error_t.Mul_(error_t);
337  double error = error_t.Sum({0, 1}).To(core::Float64).Item<double>();
338  return std::sqrt(error /
339  static_cast<double>(neighbour_indices.GetLength()));
340 }
341 
343  const geometry::PointCloud &source,
344  const geometry::PointCloud &target,
345  const core::Tensor &correspondences,
346  const core::Tensor &current_transform,
347  const std::size_t iteration) const {
348  if (!source.HasPointPositions() || !target.HasPointPositions()) {
349  utility::LogError("Source and/or Target pointcloud is empty.");
350  }
351  if (!target.HasPointNormals()) {
352  utility::LogError("Target pointcloud missing normals attribute.");
353  }
354  if (!source.HasPointAttr("dopplers")) {
355  utility::LogError("Source pointcloud missing dopplers attribute.");
356  }
357  if (!source.HasPointAttr("directions")) {
358  utility::LogError("Source pointcloud missing directions attribute.");
359  }
360 
362  {core::Float64, core::Float32});
363  const core::Dtype dtype = source.GetPointPositions().GetDtype();
364 
366  core::AssertTensorDtype(target.GetPointNormals(), dtype);
367  core::AssertTensorDtype(source.GetPointAttr("dopplers"), dtype);
368  core::AssertTensorDtype(source.GetPointAttr("directions"), dtype);
369 
371 
372  AssertValidCorrespondences(correspondences, source.GetPointPositions());
373 
374  // Get pose {6} of type Float64 from correspondences indexed source
375  // and target point cloud.
377  source.GetPointPositions(), source.GetPointAttr("dopplers"),
378  source.GetPointAttr("directions"), target.GetPointPositions(),
379  target.GetPointNormals(), correspondences, current_transform,
380  this->transform_vehicle_to_sensor_, iteration, this->period_,
381  this->lambda_doppler_, this->reject_dynamic_outliers_,
382  this->doppler_outlier_threshold_,
383  this->outlier_rejection_min_iteration_,
384  this->geometric_robust_loss_min_iteration_,
385  this->doppler_robust_loss_min_iteration_, this->geometric_kernel_,
386  this->doppler_kernel_);
387 
388  // Get transformation {4,4} of type Float64 from pose {6}.
390 
391  return transform;
392 }
393 
394 } // namespace registration
395 } // namespace pipelines
396 } // namespace t
397 } // namespace cloudViewer
void Add(const double in1[2], const double in2[2], double out[2])
Definition: Factor.cpp:130
#define AssertTensorDevice(tensor,...)
Definition: TensorCheck.h:45
#define AssertTensorDtype(tensor,...)
Definition: TensorCheck.h:21
#define AssertTensorDtypes(tensor,...)
Definition: TensorCheck.h:33
#define AssertTensorShape(tensor,...)
Definition: TensorCheck.h:61
std::string ToString() const
Definition: SizeVector.cpp:132
Tensor Sum(const SizeVector &dims, bool keepdim=false) const
Definition: Tensor.cpp:1240
int64_t GetLength() const
Definition: Tensor.h:1125
Tensor Ne(const Tensor &value) const
Element-wise not-equals-to of tensors, returning a new boolean tensor.
Definition: Tensor.cpp:1714
Dtype GetDtype() const
Definition: Tensor.h:1164
Tensor IndexGet(const std::vector< Tensor > &index_tensors) const
Advanced indexing getter. This will always allocate a new Tensor.
Definition: Tensor.cpp:905
Tensor Mul_(const Tensor &value)
Definition: Tensor.cpp:1189
Tensor Add_(const Tensor &value)
Definition: Tensor.cpp:1117
Device GetDevice() const override
Definition: Tensor.cpp:1435
Tensor Reshape(const SizeVector &dst_shape) const
Definition: Tensor.cpp:671
Tensor Mean(const SizeVector &dims, bool keepdim=false) const
Definition: Tensor.cpp:1247
SizeVector GetShape() const
Definition: Tensor.h:1127
Tensor Mul(const Tensor &value) const
Multiplies a tensor and returns the resulting tensor.
Definition: Tensor.cpp:1169
A point cloud contains a list of 3D points.
Definition: PointCloud.h:82
core::Tensor & GetPointNormals()
Get the value of the "normals" attribute. Convenience function.
Definition: PointCloud.h:130
bool HasPointAttr(const std::string &key) const
Definition: PointCloud.h:207
core::Tensor & GetPointPositions()
Get the value of the "positions" attribute. Convenience function.
Definition: PointCloud.h:124
core::Device GetDevice() const override
Returns the device attribute of this PointCloud.
Definition: PointCloud.h:421
const TensorMap & GetPointAttr() const
Getter for point_attr_ TensorMap. Used in Pybind.
Definition: PointCloud.h:111
core::Tensor & GetPointColors()
Get the value of the "colors" attribute. Convenience function.
Definition: PointCloud.h:127
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 override
Estimates the transformation matrix for ColoredICP method, a tensor of shape {4, 4},...
double ComputeRMSE(const geometry::PointCloud &source, const geometry::PointCloud &target, const core::Tensor &correspondences) const override
Computes RMSE (double) for ColoredICP method, between two pointclouds, given correspondences.
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 override
Estimates the transformation matrix for DopplerICP method, a tensor of shape {4, 4},...
double ComputeRMSE(const geometry::PointCloud &source, const geometry::PointCloud &target, const core::Tensor &correspondences) const override
Computes RMSE (double) for DopplerICP method, between two pointclouds, given correspondences.
double ComputeRMSE(const geometry::PointCloud &source, const geometry::PointCloud &target, const core::Tensor &correspondences) const override
Computes RMSE (double) for PointToPlane method, between two pointclouds, given correspondences.
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 override
Estimates the transformation matrix for PointToPlane method, a tensor of shape {4,...
double ComputeRMSE(const geometry::PointCloud &source, const geometry::PointCloud &target, const core::Tensor &correspondences) const override
Computes RMSE (double) for PointToPoint method, between two pointclouds, given correspondences.
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 override
Estimates the transformation matrix for PointToPoint method, a tensor of shape {4,...
#define LogError(...)
Definition: Logging.h:60
static void error(char *msg)
Definition: lsd.c:159
const Dtype Int64
Definition: Dtype.cpp:47
const Dtype Float64
Definition: Dtype.cpp:43
void To(const core::Tensor &src, core::Tensor &dst, double scale, double offset)
Definition: Image.cpp:17
core::Tensor RtToTransformation(const core::Tensor &R, const core::Tensor &t)
Convert rotation and translation to the transformation matrix.
core::Tensor ComputePoseDopplerICP(const core::Tensor &source_points, const core::Tensor &source_dopplers, const core::Tensor &source_directions, const core::Tensor &target_points, const core::Tensor &target_normals, const core::Tensor &correspondence_indices, const core::Tensor &current_transform, const core::Tensor &transform_vehicle_to_sensor, const std::size_t iteration, const double period, const double lambda_doppler, const bool reject_dynamic_outliers, const double doppler_outlier_threshold, const std::size_t outlier_rejection_min_iteration, const std::size_t geometric_robust_loss_min_iteration, const std::size_t doppler_robust_loss_min_iteration, const registration::RobustKernel &geometric_kernel, const registration::RobustKernel &doppler_kernel)
Computes pose for DopplerICP registration method.
std::tuple< core::Tensor, core::Tensor > ComputeRtPointToPoint(const core::Tensor &source_points, const core::Tensor &target_points, const core::Tensor &correspondence_indices)
Computes (R) Rotation {3,3} and (t) translation {3,} for point to point registration method.
core::Tensor ComputePosePointToPlane(const core::Tensor &source_points, const core::Tensor &target_points, const core::Tensor &target_normals, const core::Tensor &correspondence_indices, const registration::RobustKernel &kernel)
Computes pose for point to plane registration method.
core::Tensor ComputePoseColoredICP(const core::Tensor &source_points, const core::Tensor &source_colors, const core::Tensor &target_points, const core::Tensor &target_normals, const core::Tensor &target_colors, const core::Tensor &target_color_gradients, const core::Tensor &correspondence_indices, const registration::RobustKernel &kernel, const double &lambda_geometric)
Computes pose for colored-icp registration method.
core::Tensor PoseToTransformation(const core::Tensor &pose)
Convert pose to the transformation matrix.
static void AssertValidCorrespondences(const core::Tensor &correspondence_indices, const core::Tensor &source_points)
Generic file read and write utility for python interface.