gtsam_points
Loading...
Searching...
No Matches
integrated_gicp_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_gicp_factor.hpp>
5
6#include <gtsam/geometry/Pose3.h>
7#include <gtsam/linear/HessianFactor.h>
8#include <gtsam_points/config.hpp>
9#include <gtsam_points/ann/kdtree2.hpp>
10#include <gtsam_points/util/compact.hpp>
11#include <gtsam_points/util/parallelism.hpp>
12#include <gtsam_points/types/frame_traits.hpp>
13#include <gtsam_points/factors/impl/scan_matching_reduction.hpp>
14
15#ifdef GTSAM_POINTS_USE_TBB
16#include <tbb/parallel_for.h>
17#endif
18
19namespace gtsam_points {
20
21template <typename TargetFrame, typename SourceFrame>
23 gtsam::Key target_key,
24 gtsam::Key source_key,
25 const std::shared_ptr<const TargetFrame>& target,
26 const std::shared_ptr<const SourceFrame>& source,
27 const std::shared_ptr<const NearestNeighborSearch>& target_tree)
28: gtsam_points::IntegratedMatchingCostFactor(target_key, source_key),
29 num_threads(1),
30 max_correspondence_distance_sq(1.0),
31 mahalanobis_cache_mode(FusedCovCacheMode::FULL),
32 correspondence_update_tolerance_rot(0.0),
33 correspondence_update_tolerance_trans(0.0),
34 target(target),
35 source(source) {
36 //
37 if (!frame::has_points(*target) || !frame::has_covs(*target)) {
38 std::cerr << "error: target frame doesn't have required attributes for gicp" << std::endl;
39 abort();
40 }
41
42 if (!frame::has_points(*source) || !frame::has_covs(*source)) {
43 std::cerr << "error: source frame doesn't have required attributes for gicp" << std::endl;
44 abort();
45 }
46
47 if (target_tree) {
48 this->target_tree = target_tree;
49 } else {
50 this->target_tree.reset(new KdTree2<TargetFrame>(target));
51 }
52}
53
54template <typename TargetFrame, typename SourceFrame>
56 gtsam::Key target_key,
57 gtsam::Key source_key,
58 const std::shared_ptr<const TargetFrame>& target,
59 const std::shared_ptr<const SourceFrame>& source)
60: IntegratedGICPFactor_(target_key, source_key, target, source, nullptr) {}
61
62template <typename TargetFrame, typename SourceFrame>
64 const gtsam::Pose3& fixed_target_pose,
65 gtsam::Key source_key,
66 const std::shared_ptr<const TargetFrame>& target,
67 const std::shared_ptr<const SourceFrame>& source,
68 const std::shared_ptr<const NearestNeighborSearch>& target_tree)
69: gtsam_points::IntegratedMatchingCostFactor(fixed_target_pose, source_key),
70 num_threads(1),
71 max_correspondence_distance_sq(1.0),
72 mahalanobis_cache_mode(FusedCovCacheMode::FULL),
73 correspondence_update_tolerance_rot(0.0),
74 correspondence_update_tolerance_trans(0.0),
75 target(target),
76 source(source) {
77 //
78 if (!frame::has_points(*target) || !frame::has_covs(*target)) {
79 std::cerr << "error: target frame doesn't have required attributes for gicp" << std::endl;
80 abort();
81 }
82
83 if (!frame::has_points(*source) || !frame::has_covs(*source)) {
84 std::cerr << "error: source frame doesn't have required attributes for gicp" << std::endl;
85 abort();
86 }
87
88 if (target_tree) {
89 this->target_tree = target_tree;
90 } else {
91 this->target_tree.reset(new KdTree2<TargetFrame>(target));
92 }
93}
94
95template <typename TargetFrame, typename SourceFrame>
97 const gtsam::Pose3& fixed_target_pose,
98 gtsam::Key source_key,
99 const std::shared_ptr<const TargetFrame>& target,
100 const std::shared_ptr<const SourceFrame>& source)
101: IntegratedGICPFactor_(fixed_target_pose, source_key, target, source, nullptr) {}
102
103template <typename TargetFrame, typename SourceFrame>
104IntegratedGICPFactor_<TargetFrame, SourceFrame>::~IntegratedGICPFactor_() {}
105
106template <typename TargetFrame, typename SourceFrame>
107void IntegratedGICPFactor_<TargetFrame, SourceFrame>::print(const std::string& s, const gtsam::KeyFormatter& keyFormatter) const {
108 std::cout << s << "IntegratedGICPFactor";
109 if (is_binary) {
110 std::cout << "(" << keyFormatter(this->keys()[0]) << ", " << keyFormatter(this->keys()[1]) << ")" << std::endl;
111 } else {
112 std::cout << "(fixed, " << keyFormatter(this->keys()[0]) << ")" << std::endl;
113 }
114
115 std::cout << "|target|=" << frame::size(*target) << "pts, |source|=" << frame::size(*source) << "pts" << std::endl;
116 std::cout << "num_threads=" << num_threads << ", max_corr_dist=" << std::sqrt(max_correspondence_distance_sq)
117 << ", cache_mode=" << static_cast<int>(mahalanobis_cache_mode) << std::endl;
118}
119
125template <typename TargetFrame, typename SourceFrame>
127 return sizeof(*this) + sizeof(long) * correspondences.capacity() + sizeof(Eigen::Matrix4d) * mahalanobis_full.capacity() +
128 sizeof(Eigen::Matrix<float, 6, 1>) * mahalanobis_compact.capacity();
129}
130
131template <typename TargetFrame, typename SourceFrame>
132void IntegratedGICPFactor_<TargetFrame, SourceFrame>::update_correspondences(const Eigen::Isometry3d& delta) const {
133 linearization_point = delta;
134
135 bool do_update = true;
136 if (correspondences.size() == frame::size(*source) && (correspondence_update_tolerance_trans > 0.0 || correspondence_update_tolerance_rot > 0.0)) {
137 Eigen::Isometry3d diff = delta.inverse() * last_correspondence_point;
138 double diff_rot = Eigen::AngleAxisd(diff.linear()).angle();
139 double diff_trans = diff.translation().norm();
140 if (diff_rot < correspondence_update_tolerance_rot && diff_trans < correspondence_update_tolerance_trans) {
141 do_update = false;
142 }
143 }
144
145 if (do_update) {
146 last_correspondence_point = delta;
147 }
148
149 correspondences.resize(frame::size(*source));
150
151 switch (mahalanobis_cache_mode) {
152 case FusedCovCacheMode::FULL:
153 mahalanobis_full.resize(frame::size(*source));
154 break;
155 case FusedCovCacheMode::COMPACT:
156 mahalanobis_compact.resize(frame::size(*source));
157 break;
158 case FusedCovCacheMode::NONE:
159 break;
160 }
161
162 const auto perpoint_task = [&](int i) {
163 if (do_update) {
164 Eigen::Vector4d pt = delta * frame::point(*source, i);
165
166 size_t k_index = -1;
167 double k_sq_dist = -1;
168 size_t num_found = target_tree->knn_search(pt.data(), 1, &k_index, &k_sq_dist, max_correspondence_distance_sq);
169 correspondences[i] = (num_found && k_sq_dist < max_correspondence_distance_sq) ? k_index : -1;
170 }
171
172 switch (mahalanobis_cache_mode) {
173 case FusedCovCacheMode::FULL:
174 if (correspondences[i] < 0) {
175 mahalanobis_full[i].setZero();
176 } else {
177 const auto& target_cov = frame::cov(*target, correspondences[i]);
178 const Eigen::Matrix4d RCR = (target_cov + delta.matrix() * frame::cov(*source, i) * delta.matrix().transpose());
179 mahalanobis_full[i].setZero();
180 mahalanobis_full[i].template topLeftCorner<3, 3>() = RCR.topLeftCorner<3, 3>().inverse();
181 }
182 break;
183 case FusedCovCacheMode::COMPACT:
184 if (correspondences[i] < 0) {
185 mahalanobis_compact[i].setZero();
186 } else {
187 const auto& target_cov = frame::cov(*target, correspondences[i]);
188 const Eigen::Matrix4d RCR = (target_cov + delta.matrix() * frame::cov(*source, i) * delta.matrix().transpose());
189 const Eigen::Matrix3d maha = RCR.topLeftCorner<3, 3>().inverse();
190 mahalanobis_compact[i] = compact_cov(maha);
191 }
192 break;
193 case FusedCovCacheMode::NONE:
194 break;
195 }
196 };
197
198 if (is_omp_default() || num_threads == 1) {
199#pragma omp parallel for num_threads(num_threads) schedule(guided, 8)
200 for (int i = 0; i < frame::size(*source); i++) {
201 perpoint_task(i);
202 }
203 } else {
204#ifdef GTSAM_POINTS_USE_TBB
205 tbb::parallel_for(tbb::blocked_range<int>(0, frame::size(*source), 8), [&](const tbb::blocked_range<int>& range) {
206 for (int i = range.begin(); i < range.end(); i++) {
207 perpoint_task(i);
208 }
209 });
210#else
211 std::cerr << "error: TBB is not available" << std::endl;
212 abort();
213#endif
214 }
215}
216
217template <typename TargetFrame, typename SourceFrame>
218double IntegratedGICPFactor_<TargetFrame, SourceFrame>::evaluate(
219 const Eigen::Isometry3d& delta,
220 Eigen::Matrix<double, 6, 6>* H_target,
221 Eigen::Matrix<double, 6, 6>* H_source,
222 Eigen::Matrix<double, 6, 6>* H_target_source,
223 Eigen::Matrix<double, 6, 1>* b_target,
224 Eigen::Matrix<double, 6, 1>* b_source) const {
225 //
226 if (correspondences.size() != frame::size(*source)) {
227 update_correspondences(delta);
228 }
229
230 const auto perpoint_task = [&](
231 int i,
232 Eigen::Matrix<double, 6, 6>* H_target,
233 Eigen::Matrix<double, 6, 6>* H_source,
234 Eigen::Matrix<double, 6, 6>* H_target_source,
235 Eigen::Matrix<double, 6, 1>* b_target,
236 Eigen::Matrix<double, 6, 1>* b_source) {
237 const long target_index = correspondences[i];
238 if (target_index < 0) {
239 return 0.0;
240 }
241
242 const auto& mean_A = frame::point(*source, i);
243 const auto& cov_A = frame::cov(*source, i);
244 const auto& mean_B = frame::point(*target, target_index);
245 const auto& cov_B = frame::cov(*target, target_index);
246
247 const Eigen::Vector4d transed_mean_A = delta * mean_A;
248 const Eigen::Vector4d residual = mean_B - transed_mean_A;
249
250 Eigen::Matrix4d mahalanobis;
251 switch (mahalanobis_cache_mode) {
252 case FusedCovCacheMode::FULL:
253 mahalanobis = mahalanobis_full[i];
254 break;
255 case FusedCovCacheMode::COMPACT:
256 mahalanobis = uncompact_cov(mahalanobis_compact[i]);
257 break;
258 case FusedCovCacheMode::NONE: {
259 const auto& delta_l = linearization_point; // Delta at the linearization point
260 const Eigen::Matrix4d RCR = (cov_B + delta_l.matrix() * cov_A * delta_l.matrix().transpose());
261 mahalanobis.setZero();
262 mahalanobis.topLeftCorner<3, 3>() = RCR.topLeftCorner<3, 3>().inverse();
263 } break;
264 }
265
266 const double error = residual.transpose() * mahalanobis * residual;
267 if (H_target == nullptr) {
268 return error;
269 }
270
271 Eigen::Matrix<double, 4, 6> J_target = Eigen::Matrix<double, 4, 6>::Zero();
272 J_target.block<3, 3>(0, 0) = -gtsam::SO3::Hat(transed_mean_A.head<3>());
273 J_target.block<3, 3>(0, 3) = Eigen::Matrix3d::Identity();
274
275 Eigen::Matrix<double, 4, 6> J_source = Eigen::Matrix<double, 4, 6>::Zero();
276 J_source.block<3, 3>(0, 0) = delta.linear() * gtsam::SO3::Hat(mean_A.template head<3>());
277 J_source.block<3, 3>(0, 3) = -delta.linear();
278
279 Eigen::Matrix<double, 6, 4> J_target_mahalanobis = J_target.transpose() * mahalanobis;
280 Eigen::Matrix<double, 6, 4> J_source_mahalanobis = J_source.transpose() * mahalanobis;
281
282 *H_target += J_target_mahalanobis * J_target;
283 *H_source += J_source_mahalanobis * J_source;
284 *H_target_source += J_target_mahalanobis * J_source;
285 *b_target += J_target_mahalanobis * residual;
286 *b_source += J_source_mahalanobis * residual;
287
288 return error;
289 };
290
291 if (is_omp_default() || num_threads == 1) {
292 return scan_matching_reduce_omp(perpoint_task, frame::size(*source), num_threads, H_target, H_source, H_target_source, b_target, b_source);
293 } else {
294 return scan_matching_reduce_tbb(perpoint_task, frame::size(*source), H_target, H_source, H_target_source, b_target, b_source);
295 }
296}
297
298} // namespace gtsam_points
Generalized ICP matching cost factor Segal et al., "Generalized-ICP", RSS2005.
Definition integrated_gicp_factor.hpp:31
IntegratedGICPFactor_(gtsam::Key target_key, gtsam::Key source_key, const std::shared_ptr< const TargetFrame > &target, const std::shared_ptr< const SourceFrame > &source, const std::shared_ptr< const NearestNeighborSearch > &target_tree)
Create a binary ICP factor between target and source poses.
Definition integrated_gicp_factor_impl.hpp:22
virtual size_t memory_usage() const override
Calculate the memory usage of this factor.
Definition integrated_gicp_factor_impl.hpp:126
virtual void print(const std::string &s="", const gtsam::KeyFormatter &keyFormatter=gtsam::DefaultKeyFormatter) const override
Print the factor information.
Definition integrated_gicp_factor_impl.hpp:107
Abstraction of LSQ-based scan matching constraints between point clouds.
Definition integrated_matching_cost_factor.hpp:19