iterative closest points
CEV ICP algorithm library
Loading...
Searching...
No Matches
trimmed.cpp
Go to the documentation of this file.
1/*
2 * @author Ethan Uppal
3 * @copyright Copyright (C) 2024 Ethan Uppal. All rights reserved.
4 */
5
6#include <cassert>
7#include <cstdlib>
8#include "../icp.h"
9#include <Eigen/Core>
10#include <Eigen/SVD>
11#include <Eigen/Geometry>
12
13/* #name Trimmed */
14
15/* #desc Trimmed ICP is identical to \ref vanilla_icp with the addition of an
16overlap rate parameter, which specifies the percentage of points between the two
17point sets that have correspondences. When the overlap rate is 1, the algorithm
18reduces to vanilla. */
19
20namespace icp {
21 struct Trimmed final : public ICP {
22 double overlap_rate;
23 std::vector<icp::Vector> a_current;
24 icp::Vector b_cm;
25
26 Trimmed(double overlap_rate): ICP(), overlap_rate(overlap_rate) {}
27 ~Trimmed() override {}
28
29 void setup() override {
30 if (a_current.size() < a.size()) {
31 a_current.resize(a.size());
32 }
33
34 b_cm = get_centroid(b);
35 }
36
37 void iterate() override {
38 const size_t n = a.size();
39 const size_t m = b.size();
40
41 for (size_t i = 0; i < n; i++) {
42 a_current[i] = transform.apply_to(a[i]);
43 }
44
45 /* #step Matching Step: see \ref vanilla_icp for details. */
46 for (size_t i = 0; i < n; i++) {
47 matches[i].point = i;
48 matches[i].sq_dist = std::numeric_limits<double>::infinity();
49 for (size_t j = 0; j < m; j++) {
50 // Point-to-point matching
51 double dist_ij = (b[j] - a_current[i]).squaredNorm();
52
53 if (dist_ij < matches[i].sq_dist) {
54 matches[i].sq_dist = dist_ij;
55 matches[i].pair = j;
56 }
57 }
58 }
59
60 /*
61 #step
62 Trimming Step
63
64 Matches are considered in increasing order of distance.
65
66 Sources:
67 https://ieeexplore.ieee.org/abstract/document/1047997
68 */
69 std::sort(matches.begin(), matches.end(),
70 [](const auto& a, const auto& b) { return a.sq_dist < b.sq_dist; });
71 size_t new_n = (size_t)(overlap_rate * n);
72
73 // yeah, i know this is inefficient. we'll get back to it later.
74 std::vector<icp::Vector> trimmed_current(new_n);
75 std::vector<icp::Vector> trimmed_b(new_n);
76 for (size_t i = 0; i < new_n; i++) {
77 trimmed_current[i] = a_current[matches[i].point];
78 trimmed_b[i] = b[matches[i].point];
79 }
80
81 icp::Vector trimmed_cm = get_centroid(trimmed_current);
82 icp::Vector trimmed_b_cm = get_centroid(trimmed_b);
83
84 Matrix N{};
85 for (size_t i = 0; i < new_n; i++) {
86 N += (trimmed_current[i] - trimmed_cm) * (trimmed_b[i] - trimmed_b_cm).transpose();
87 }
88
89 auto svd = N.jacobiSvd(Eigen::ComputeFullU | Eigen::ComputeFullV);
90 Matrix U = svd.matrixU();
91 Matrix V = svd.matrixV();
92 Matrix R = V * U.transpose();
93
94 /*
95 #step
96 Reflection Handling
97
98 SVD may return a reflection instead of a rotation. For 2D scans, this case is
99 exceedingly rare because it only happens when our "a" scan is colinear, as far as I
100 can tell (at least in a noiseless case). If this happens in a noisy case and the
101 points aren't somewhat colinear, I don't know of a method to recover a good
102 rotation. So we will just assume little noise and guess that the scan is colinear.
103 The source below claims that the least squares solution is probably useless in a
104 similar case in 3D.
105
106 TODO: This is the 2D translation of what the source does. Can we prove that this
107 actually works?
108
109 Sources:
110 https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=4767965
111 */
112 if (R.determinant() < 0) {
113 // clearly average angle is a wrong idea lol
114 std::cout << "Reflection" << std::endl;
115 std::cout << "Matrix V: " << V << std::endl;
116 std::cout << "Matrix U: " << U << std::endl;
117 std::cout << "Sing vals: " << svd.singularValues() << std::endl;
118
119 V = V * Eigen::DiagonalMatrix<double, 2>(1, -1);
120 R = V * U.transpose();
121 }
122
124
125 /* #step Transformation Step: see \ref vanilla_icp for details. */
126 transform.translation += trimmed_b_cm - R * trimmed_cm;
127 }
128 };
129
130 static bool static_initialization = []() {
131 assert(ICP::register_method("trimmed",
132 [](const ICP::Config& config) -> std::unique_ptr<ICP> {
133 /* #conf "overlap_rate" A `double` between `0.0` and `1.0` for
134 * the overlap rate. The default is `1.0`. */
135 double overlap_rate = config.get<double>("overlap_rate", 1.0);
136 assert(overlap_rate >= 0 && overlap_rate <= 1);
137 return std::make_unique<Trimmed>(overlap_rate);
138 }));
139 return true;
140 }();
141}
static bool register_method(std::string name, std::function< std::unique_ptr< ICP >(const Config &)> constructor)
Registers a new ICP method that can be created with constructor, returning false if name has already ...
Definition icp.cpp:85
RBTransform transform
The current point cloud transformation that is being optimized.
Definition icp.h:58
ICP()
Definition icp.cpp:12
std::vector< Vector > a
The source point cloud relative to its centroid.
Definition icp.h:61
std::vector< Vector > b
The destination point cloud relative to its centroid.
Definition icp.h:64
std::vector< Match > matches
The pairing of each point in a to its closest in b.
Definition icp.h:74
Definition geo.cpp:9
Eigen::Vector2d Vector
Definition geo.h:15
Vector get_centroid(const std::vector< Vector > &points)
Definition geo.cpp:10
Eigen::Matrix2d Matrix
Definition geo.h:16
Matrix rotation
Definition geo.h:21
Vector apply_to(Vector v) const
Definition geo.h:31
Vector translation
Definition geo.h:20