fast_correlative_scan_matcher_3d.cc
Go to the documentation of this file.
1 /*
2  * Copyright 2016 The Cartographer Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
18 
19 #include <algorithm>
20 #include <cmath>
21 #include <functional>
22 #include <limits>
23 
24 #include "Eigen/Geometry"
28 #include "cartographer/mapping/proto/scan_matching//fast_correlative_scan_matcher_options_3d.pb.h"
30 #include "glog/logging.h"
31 
32 namespace cartographer {
33 namespace mapping {
34 namespace scan_matching {
35 
36 proto::FastCorrelativeScanMatcherOptions3D
38  common::LuaParameterDictionary* const parameter_dictionary) {
39  proto::FastCorrelativeScanMatcherOptions3D options;
40  options.set_branch_and_bound_depth(
41  parameter_dictionary->GetInt("branch_and_bound_depth"));
42  options.set_full_resolution_depth(
43  parameter_dictionary->GetInt("full_resolution_depth"));
44  options.set_min_rotational_score(
45  parameter_dictionary->GetDouble("min_rotational_score"));
46  options.set_min_low_resolution_score(
47  parameter_dictionary->GetDouble("min_low_resolution_score"));
48  options.set_linear_xy_search_window(
49  parameter_dictionary->GetDouble("linear_xy_search_window"));
50  options.set_linear_z_search_window(
51  parameter_dictionary->GetDouble("linear_z_search_window"));
52  options.set_angular_search_window(
53  parameter_dictionary->GetDouble("angular_search_window"));
54  return options;
55 }
56 
58  const HybridGrid& hybrid_grid,
59  const proto::FastCorrelativeScanMatcherOptions3D& options) {
60  CHECK_GE(options.branch_and_bound_depth(), 1);
61  CHECK_GE(options.full_resolution_depth(), 1);
62  precomputation_grids_.reserve(options.branch_and_bound_depth());
63  precomputation_grids_.push_back(ConvertToPrecomputationGrid(hybrid_grid));
64  Eigen::Array3i last_width = Eigen::Array3i::Ones();
65  for (int depth = 1; depth != options.branch_and_bound_depth(); ++depth) {
66  const bool half_resolution = depth >= options.full_resolution_depth();
67  const Eigen::Array3i next_width = ((1 << depth) * Eigen::Array3i::Ones());
68  const int full_voxels_per_high_resolution_voxel =
69  1 << std::max(0, depth - options.full_resolution_depth());
70  const Eigen::Array3i shift = (next_width - last_width +
71  (full_voxels_per_high_resolution_voxel - 1)) /
72  full_voxels_per_high_resolution_voxel;
73  precomputation_grids_.push_back(
74  PrecomputeGrid(precomputation_grids_.back(), half_resolution, shift));
75  last_width = next_width;
76  }
77 }
78 
81  // Contains a vector of discretized scans for each 'depth'.
82  std::vector<std::vector<Eigen::Array3i>> cell_indices_per_depth;
84 };
85 
86 struct Candidate3D {
87  Candidate3D(const int scan_index, const Eigen::Array3i& offset)
88  : scan_index(scan_index), offset(offset) {}
89 
91  return Candidate3D(0, Eigen::Array3i::Zero());
92  }
93 
94  // Index into the discrete scans vectors.
96 
97  // Linear offset from the initial pose in cell indices. For lower resolution
98  // candidates this is the lowest offset of the 2^depth x 2^depth x 2^depth
99  // block of possibilities.
100  Eigen::Array3i offset;
101 
102  // Score, higher is better.
103  float score = -std::numeric_limits<float>::infinity();
104 
105  // Score of the low resolution matcher.
106  float low_resolution_score = 0.f;
107 
108  bool operator<(const Candidate3D& other) const { return score < other.score; }
109  bool operator>(const Candidate3D& other) const { return score > other.score; }
110 };
111 
112 namespace {
113 
114 std::vector<std::pair<Eigen::VectorXf, float>> HistogramsAtAnglesFromNodes(
115  const std::vector<TrajectoryNode>& nodes) {
116  std::vector<std::pair<Eigen::VectorXf, float>> histograms_at_angles;
117  for (const auto& node : nodes) {
118  histograms_at_angles.emplace_back(
119  node.constant_data->rotational_scan_matcher_histogram,
121  node.global_pose *
123  node.constant_data->gravity_alignment.inverse())));
124  }
125  return histograms_at_angles;
126 }
127 
128 } // namespace
129 
131  const HybridGrid& hybrid_grid,
132  const HybridGrid* const low_resolution_hybrid_grid,
133  const std::vector<TrajectoryNode>& nodes,
134  const proto::FastCorrelativeScanMatcherOptions3D& options)
135  : options_(options),
136  resolution_(hybrid_grid.resolution()),
137  width_in_voxels_(hybrid_grid.grid_size()),
138  precomputation_grid_stack_(
139  common::make_unique<PrecomputationGridStack3D>(hybrid_grid, options)),
140  low_resolution_hybrid_grid_(low_resolution_hybrid_grid),
141  rotational_scan_matcher_(HistogramsAtAnglesFromNodes(nodes)) {}
142 
144 
145 std::unique_ptr<FastCorrelativeScanMatcher3D::Result>
147  const transform::Rigid3d& global_node_pose,
148  const transform::Rigid3d& global_submap_pose,
149  const TrajectoryNode::Data& constant_data, const float min_score) const {
150  const auto low_resolution_matcher = scan_matching::CreateLowResolutionMatcher(
152  const SearchParameters search_parameters{
153  common::RoundToInt(options_.linear_xy_search_window() / resolution_),
154  common::RoundToInt(options_.linear_z_search_window() / resolution_),
155  options_.angular_search_window(), &low_resolution_matcher};
157  search_parameters, global_node_pose.cast<float>(),
158  global_submap_pose.cast<float>(),
159  constant_data.high_resolution_point_cloud,
160  constant_data.rotational_scan_matcher_histogram,
161  constant_data.gravity_alignment, min_score);
162 }
163 
164 std::unique_ptr<FastCorrelativeScanMatcher3D::Result>
166  const Eigen::Quaterniond& global_node_rotation,
167  const Eigen::Quaterniond& global_submap_rotation,
168  const TrajectoryNode::Data& constant_data, const float min_score) const {
169  float max_point_distance = 0.f;
170  for (const Eigen::Vector3f& point :
171  constant_data.high_resolution_point_cloud) {
172  max_point_distance = std::max(max_point_distance, point.norm());
173  }
174  const int linear_window_size =
175  (width_in_voxels_ + 1) / 2 +
176  common::RoundToInt(max_point_distance / resolution_ + 0.5f);
177  const auto low_resolution_matcher = scan_matching::CreateLowResolutionMatcher(
179  const SearchParameters search_parameters{
180  linear_window_size, linear_window_size, M_PI, &low_resolution_matcher};
182  search_parameters,
183  transform::Rigid3f::Rotation(global_node_rotation.cast<float>()),
184  transform::Rigid3f::Rotation(global_submap_rotation.cast<float>()),
185  constant_data.high_resolution_point_cloud,
186  constant_data.rotational_scan_matcher_histogram,
187  constant_data.gravity_alignment, min_score);
188 }
189 
190 std::unique_ptr<FastCorrelativeScanMatcher3D::Result>
192  const FastCorrelativeScanMatcher3D::SearchParameters& search_parameters,
193  const transform::Rigid3f& global_node_pose,
194  const transform::Rigid3f& global_submap_pose,
195  const sensor::PointCloud& point_cloud,
196  const Eigen::VectorXf& rotational_scan_matcher_histogram,
197  const Eigen::Quaterniond& gravity_alignment, const float min_score) const {
198  const std::vector<DiscreteScan3D> discrete_scans = GenerateDiscreteScans(
199  search_parameters, point_cloud, rotational_scan_matcher_histogram,
200  gravity_alignment, global_node_pose, global_submap_pose);
201 
202  const std::vector<Candidate3D> lowest_resolution_candidates =
203  ComputeLowestResolutionCandidates(search_parameters, discrete_scans);
204 
205  const Candidate3D best_candidate = BranchAndBound(
206  search_parameters, discrete_scans, lowest_resolution_candidates,
207  precomputation_grid_stack_->max_depth(), min_score);
208  if (best_candidate.score > min_score) {
209  return common::make_unique<Result>(Result{
210  best_candidate.score,
211  GetPoseFromCandidate(discrete_scans, best_candidate).cast<double>(),
212  discrete_scans[best_candidate.scan_index].rotational_score,
213  best_candidate.low_resolution_score});
214  }
215  return nullptr;
216 }
217 
219  const FastCorrelativeScanMatcher3D::SearchParameters& search_parameters,
220  const sensor::PointCloud& point_cloud, const transform::Rigid3f& pose,
221  const float rotational_score) const {
222  std::vector<std::vector<Eigen::Array3i>> cell_indices_per_depth;
223  const PrecomputationGrid3D& original_grid =
225  std::vector<Eigen::Array3i> full_resolution_cell_indices;
226  for (const Eigen::Vector3f& point :
227  sensor::TransformPointCloud(point_cloud, pose)) {
228  full_resolution_cell_indices.push_back(original_grid.GetCellIndex(point));
229  }
230  const int full_resolution_depth = std::min(options_.full_resolution_depth(),
231  options_.branch_and_bound_depth());
232  CHECK_GE(full_resolution_depth, 1);
233  for (int i = 0; i != full_resolution_depth; ++i) {
234  cell_indices_per_depth.push_back(full_resolution_cell_indices);
235  }
236  const int low_resolution_depth =
237  options_.branch_and_bound_depth() - full_resolution_depth;
238  CHECK_GE(low_resolution_depth, 0);
239  const Eigen::Array3i search_window_start(
240  -search_parameters.linear_xy_window_size,
241  -search_parameters.linear_xy_window_size,
242  -search_parameters.linear_z_window_size);
243  for (int i = 0; i != low_resolution_depth; ++i) {
244  const int reduction_exponent = i + 1;
245  const Eigen::Array3i low_resolution_search_window_start(
246  search_window_start[0] >> reduction_exponent,
247  search_window_start[1] >> reduction_exponent,
248  search_window_start[2] >> reduction_exponent);
249  cell_indices_per_depth.emplace_back();
250  for (const Eigen::Array3i& cell_index : full_resolution_cell_indices) {
251  const Eigen::Array3i cell_at_start = cell_index + search_window_start;
252  const Eigen::Array3i low_resolution_cell_at_start(
253  cell_at_start[0] >> reduction_exponent,
254  cell_at_start[1] >> reduction_exponent,
255  cell_at_start[2] >> reduction_exponent);
256  cell_indices_per_depth.back().push_back(
257  low_resolution_cell_at_start - low_resolution_search_window_start);
258  }
259  }
260  return DiscreteScan3D{pose, cell_indices_per_depth, rotational_score};
261 }
262 
264  const FastCorrelativeScanMatcher3D::SearchParameters& search_parameters,
265  const sensor::PointCloud& point_cloud,
266  const Eigen::VectorXf& rotational_scan_matcher_histogram,
267  const Eigen::Quaterniond& gravity_alignment,
268  const transform::Rigid3f& global_node_pose,
269  const transform::Rigid3f& global_submap_pose) const {
270  std::vector<DiscreteScan3D> result;
271  // We set this value to something on the order of resolution to make sure that
272  // the std::acos() below is defined.
273  float max_scan_range = 3.f * resolution_;
274  for (const Eigen::Vector3f& point : point_cloud) {
275  const float range = point.norm();
276  max_scan_range = std::max(range, max_scan_range);
277  }
278  const float kSafetyMargin = 1.f - 1e-2f;
279  const float angular_step_size =
280  kSafetyMargin * std::acos(1.f - common::Pow2(resolution_) /
281  (2.f * common::Pow2(max_scan_range)));
282  const int angular_window_size = common::RoundToInt(
283  search_parameters.angular_search_window / angular_step_size);
284  std::vector<float> angles;
285  for (int rz = -angular_window_size; rz <= angular_window_size; ++rz) {
286  angles.push_back(rz * angular_step_size);
287  }
288  const transform::Rigid3f node_to_submap =
289  global_submap_pose.inverse() * global_node_pose;
290  const std::vector<float> scores = rotational_scan_matcher_.Match(
291  rotational_scan_matcher_histogram,
292  transform::GetYaw(node_to_submap.rotation() *
293  gravity_alignment.inverse().cast<float>()),
294  angles);
295  for (size_t i = 0; i != angles.size(); ++i) {
296  if (scores[i] < options_.min_rotational_score()) {
297  continue;
298  }
299  const Eigen::Vector3f angle_axis(0.f, 0.f, angles[i]);
300  // It's important to apply the 'angle_axis' rotation between the translation
301  // and rotation of the 'initial_pose', so that the rotation is around the
302  // origin of the range data, and yaw is in map frame.
303  const transform::Rigid3f pose(
304  node_to_submap.translation(),
305  global_submap_pose.rotation().inverse() *
307  global_node_pose.rotation());
308  result.push_back(
309  DiscretizeScan(search_parameters, point_cloud, pose, scores[i]));
310  }
311  return result;
312 }
313 
314 std::vector<Candidate3D>
316  const FastCorrelativeScanMatcher3D::SearchParameters& search_parameters,
317  const int num_discrete_scans) const {
318  const int linear_step_size = 1 << precomputation_grid_stack_->max_depth();
319  const int num_lowest_resolution_linear_xy_candidates =
320  (2 * search_parameters.linear_xy_window_size + linear_step_size) /
321  linear_step_size;
322  const int num_lowest_resolution_linear_z_candidates =
323  (2 * search_parameters.linear_z_window_size + linear_step_size) /
324  linear_step_size;
325  const int num_candidates =
326  num_discrete_scans *
327  common::Power(num_lowest_resolution_linear_xy_candidates, 2) *
328  num_lowest_resolution_linear_z_candidates;
329  std::vector<Candidate3D> candidates;
330  candidates.reserve(num_candidates);
331  for (int scan_index = 0; scan_index != num_discrete_scans; ++scan_index) {
332  for (int z = -search_parameters.linear_z_window_size;
333  z <= search_parameters.linear_z_window_size; z += linear_step_size) {
334  for (int y = -search_parameters.linear_xy_window_size;
335  y <= search_parameters.linear_xy_window_size;
336  y += linear_step_size) {
337  for (int x = -search_parameters.linear_xy_window_size;
338  x <= search_parameters.linear_xy_window_size;
339  x += linear_step_size) {
340  candidates.emplace_back(scan_index, Eigen::Array3i(x, y, z));
341  }
342  }
343  }
344  }
345  CHECK_EQ(candidates.size(), num_candidates);
346  return candidates;
347 }
348 
350  const int depth, const std::vector<DiscreteScan3D>& discrete_scans,
351  std::vector<Candidate3D>* const candidates) const {
352  const int reduction_exponent =
353  std::max(0, depth - options_.full_resolution_depth() + 1);
354  for (Candidate3D& candidate : *candidates) {
355  int sum = 0;
356  const DiscreteScan3D& discrete_scan = discrete_scans[candidate.scan_index];
357  const Eigen::Array3i offset(candidate.offset[0] >> reduction_exponent,
358  candidate.offset[1] >> reduction_exponent,
359  candidate.offset[2] >> reduction_exponent);
360  CHECK_LT(depth, discrete_scan.cell_indices_per_depth.size());
361  for (const Eigen::Array3i& cell_index :
362  discrete_scan.cell_indices_per_depth[depth]) {
363  const Eigen::Array3i proposed_cell_index = cell_index + offset;
364  sum += precomputation_grid_stack_->Get(depth).value(proposed_cell_index);
365  }
366  candidate.score = PrecomputationGrid3D::ToProbability(
367  sum /
368  static_cast<float>(discrete_scan.cell_indices_per_depth[depth].size()));
369  }
370  std::sort(candidates->begin(), candidates->end(),
371  std::greater<Candidate3D>());
372 }
373 
374 std::vector<Candidate3D>
376  const FastCorrelativeScanMatcher3D::SearchParameters& search_parameters,
377  const std::vector<DiscreteScan3D>& discrete_scans) const {
378  std::vector<Candidate3D> lowest_resolution_candidates =
379  GenerateLowestResolutionCandidates(search_parameters,
380  discrete_scans.size());
381  ScoreCandidates(precomputation_grid_stack_->max_depth(), discrete_scans,
382  &lowest_resolution_candidates);
383  return lowest_resolution_candidates;
384 }
385 
387  const std::vector<DiscreteScan3D>& discrete_scans,
388  const Candidate3D& candidate) const {
390  resolution_ * candidate.offset.matrix().cast<float>()) *
391  discrete_scans[candidate.scan_index].pose;
392 }
393 
395  const FastCorrelativeScanMatcher3D::SearchParameters& search_parameters,
396  const std::vector<DiscreteScan3D>& discrete_scans,
397  const std::vector<Candidate3D>& candidates, const int candidate_depth,
398  float min_score) const {
399  if (candidate_depth == 0) {
400  for (const Candidate3D& candidate : candidates) {
401  if (candidate.score <= min_score) {
402  // Return if the candidate is bad because the following candidate will
403  // not have better score.
404  return Candidate3D::Unsuccessful();
405  }
406  const float low_resolution_score =
407  (*search_parameters.low_resolution_matcher)(
408  GetPoseFromCandidate(discrete_scans, candidate));
409  if (low_resolution_score >= options_.min_low_resolution_score()) {
410  // We found the best candidate that passes the matching function.
411  Candidate3D best_candidate = candidate;
412  best_candidate.low_resolution_score = low_resolution_score;
413  return best_candidate;
414  }
415  }
416 
417  // All candidates have good scores but none passes the matching function.
418  return Candidate3D::Unsuccessful();
419  }
420 
421  Candidate3D best_high_resolution_candidate = Candidate3D::Unsuccessful();
422  best_high_resolution_candidate.score = min_score;
423  for (const Candidate3D& candidate : candidates) {
424  if (candidate.score <= min_score) {
425  break;
426  }
427  std::vector<Candidate3D> higher_resolution_candidates;
428  const int half_width = 1 << (candidate_depth - 1);
429  for (int z : {0, half_width}) {
430  if (candidate.offset.z() + z > search_parameters.linear_z_window_size) {
431  break;
432  }
433  for (int y : {0, half_width}) {
434  if (candidate.offset.y() + y >
435  search_parameters.linear_xy_window_size) {
436  break;
437  }
438  for (int x : {0, half_width}) {
439  if (candidate.offset.x() + x >
440  search_parameters.linear_xy_window_size) {
441  break;
442  }
443  higher_resolution_candidates.emplace_back(
444  candidate.scan_index, candidate.offset + Eigen::Array3i(x, y, z));
445  }
446  }
447  }
448  ScoreCandidates(candidate_depth - 1, discrete_scans,
449  &higher_resolution_candidates);
450  best_high_resolution_candidate = std::max(
451  best_high_resolution_candidate,
452  BranchAndBound(search_parameters, discrete_scans,
453  higher_resolution_candidates, candidate_depth - 1,
454  best_high_resolution_candidate.score));
455  }
456  return best_high_resolution_candidate;
457 }
458 
459 } // namespace scan_matching
460 } // namespace mapping
461 } // namespace cartographer
PointCloud TransformPointCloud(const PointCloud &point_cloud, const transform::Rigid3f &transform)
Definition: point_cloud.cc:25
Eigen::Quaternion< T > AngleAxisVectorToRotationQuaternion(const Eigen::Matrix< T, 3, 1 > &angle_axis)
Definition: transform.h:86
static Rigid3 Rotation(const AngleAxis &angle_axis)
PrecomputationGrid3D ConvertToPrecomputationGrid(const HybridGrid &hybrid_grid)
_Unique_if< T >::_Single_object make_unique(Args &&... args)
Definition: make_unique.h:46
constexpr T Pow2(T a)
Definition: math.h:50
std::function< float(const transform::Rigid3f &)> CreateLowResolutionMatcher(const HybridGrid *low_resolution_grid, const sensor::PointCloud *points)
int RoundToInt(const float x)
Definition: port.h:41
transform::Rigid3f GetPoseFromCandidate(const std::vector< DiscreteScan3D > &discrete_scans, const Candidate3D &candidate) const
std::unique_ptr< Result > MatchWithSearchParameters(const SearchParameters &search_parameters, const transform::Rigid3f &global_node_pose, const transform::Rigid3f &global_submap_pose, const sensor::PointCloud &point_cloud, const Eigen::VectorXf &rotational_scan_matcher_histogram, const Eigen::Quaterniond &gravity_alignment, float min_score) const
Rigid3< OtherType > cast() const
constexpr T Power(T base, int exponent)
Definition: math.h:44
FastCorrelativeScanMatcher3D(const HybridGrid &hybrid_grid, const HybridGrid *low_resolution_hybrid_grid, const std::vector< TrajectoryNode > &nodes, const proto::FastCorrelativeScanMatcherOptions3D &options)
T GetYaw(const Eigen::Quaternion< T > &rotation)
Definition: transform.h:43
std::unique_ptr< Result > Match(const transform::Rigid3d &global_node_pose, const transform::Rigid3d &global_submap_pose, const TrajectoryNode::Data &constant_data, float min_score) const
const Quaternion & rotation() const
std::unique_ptr< Result > MatchFullSubmap(const Eigen::Quaterniond &global_node_rotation, const Eigen::Quaterniond &global_submap_rotation, const TrajectoryNode::Data &constant_data, float min_score) const
Candidate3D BranchAndBound(const SearchParameters &search_parameters, const std::vector< DiscreteScan3D > &discrete_scans, const std::vector< Candidate3D > &candidates, int candidate_depth, float min_score) const
proto::ProbabilityGridRangeDataInserterOptions2D options_
std::vector< float > Match(const Eigen::VectorXf &histogram, float initial_angle, const std::vector< float > &angles) const
void ScoreCandidates(int depth, const std::vector< DiscreteScan3D > &discrete_scans, std::vector< Candidate3D > *const candidates) const
const Vector & translation() const
std::vector< Candidate3D > ComputeLowestResolutionCandidates(const SearchParameters &search_parameters, const std::vector< DiscreteScan3D > &discrete_scans) const
std::vector< Eigen::Vector3f > PointCloud
Definition: point_cloud.h:32
std::vector< Candidate3D > GenerateLowestResolutionCandidates(const SearchParameters &search_parameters, int num_discrete_scans) const
Eigen::Array3i GetCellIndex(const Eigen::Vector3f &point) const
Definition: hybrid_grid.h:428
std::vector< DiscreteScan3D > GenerateDiscreteScans(const SearchParameters &search_parameters, const sensor::PointCloud &point_cloud, const Eigen::VectorXf &rotational_scan_matcher_histogram, const Eigen::Quaterniond &gravity_alignment, const transform::Rigid3f &global_node_pose, const transform::Rigid3f &global_submap_pose) const
PrecomputationGrid3D PrecomputeGrid(const PrecomputationGrid3D &grid, const bool half_resolution, const Eigen::Array3i &shift)
proto::FastCorrelativeScanMatcherOptions3D CreateFastCorrelativeScanMatcherOptions3D(common::LuaParameterDictionary *const parameter_dictionary)
Candidate3D(const int scan_index, const Eigen::Array3i &offset)
transform::Rigid3d pose
std::vector< std::vector< Eigen::Array3i > > cell_indices_per_depth
static Rigid3 Translation(const Vector &vector)
DiscreteScan3D DiscretizeScan(const SearchParameters &search_parameters, const sensor::PointCloud &point_cloud, const transform::Rigid3f &pose, float rotational_score) const
PrecomputationGridStack3D(const HybridGrid &hybrid_grid, const proto::FastCorrelativeScanMatcherOptions3D &options)


cartographer
Author(s): The Cartographer Authors
autogenerated on Mon Feb 28 2022 22:00:58