gtsam_points
Loading...
Searching...
No Matches
integrated_ct_icp_factor_impl.hpp
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2021 Kenji Koide (k.koide@aist.go.jp)
3
4#include <gtsam_points/factors/integrated_ct_icp_factor.hpp>
5
6#include <gtsam/linear/HessianFactor.h>
7#include <gtsam_points/config.hpp>
8#include <gtsam_points/ann/kdtree2.hpp>
9#include <gtsam_points/util/parallelism.hpp>
10#include <gtsam_points/factors/impl/scan_matching_reduction.hpp>
11
12#ifdef GTSAM_POINTS_USE_TBB
13#include <tbb/parallel_for.h>
14#endif
15
16namespace gtsam_points {
17
18template <typename TargetFrame, typename SourceFrame>
20 gtsam::Key source_t0_key,
21 gtsam::Key source_t1_key,
22 const std::shared_ptr<const TargetFrame>& target,
23 const std::shared_ptr<const SourceFrame>& source,
24 const std::shared_ptr<const NearestNeighborSearch>& target_tree)
25: gtsam::NonlinearFactor(gtsam::KeyVector{source_t0_key, source_t1_key}),
26 num_threads(1),
27 max_correspondence_distance_sq(1.0),
28 target(target),
29 source(source) {
30 //
31 if (!frame::has_points(*target)) {
32 std::cerr << "error: target frame doesn't have required attributes for ct_icp" << std::endl;
33 abort();
34 }
35
36 if (!frame::has_points(*source) || !frame::has_times(*source)) {
37 std::cerr << "error: source frame doesn't have required attributes for ct_icp" << std::endl;
38 abort();
39 }
40
41 time_table.reserve(frame::size(*source) / 10);
42 time_indices.reserve(frame::size(*source));
43
44 const double time_eps = 1e-3;
45 for (int i = 0; i < frame::size(*source); i++) {
46 const double t = frame::time(*source, i);
47 if (time_table.empty() || t - time_table.back() > time_eps) {
48 time_table.push_back(t);
49 }
50 time_indices.push_back(time_table.size() - 1);
51 }
52
53 for (auto& t : time_table) {
54 t = t / std::max(1e-9, time_table.back());
55 }
56
57 if (target_tree) {
58 this->target_tree = target_tree;
59 } else {
60 this->target_tree.reset(new KdTree2<TargetFrame>(target));
61 }
62}
63
64template <typename TargetFrame, typename SourceFrame>
66 gtsam::Key source_t0_key,
67 gtsam::Key source_t1_key,
68 const std::shared_ptr<const TargetFrame>& target,
69 const std::shared_ptr<const SourceFrame>& source)
70: IntegratedCT_ICPFactor_(source_t0_key, source_t1_key, target, source, nullptr) {}
71
72template <typename TargetFrame, typename SourceFrame>
74
75template <typename TargetFrame, typename SourceFrame>
76void IntegratedCT_ICPFactor_<TargetFrame, SourceFrame>::print(const std::string& s, const gtsam::KeyFormatter& keyFormatter) const {
77 std::cout << s << "IntegratedCT_ICPFactor";
78 std::cout << "(" << keyFormatter(this->keys()[0]) << ", " << keyFormatter(this->keys()[1]) << ")" << std::endl;
79
80 std::cout << "|target|=" << frame::size(*target) << "pts, |source|=" << frame::size(*source) << "pts" << std::endl;
81 std::cout << "num_threads=" << num_threads << ", max_corr_dist=" << std::sqrt(max_correspondence_distance_sq) << std::endl;
82}
83
84template <typename TargetFrame, typename SourceFrame>
86 return sizeof(*this) + sizeof(double) * time_table.capacity() + sizeof(gtsam::Pose3) * source_poses.capacity() +
87 sizeof(gtsam::Matrix6) * pose_derivatives_t0.capacity() + sizeof(gtsam::Matrix6) * pose_derivatives_t1.capacity() +
88 sizeof(long) * correspondences.capacity() + sizeof(int) * time_indices.capacity();
89}
90
91template <typename TargetFrame, typename SourceFrame>
92double IntegratedCT_ICPFactor_<TargetFrame, SourceFrame>::error(const gtsam::Values& values) const {
93 update_poses(values);
94 if (correspondences.size() != frame::size(*source)) {
95 update_correspondences();
96 }
97
98 const auto perpoint_task = [&](
99 int i,
100 Eigen::Matrix<double, 6, 6>* H_target,
101 Eigen::Matrix<double, 6, 6>* H_source,
102 Eigen::Matrix<double, 6, 6>* H_target_source,
103 Eigen::Matrix<double, 6, 1>* b_target,
104 Eigen::Matrix<double, 6, 1>* b_source) {
105 const long target_index = correspondences[i];
106 if (target_index < 0) {
107 return 0.0;
108 }
109
110 const int time_index = time_indices[i];
111 const auto& pose = source_poses[time_index];
112
113 const auto& source_pt = frame::point(*source, i);
114 const auto& target_pt = frame::point(*target, target_index);
115 const auto& target_normal = frame::normal(*target, target_index);
116
117 gtsam::Point3 transed_source_pt = pose.transformFrom(source_pt.template head<3>().eval());
118 gtsam::Point3 residual = transed_source_pt - target_pt.template head<3>();
119 double error = gtsam::dot(residual, target_normal.template head<3>());
120
121 return error * error;
122 };
123
124 if (is_omp_default() || num_threads == 1) {
125 return scan_matching_reduce_omp(perpoint_task, frame::size(*this->source), this->num_threads, nullptr, nullptr, nullptr, nullptr, nullptr);
126 } else {
127 return scan_matching_reduce_tbb(perpoint_task, frame::size(*this->source), nullptr, nullptr, nullptr, nullptr, nullptr);
128 }
129}
130
131template <typename TargetFrame, typename SourceFrame>
132gtsam::GaussianFactor::shared_ptr IntegratedCT_ICPFactor_<TargetFrame, SourceFrame>::linearize(const gtsam::Values& values) const {
133 if (!frame::has_normals(*target)) {
134 std::cerr << "error: target cloud doesn't have normals!!" << std::endl;
135 abort();
136 }
137
138 update_poses(values);
139 update_correspondences();
140
141 const auto perpoint_task = [&](
142 int i,
143 Eigen::Matrix<double, 6, 6>* H_00,
144 Eigen::Matrix<double, 6, 6>* H_11,
145 Eigen::Matrix<double, 6, 6>* H_01,
146 Eigen::Matrix<double, 6, 1>* b_0,
147 Eigen::Matrix<double, 6, 1>* b_1) {
148 const long target_index = correspondences[i];
149 if (target_index < 0) {
150 return 0.0;
151 }
152
153 const int time_index = time_indices[i];
154
155 const auto& pose = source_poses[time_index];
156 const auto& H_pose_0 = pose_derivatives_t0[time_index];
157 const auto& H_pose_1 = pose_derivatives_t1[time_index];
158
159 const auto& source_pt = frame::point(*source, i);
160 const auto& target_pt = frame::point(*target, target_index);
161 const auto& target_normal = frame::normal(*target, target_index);
162
163 gtsam::Matrix36 H_transed_pose;
164 gtsam::Point3 transed_source_pt = pose.transformFrom(source_pt.template head<3>(), H_transed_pose);
165
166 gtsam::Point3 residual = transed_source_pt - target_pt.template head<3>();
167
168 gtsam::Matrix13 H_error_transed;
169 double error = gtsam::dot(residual, target_normal.template head<3>(), H_error_transed);
170
171 gtsam::Matrix16 H_error_pose = H_error_transed * H_transed_pose;
172 gtsam::Matrix16 H_0 = H_error_pose * H_pose_0;
173 gtsam::Matrix16 H_1 = H_error_pose * H_pose_1;
174
175 *H_00 += H_0.transpose() * H_0;
176 *H_11 += H_1.transpose() * H_1;
177 *H_01 += H_0.transpose() * H_1;
178 *b_0 += H_0.transpose() * error;
179 *b_1 += H_1.transpose() * error;
180
181 return error * error;
182 };
183
184 double error = 0.0;
185 gtsam::Matrix6 H_00;
186 gtsam::Matrix6 H_01;
187 gtsam::Matrix6 H_11;
188 gtsam::Vector6 b_0;
189 gtsam::Vector6 b_1;
190
191 if (is_omp_default() || num_threads == 1) {
192 error = scan_matching_reduce_omp(perpoint_task, frame::size(*this->source), this->num_threads, &H_00, &H_11, &H_01, &b_0, &b_1);
193 } else {
194 error = scan_matching_reduce_tbb(perpoint_task, frame::size(*this->source), &H_00, &H_11, &H_01, &b_0, &b_1);
195 }
196
197 auto factor = gtsam::HessianFactor::shared_ptr(new gtsam::HessianFactor(this->keys_[0], this->keys_[1], H_00, H_01, -b_0, H_11, -b_1, error));
198 return factor;
199}
200
201template <typename TargetFrame, typename SourceFrame>
202void IntegratedCT_ICPFactor_<TargetFrame, SourceFrame>::update_poses(const gtsam::Values& values) const {
203 gtsam::Pose3 pose0 = values.at<gtsam::Pose3>(keys_[0]);
204 gtsam::Pose3 pose1 = values.at<gtsam::Pose3>(keys_[1]);
205
206 gtsam::Matrix6 H_delta_0, H_delta_1;
207 gtsam::Pose3 delta = pose0.between(pose1, H_delta_0, H_delta_1);
208
209 gtsam::Matrix6 H_vel_delta;
210 gtsam::Vector6 vel = gtsam::Pose3::Logmap(delta, H_vel_delta);
211
212 source_poses.resize(time_table.size());
213 pose_derivatives_t0.resize(time_table.size());
214 pose_derivatives_t1.resize(time_table.size());
215
216 const auto task = [&](int i) {
217 const double t = time_table[i];
218
219 gtsam::Matrix6 H_inc_vel;
220 gtsam::Pose3 inc = gtsam::Pose3::Expmap(t * vel, H_inc_vel);
221
222 gtsam::Matrix6 H_pose_0_a, H_pose_inc;
223 source_poses[i] = pose0.compose(inc, H_pose_0_a, H_pose_inc);
224
225 gtsam::Matrix6 H_pose_delta = H_pose_inc * H_inc_vel * t * H_vel_delta;
226
227 pose_derivatives_t0[i] = H_pose_0_a + H_pose_delta * H_delta_0;
228 pose_derivatives_t1[i] = H_pose_delta * H_delta_1;
229 };
230
231 if (is_omp_default() || num_threads == 1) {
232#pragma omp parallel for num_threads(this->num_threads) schedule(guided, 32)
233 for (int i = 0; i < time_table.size(); i++) {
234 task(i);
235 }
236 } else {
237#ifdef GTSAM_POINTS_USE_TBB
238 tbb::parallel_for(tbb::blocked_range<int>(0, time_table.size(), 32), [&](const tbb::blocked_range<int>& range) {
239 for (int i = range.begin(); i < range.end(); i++) {
240 task(i);
241 }
242 });
243#else
244 std::cerr << "error: TBB is not available" << std::endl;
245 abort();
246#endif
247 }
248}
249
250template <typename TargetFrame, typename SourceFrame>
251void IntegratedCT_ICPFactor_<TargetFrame, SourceFrame>::update_correspondences() const {
252 correspondences.resize(frame::size(*source));
253
254 const auto perpoint_task = [&](int i) {
255 const int time_index = time_indices[i];
256
257 const auto& pt = frame::point(*source, i);
258 gtsam::Point3 transed_pt = source_poses[time_index] * pt.template head<3>();
259
260 size_t k_index = -1;
261 double k_sq_dist = -1;
262 size_t num_found = target_tree->knn_search(transed_pt.data(), 1, &k_index, &k_sq_dist, max_correspondence_distance_sq);
263
264 if (num_found == 0 || k_sq_dist > max_correspondence_distance_sq) {
265 correspondences[i] = -1;
266 } else {
267 correspondences[i] = k_index;
268 }
269 };
270
271 if (is_omp_default() || num_threads == 1) {
272#pragma omp parallel for num_threads(this->num_threads) schedule(guided, 8)
273 for (int i = 0; i < frame::size(*this->source); i++) {
274 perpoint_task(i);
275 }
276 } else {
277#ifdef GTSAM_POINTS_USE_TBB
278 tbb::parallel_for(tbb::blocked_range<int>(0, frame::size(*this->source), 8), [&](const tbb::blocked_range<int>& range) {
279 for (int i = range.begin(); i < range.end(); i++) {
280 perpoint_task(i);
281 }
282 });
283#else
284 std::cerr << "error: TBB is not available" << std::endl;
285 abort();
286#endif
287 }
288}
289
290template <typename TargetFrame, typename SourceFrame>
291std::vector<Eigen::Vector4d> IntegratedCT_ICPFactor_<TargetFrame, SourceFrame>::deskewed_source_points(const gtsam::Values& values, bool local) {
292 update_poses(values);
293
294 if (local) {
295 for (auto& pose : source_poses) {
296 pose = values.at<gtsam::Pose3>(keys_[0]).inverse() * pose;
297 }
298 }
299
300 std::vector<Eigen::Vector4d> deskewed(frame::size(*source));
301 for (int i = 0; i < frame::size(*source); i++) {
302 const int time_index = time_indices[i];
303 const auto& pose = source_poses[time_index];
304 deskewed[i] = pose.matrix() * (Eigen::Vector4d() << frame::point(*source, i).template head<3>(), 1.0).finished();
305 }
306
307 return deskewed;
308}
309
310} // namespace gtsam_points
Continuous Time ICP Factor Bellenbach et al., "CT-ICP: Real-time Elastic LiDAR Odometry with Loop Clo...
Definition integrated_ct_icp_factor.hpp:21
IntegratedCT_ICPFactor_(gtsam::Key source_t0_key, gtsam::Key source_t1_key, const std::shared_ptr< const TargetFrame > &target, const std::shared_ptr< const SourceFrame > &source, const std::shared_ptr< const NearestNeighborSearch > &target_tree)
Constructor.
Definition integrated_ct_icp_factor_impl.hpp:19
virtual void print(const std::string &s="", const gtsam::KeyFormatter &keyFormatter=gtsam::DefaultKeyFormatter) const override
Print the factor information.
Definition integrated_ct_icp_factor_impl.hpp:76