cpp-toolbox  0.0.1
A toolbox library for C++
Loading...
Searching...
No Matches
point_cloud_metrics.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cmath>
5#include <limits>
6#include <numeric>
7#include <unordered_map>
8#include <vector>
9
10#include <Eigen/Core>
11
15
16namespace toolbox::metrics
17{
18
25template<typename T>
26class HausdorffMetric : public base_metric_t<HausdorffMetric<T>, T>
27{
28public:
29 using element_type = T;
32
33 // For compatibility with base_metric_t, we use pointers to clouds
34 constexpr T distance_impl(const T* cloud_a, const T* cloud_b, std::size_t size) const
35 {
36 // This overload is not used for point clouds
37 throw std::runtime_error("HausdorffMetric requires point cloud objects, not raw arrays");
38 }
39
40 T distance(const cloud_type& cloud_a, const cloud_type& cloud_b) const
41 {
42 if (cloud_a.empty() || cloud_b.empty()) {
43 return std::numeric_limits<T>::infinity();
44 }
45
46 // Compute directed Hausdorff distance from A to B
47 T max_dist_a_to_b = 0;
48 for (const auto& point_a : cloud_a.points) {
49 T min_dist = std::numeric_limits<T>::max();
50 for (const auto& point_b : cloud_b.points) {
51 T dist = point_a.distance(point_b);
52 min_dist = std::min(min_dist, dist);
53 }
54 max_dist_a_to_b = std::max(max_dist_a_to_b, min_dist);
55 }
56
57 // Compute directed Hausdorff distance from B to A
58 T max_dist_b_to_a = 0;
59 for (const auto& point_b : cloud_b.points) {
60 T min_dist = std::numeric_limits<T>::max();
61 for (const auto& point_a : cloud_a.points) {
62 T dist = point_b.distance(point_a);
63 min_dist = std::min(min_dist, dist);
64 }
65 max_dist_b_to_a = std::max(max_dist_b_to_a, min_dist);
66 }
67
68 // Return the maximum of both directions
69 return std::max(max_dist_a_to_b, max_dist_b_to_a);
70 }
71
72 constexpr T squared_distance_impl(const T* a, const T* b, std::size_t size) const
73 {
74 T dist = distance_impl(a, b, size);
75 return dist * dist;
76 }
77};
78
84template<typename T>
85class ModifiedHausdorffMetric : public base_metric_t<ModifiedHausdorffMetric<T>, T>
86{
87public:
88 using element_type = T;
91
92 explicit ModifiedHausdorffMetric(std::size_t k = 1) : k_(k) {}
93
94 constexpr T distance_impl(const T* cloud_a, const T* cloud_b, std::size_t size) const
95 {
96 throw std::runtime_error("ModifiedHausdorffMetric requires point cloud objects");
97 }
98
99 T distance(const cloud_type& cloud_a, const cloud_type& cloud_b) const
100 {
101 if (cloud_a.empty() || cloud_b.empty()) {
102 return std::numeric_limits<T>::infinity();
103 }
104
105 // Ensure k is valid
106 std::size_t k_a = std::min(k_, cloud_a.size());
107 std::size_t k_b = std::min(k_, cloud_b.size());
108
109 // Compute distances from each point in A to all points in B
110 std::vector<T> min_dists_a;
111 min_dists_a.reserve(cloud_a.size());
112
113 for (const auto& point_a : cloud_a.points) {
114 T min_dist = std::numeric_limits<T>::max();
115 for (const auto& point_b : cloud_b.points) {
116 T dist = point_a.distance(point_b);
117 min_dist = std::min(min_dist, dist);
118 }
119 min_dists_a.push_back(min_dist);
120 }
121
122 // Sort and take average of k smallest
123 std::partial_sort(min_dists_a.begin(), min_dists_a.begin() + k_a, min_dists_a.end());
124 T avg_a_to_b = std::accumulate(min_dists_a.begin(), min_dists_a.begin() + k_a, T(0)) / k_a;
125
126 // Compute distances from each point in B to all points in A
127 std::vector<T> min_dists_b;
128 min_dists_b.reserve(cloud_b.size());
129
130 for (const auto& point_b : cloud_b.points) {
131 T min_dist = std::numeric_limits<T>::max();
132 for (const auto& point_a : cloud_a.points) {
133 T dist = point_b.distance(point_a);
134 min_dist = std::min(min_dist, dist);
135 }
136 min_dists_b.push_back(min_dist);
137 }
138
139 // Sort and take average of k smallest
140 std::partial_sort(min_dists_b.begin(), min_dists_b.begin() + k_b, min_dists_b.end());
141 T avg_b_to_a = std::accumulate(min_dists_b.begin(), min_dists_b.begin() + k_b, T(0)) / k_b;
142
143 return std::max(avg_a_to_b, avg_b_to_a);
144 }
145
146 constexpr T squared_distance_impl(const T* a, const T* b, std::size_t size) const
147 {
148 T dist = distance_impl(a, b, size);
149 return dist * dist;
150 }
151
152private:
153 std::size_t k_;
154};
155
161template<typename T>
162class ChamferMetric : public base_metric_t<ChamferMetric<T>, T>
163{
164public:
165 using element_type = T;
168
169 constexpr T distance_impl(const T* cloud_a, const T* cloud_b, std::size_t size) const
170 {
171 throw std::runtime_error("ChamferMetric requires point cloud objects");
172 }
173
174 T distance(const cloud_type& cloud_a, const cloud_type& cloud_b) const
175 {
176 if (cloud_a.empty() || cloud_b.empty()) {
177 return std::numeric_limits<T>::infinity();
178 }
179
180 // Compute average distance from A to B
181 T sum_a_to_b = 0;
182 for (const auto& point_a : cloud_a.points) {
183 T min_dist = std::numeric_limits<T>::max();
184 for (const auto& point_b : cloud_b.points) {
185 T dist = point_a.distance(point_b);
186 min_dist = std::min(min_dist, dist);
187 }
188 sum_a_to_b += min_dist;
189 }
190 T avg_a_to_b = sum_a_to_b / cloud_a.size();
191
192 // Compute average distance from B to A
193 T sum_b_to_a = 0;
194 for (const auto& point_b : cloud_b.points) {
195 T min_dist = std::numeric_limits<T>::max();
196 for (const auto& point_a : cloud_a.points) {
197 T dist = point_b.distance(point_a);
198 min_dist = std::min(min_dist, dist);
199 }
200 sum_b_to_a += min_dist;
201 }
202 T avg_b_to_a = sum_b_to_a / cloud_b.size();
203
204 // Return average of both directions
205 return (avg_a_to_b + avg_b_to_a) / 2;
206 }
207
208 constexpr T squared_distance_impl(const T* a, const T* b, std::size_t size) const
209 {
210 T dist = distance_impl(a, b, size);
211 return dist * dist;
212 }
213};
214
220template<typename T>
221class PointCloudEMDMetric : public base_metric_t<PointCloudEMDMetric<T>, T>
222{
223public:
224 using element_type = T;
227
228 constexpr T distance_impl(const T* cloud_a, const T* cloud_b, std::size_t size) const
229 {
230 throw std::runtime_error("PointCloudEMDMetric requires point cloud objects");
231 }
232
233 T distance(const cloud_type& cloud_a, const cloud_type& cloud_b) const
234 {
235 if (cloud_a.empty() || cloud_b.empty()) {
236 return std::numeric_limits<T>::infinity();
237 }
238
239 // For equal-sized clouds, compute minimum matching cost
240 if (cloud_a.size() == cloud_b.size()) {
241 // Simple greedy matching (not optimal but fast)
242 std::vector<bool> matched_b(cloud_b.size(), false);
243 T total_cost = 0;
244
245 for (const auto& point_a : cloud_a.points) {
246 T min_dist = std::numeric_limits<T>::max();
247 std::size_t best_match = 0;
248
249 for (std::size_t j = 0; j < cloud_b.size(); ++j) {
250 if (!matched_b[j]) {
251 T dist = point_a.distance(cloud_b.points[j]);
252 if (dist < min_dist) {
253 min_dist = dist;
254 best_match = j;
255 }
256 }
257 }
258
259 matched_b[best_match] = true;
260 total_cost += min_dist;
261 }
262
263 return total_cost / cloud_a.size();
264 }
265 else {
266 // For different-sized clouds, use Chamfer distance as approximation
267 ChamferMetric<T> chamfer;
268 return chamfer.distance(cloud_a, cloud_b);
269 }
270 }
271
272 constexpr T squared_distance_impl(const T* a, const T* b, std::size_t size) const
273 {
274 T dist = distance_impl(a, b, size);
275 return dist * dist;
276 }
277};
278
284template<typename T>
285class CentroidMetric : public base_metric_t<CentroidMetric<T>, T>
286{
287public:
288 using element_type = T;
291
292 constexpr T distance_impl(const T* cloud_a, const T* cloud_b, std::size_t size) const
293 {
294 throw std::runtime_error("CentroidMetric requires point cloud objects");
295 }
296
297 T distance(const cloud_type& cloud_a, const cloud_type& cloud_b) const
298 {
299 if (cloud_a.empty() || cloud_b.empty()) {
300 return std::numeric_limits<T>::infinity();
301 }
302
303 // Compute centroid of cloud A
304 point_type centroid_a(0, 0, 0);
305 for (const auto& point : cloud_a.points) {
306 centroid_a.x += point.x;
307 centroid_a.y += point.y;
308 centroid_a.z += point.z;
309 }
310 centroid_a.x /= cloud_a.size();
311 centroid_a.y /= cloud_a.size();
312 centroid_a.z /= cloud_a.size();
313
314 // Compute centroid of cloud B
315 point_type centroid_b(0, 0, 0);
316 for (const auto& point : cloud_b.points) {
317 centroid_b.x += point.x;
318 centroid_b.y += point.y;
319 centroid_b.z += point.z;
320 }
321 centroid_b.x /= cloud_b.size();
322 centroid_b.y /= cloud_b.size();
323 centroid_b.z /= cloud_b.size();
324
325 return centroid_a.distance(centroid_b);
326 }
327
328 constexpr T squared_distance_impl(const T* a, const T* b, std::size_t size) const
329 {
330 T dist = distance_impl(a, b, size);
331 return dist * dist;
332 }
333};
334
340template<typename T>
341class BoundingBoxMetric : public base_metric_t<BoundingBoxMetric<T>, T>
342{
343public:
344 using element_type = T;
347
348 enum class Mode {
349 CENTER_DISTANCE, // Distance between bounding box centers
350 MIN_DISTANCE, // Minimum distance between bounding boxes
351 IOU_DISTANCE // 1 - IoU (Intersection over Union)
352 };
353
354 explicit BoundingBoxMetric(Mode mode = Mode::CENTER_DISTANCE) : mode_(mode) {}
355
356 constexpr T distance_impl(const T* cloud_a, const T* cloud_b, std::size_t size) const
357 {
358 throw std::runtime_error("BoundingBoxMetric requires point cloud objects");
359 }
360
361 T distance(const cloud_type& cloud_a, const cloud_type& cloud_b) const
362 {
363 if (cloud_a.empty() || cloud_b.empty()) {
364 return std::numeric_limits<T>::infinity();
365 }
366
367 // Compute bounding box for cloud A
368 auto [min_a, max_a] = compute_bounding_box(cloud_a);
369
370 // Compute bounding box for cloud B
371 auto [min_b, max_b] = compute_bounding_box(cloud_b);
372
373 switch (mode_) {
375 point_type center_a((min_a.x + max_a.x) / 2,
376 (min_a.y + max_a.y) / 2,
377 (min_a.z + max_a.z) / 2);
378 point_type center_b((min_b.x + max_b.x) / 2,
379 (min_b.y + max_b.y) / 2,
380 (min_b.z + max_b.z) / 2);
381 return center_a.distance(center_b);
382 }
383
384 case Mode::MIN_DISTANCE: {
385 // Check if boxes overlap
386 bool overlap_x = !(max_a.x < min_b.x || max_b.x < min_a.x);
387 bool overlap_y = !(max_a.y < min_b.y || max_b.y < min_a.y);
388 bool overlap_z = !(max_a.z < min_b.z || max_b.z < min_a.z);
389
390 if (overlap_x && overlap_y && overlap_z) {
391 return T(0); // Boxes overlap
392 }
393
394 // Compute minimum distance between boxes
395 T dx = std::max(T(0), std::max(min_a.x - max_b.x, min_b.x - max_a.x));
396 T dy = std::max(T(0), std::max(min_a.y - max_b.y, min_b.y - max_a.y));
397 T dz = std::max(T(0), std::max(min_a.z - max_b.z, min_b.z - max_a.z));
398
399 return std::sqrt(dx * dx + dy * dy + dz * dz);
400 }
401
402 case Mode::IOU_DISTANCE: {
403 // Compute intersection
404 T inter_x = std::max(T(0), std::min(max_a.x, max_b.x) - std::max(min_a.x, min_b.x));
405 T inter_y = std::max(T(0), std::min(max_a.y, max_b.y) - std::max(min_a.y, min_b.y));
406 T inter_z = std::max(T(0), std::min(max_a.z, max_b.z) - std::max(min_a.z, min_b.z));
407 T intersection_volume = inter_x * inter_y * inter_z;
408
409 // Compute volumes
410 T volume_a = (max_a.x - min_a.x) * (max_a.y - min_a.y) * (max_a.z - min_a.z);
411 T volume_b = (max_b.x - min_b.x) * (max_b.y - min_b.y) * (max_b.z - min_b.z);
412 T union_volume = volume_a + volume_b - intersection_volume;
413
414 if (union_volume < std::numeric_limits<T>::epsilon()) {
415 return T(1); // Both boxes are degenerate
416 }
417
418 T iou = intersection_volume / union_volume;
419 return T(1) - iou;
420 }
421 }
422
423 return T(0);
424 }
425
426 constexpr T squared_distance_impl(const T* a, const T* b, std::size_t size) const
427 {
428 T dist = distance_impl(a, b, size);
429 return dist * dist;
430 }
431
432private:
433 Mode mode_;
434
435 std::pair<point_type, point_type> compute_bounding_box(const cloud_type& cloud) const
436 {
437 point_type min_pt = cloud.points[0];
438 point_type max_pt = cloud.points[0];
439
440 for (const auto& point : cloud.points) {
441 min_pt.x = std::min(min_pt.x, point.x);
442 min_pt.y = std::min(min_pt.y, point.y);
443 min_pt.z = std::min(min_pt.z, point.z);
444
445 max_pt.x = std::max(max_pt.x, point.x);
446 max_pt.y = std::max(max_pt.y, point.y);
447 max_pt.z = std::max(max_pt.z, point.z);
448 }
449
450 return {min_pt, max_pt};
451 }
452};
453
463template<typename T>
464class LCPMetric : public base_metric_t<LCPMetric<T>, T>
465{
466public:
467 using element_type = T;
470 using transformation_type = Eigen::Matrix<T, 4, 4>;
471
476 explicit LCPMetric(T inlier_threshold = T(1.0))
477 : inlier_threshold_(inlier_threshold) {}
478
479 constexpr T distance_impl(const T* cloud_a, const T* cloud_b, std::size_t size) const
480 {
481 throw std::runtime_error("LCPMetric requires point cloud objects and transformation");
482 }
483
493 const cloud_type& target,
494 const transformation_type& transform,
495 std::vector<std::size_t>* inliers = nullptr) const
496 {
497 if (source.empty() || target.empty()) {
498 return std::numeric_limits<T>::max();
499 }
500
501 // Clear inliers vector if provided
502 if (inliers) {
503 inliers->clear();
504 inliers->reserve(source.size());
505 }
506
507 T total_distance = 0;
508 std::size_t inlier_count = 0;
509 const T threshold_squared = inlier_threshold_ * inlier_threshold_;
510
511 // Extract rotation and translation from transformation
512 Eigen::Matrix<T, 3, 3> rotation = transform.template block<3, 3>(0, 0);
513 Eigen::Matrix<T, 3, 1> translation = transform.template block<3, 1>(0, 3);
514
515 // For each source point
516 for (std::size_t i = 0; i < source.size(); ++i) {
517 const auto& src_pt = source.points[i];
518
519 // Apply transformation
520 Eigen::Matrix<T, 3, 1> src_vec(src_pt.x, src_pt.y, src_pt.z);
521 Eigen::Matrix<T, 3, 1> transformed = rotation * src_vec + translation;
522 point_type transformed_pt(transformed[0], transformed[1], transformed[2]);
523
524 // Find nearest neighbor in target cloud
525 T min_dist_squared = std::numeric_limits<T>::max();
526 for (const auto& tgt_pt : target.points) {
527 T dx = transformed_pt.x - tgt_pt.x;
528 T dy = transformed_pt.y - tgt_pt.y;
529 T dz = transformed_pt.z - tgt_pt.z;
530 T dist_squared = dx * dx + dy * dy + dz * dz;
531 min_dist_squared = std::min(min_dist_squared, dist_squared);
532 }
533
534 // Check if within threshold
535 if (min_dist_squared <= threshold_squared) {
536 total_distance += std::sqrt(min_dist_squared);
537 inlier_count++;
538 if (inliers) {
539 inliers->push_back(i);
540 }
541 }
542 }
543
544 // Return average distance of inliers
545 if (inlier_count == 0) {
546 return std::numeric_limits<T>::max();
547 }
548 return total_distance / inlier_count;
549 }
550
551
556 void set_inlier_threshold(T threshold) { inlier_threshold_ = threshold; }
557
562 [[nodiscard]] T get_inlier_threshold() const { return inlier_threshold_; }
563
564 constexpr T squared_distance_impl(const T* a, const T* b, std::size_t size) const
565 {
566 T dist = distance_impl(a, b, size);
567 return dist * dist;
568 }
569
570private:
571 T inlier_threshold_;
572};
573
574} // namespace toolbox::metrics
Bounding box distance between two point clouds.
Definition point_cloud_metrics.hpp:342
toolbox::types::point_cloud_t< T > cloud_type
Definition point_cloud_metrics.hpp:346
constexpr T squared_distance_impl(const T *a, const T *b, std::size_t size) const
Definition point_cloud_metrics.hpp:426
Mode
Definition point_cloud_metrics.hpp:348
constexpr T distance_impl(const T *cloud_a, const T *cloud_b, std::size_t size) const
Definition point_cloud_metrics.hpp:356
toolbox::types::point_t< T > point_type
Definition point_cloud_metrics.hpp:345
T distance(const cloud_type &cloud_a, const cloud_type &cloud_b) const
Definition point_cloud_metrics.hpp:361
BoundingBoxMetric(Mode mode=Mode::CENTER_DISTANCE)
Definition point_cloud_metrics.hpp:354
T element_type
Definition point_cloud_metrics.hpp:344
Centroid distance between two point clouds.
Definition point_cloud_metrics.hpp:286
constexpr T distance_impl(const T *cloud_a, const T *cloud_b, std::size_t size) const
Definition point_cloud_metrics.hpp:292
T element_type
Definition point_cloud_metrics.hpp:288
constexpr T squared_distance_impl(const T *a, const T *b, std::size_t size) const
Definition point_cloud_metrics.hpp:328
T distance(const cloud_type &cloud_a, const cloud_type &cloud_b) const
Definition point_cloud_metrics.hpp:297
Chamfer distance between two point clouds.
Definition point_cloud_metrics.hpp:163
constexpr T distance_impl(const T *cloud_a, const T *cloud_b, std::size_t size) const
Definition point_cloud_metrics.hpp:169
T element_type
Definition point_cloud_metrics.hpp:165
T distance(const cloud_type &cloud_a, const cloud_type &cloud_b) const
Definition point_cloud_metrics.hpp:174
constexpr T squared_distance_impl(const T *a, const T *b, std::size_t size) const
Definition point_cloud_metrics.hpp:208
Hausdorff distance between two point clouds.
Definition point_cloud_metrics.hpp:27
T distance(const cloud_type &cloud_a, const cloud_type &cloud_b) const
Definition point_cloud_metrics.hpp:40
constexpr T distance_impl(const T *cloud_a, const T *cloud_b, std::size_t size) const
Definition point_cloud_metrics.hpp:34
constexpr T squared_distance_impl(const T *a, const T *b, std::size_t size) const
Definition point_cloud_metrics.hpp:72
T element_type
Definition point_cloud_metrics.hpp:29
LCP (Largest Common Pointset) metric for evaluating point cloud registration.
Definition point_cloud_metrics.hpp:465
void set_inlier_threshold(T threshold)
Set inlier threshold.
Definition point_cloud_metrics.hpp:556
constexpr T squared_distance_impl(const T *a, const T *b, std::size_t size) const
Definition point_cloud_metrics.hpp:564
Eigen::Matrix< T, 4, 4 > transformation_type
Definition point_cloud_metrics.hpp:470
T get_inlier_threshold() const
Get inlier threshold.
Definition point_cloud_metrics.hpp:562
T element_type
Definition point_cloud_metrics.hpp:467
T compute_lcp_score(const cloud_type &source, const cloud_type &target, const transformation_type &transform, std::vector< std::size_t > *inliers=nullptr) const
Compute LCP score between two point clouds.
Definition point_cloud_metrics.hpp:492
LCPMetric(T inlier_threshold=T(1.0))
Constructor.
Definition point_cloud_metrics.hpp:476
constexpr T distance_impl(const T *cloud_a, const T *cloud_b, std::size_t size) const
Definition point_cloud_metrics.hpp:479
Modified Hausdorff distance (average of k-th smallest distances)
Definition point_cloud_metrics.hpp:86
T distance(const cloud_type &cloud_a, const cloud_type &cloud_b) const
Definition point_cloud_metrics.hpp:99
constexpr T distance_impl(const T *cloud_a, const T *cloud_b, std::size_t size) const
Definition point_cloud_metrics.hpp:94
constexpr T squared_distance_impl(const T *a, const T *b, std::size_t size) const
Definition point_cloud_metrics.hpp:146
ModifiedHausdorffMetric(std::size_t k=1)
Definition point_cloud_metrics.hpp:92
T element_type
Definition point_cloud_metrics.hpp:88
Earth Mover's Distance (EMD) for point clouds.
Definition point_cloud_metrics.hpp:222
T element_type
Definition point_cloud_metrics.hpp:224
T distance(const cloud_type &cloud_a, const cloud_type &cloud_b) const
Definition point_cloud_metrics.hpp:233
constexpr T distance_impl(const T *cloud_a, const T *cloud_b, std::size_t size) const
Definition point_cloud_metrics.hpp:228
constexpr T squared_distance_impl(const T *a, const T *b, std::size_t size) const
Definition point_cloud_metrics.hpp:272
Definition base_metric.hpp:13
包含点和相关数据的点云类 / A point cloud class containing points and associated data
Definition point.hpp:268
std::vector< point_t< T > > points
点坐标 / Point coordinates
Definition point.hpp:270
auto size() const -> std::size_t
获取点云中的点数 / Get number of points in cloud
Definition point_impl.hpp:293
auto empty() const -> bool
检查点云是否为空 / Check if cloud is empty
Definition point_impl.hpp:299
Definition angular_metrics.hpp:11
3D点/向量模板类 / A 3D point/vector template class
Definition point.hpp:48
T x
X坐标 / X coordinate.
Definition point.hpp:51
T z
Z坐标 / Z coordinate.
Definition point.hpp:53
auto distance(const point_t &other) const -> double
计算到另一点的欧几里得距离 / Calculate Euclidean distance to another point
Definition point_impl.hpp:144
T y
Y坐标 / Y coordinate.
Definition point.hpp:52