fast_correlative_scan_matcher_2d.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 <deque>
22 #include <functional>
23 #include <limits>
24 
25 #include "Eigen/Geometry"
31 #include "glog/logging.h"
32 
33 namespace cartographer {
34 namespace mapping {
35 namespace scan_matching {
36 namespace {
37 
38 // A collection of values which can be added and later removed, and the maximum
39 // of the current values in the collection can be retrieved.
40 // All of it in (amortized) O(1).
41 class SlidingWindowMaximum {
42  public:
43  void AddValue(const float value) {
44  while (!non_ascending_maxima_.empty() &&
45  value > non_ascending_maxima_.back()) {
46  non_ascending_maxima_.pop_back();
47  }
48  non_ascending_maxima_.push_back(value);
49  }
50 
51  void RemoveValue(const float value) {
52  // DCHECK for performance, since this is done for every value in the
53  // precomputation grid.
54  DCHECK(!non_ascending_maxima_.empty());
55  DCHECK_LE(value, non_ascending_maxima_.front());
56  if (value == non_ascending_maxima_.front()) {
57  non_ascending_maxima_.pop_front();
58  }
59  }
60 
61  float GetMaximum() const {
62  // DCHECK for performance, since this is done for every value in the
63  // precomputation grid.
64  DCHECK_GT(non_ascending_maxima_.size(), 0);
65  return non_ascending_maxima_.front();
66  }
67 
68  void CheckIsEmpty() const { CHECK_EQ(non_ascending_maxima_.size(), 0); }
69 
70  private:
71  // Maximum of the current sliding window at the front. Then the maximum of the
72  // remaining window that came after this values first occurrence, and so on.
73  std::deque<float> non_ascending_maxima_;
74 };
75 
76 } // namespace
77 
78 proto::FastCorrelativeScanMatcherOptions2D
80  common::LuaParameterDictionary* const parameter_dictionary) {
81  proto::FastCorrelativeScanMatcherOptions2D options;
82  options.set_linear_search_window(
83  parameter_dictionary->GetDouble("linear_search_window"));
84  options.set_angular_search_window(
85  parameter_dictionary->GetDouble("angular_search_window"));
86  options.set_branch_and_bound_depth(
87  parameter_dictionary->GetInt("branch_and_bound_depth"));
88  return options;
89 }
90 
92  const Grid2D& grid, const CellLimits& limits, const int width,
93  std::vector<float>* reusable_intermediate_grid)
94  : offset_(-width + 1, -width + 1),
95  wide_limits_(limits.num_x_cells + width - 1,
96  limits.num_y_cells + width - 1),
97  min_score_(1.f - grid.GetMaxCorrespondenceCost()),
98  max_score_(1.f - grid.GetMinCorrespondenceCost()),
99  cells_(wide_limits_.num_x_cells * wide_limits_.num_y_cells) {
100  CHECK_GE(width, 1);
101  CHECK_GE(limits.num_x_cells, 1);
102  CHECK_GE(limits.num_y_cells, 1);
103  const int stride = wide_limits_.num_x_cells;
104  // First we compute the maximum probability for each (x0, y) achieved in the
105  // span defined by x0 <= x < x0 + width.
106  std::vector<float>& intermediate = *reusable_intermediate_grid;
107  intermediate.resize(wide_limits_.num_x_cells * limits.num_y_cells);
108  for (int y = 0; y != limits.num_y_cells; ++y) {
109  SlidingWindowMaximum current_values;
110  current_values.AddValue(
111  1.f - std::abs(grid.GetCorrespondenceCost(Eigen::Array2i(0, y))));
112  for (int x = -width + 1; x != 0; ++x) {
113  intermediate[x + width - 1 + y * stride] = current_values.GetMaximum();
114  if (x + width < limits.num_x_cells) {
115  current_values.AddValue(1.f - std::abs(grid.GetCorrespondenceCost(
116  Eigen::Array2i(x + width, y))));
117  }
118  }
119  for (int x = 0; x < limits.num_x_cells - width; ++x) {
120  intermediate[x + width - 1 + y * stride] = current_values.GetMaximum();
121  current_values.RemoveValue(
122  1.f - std::abs(grid.GetCorrespondenceCost(Eigen::Array2i(x, y))));
123  current_values.AddValue(1.f - std::abs(grid.GetCorrespondenceCost(
124  Eigen::Array2i(x + width, y))));
125  }
126  for (int x = std::max(limits.num_x_cells - width, 0);
127  x != limits.num_x_cells; ++x) {
128  intermediate[x + width - 1 + y * stride] = current_values.GetMaximum();
129  current_values.RemoveValue(
130  1.f - std::abs(grid.GetCorrespondenceCost(Eigen::Array2i(x, y))));
131  }
132  current_values.CheckIsEmpty();
133  }
134  // For each (x, y), we compute the maximum probability in the width x width
135  // region starting at each (x, y) and precompute the resulting bound on the
136  // score.
137  for (int x = 0; x != wide_limits_.num_x_cells; ++x) {
138  SlidingWindowMaximum current_values;
139  current_values.AddValue(intermediate[x]);
140  for (int y = -width + 1; y != 0; ++y) {
141  cells_[x + (y + width - 1) * stride] =
142  ComputeCellValue(current_values.GetMaximum());
143  if (y + width < limits.num_y_cells) {
144  current_values.AddValue(intermediate[x + (y + width) * stride]);
145  }
146  }
147  for (int y = 0; y < limits.num_y_cells - width; ++y) {
148  cells_[x + (y + width - 1) * stride] =
149  ComputeCellValue(current_values.GetMaximum());
150  current_values.RemoveValue(intermediate[x + y * stride]);
151  current_values.AddValue(intermediate[x + (y + width) * stride]);
152  }
153  for (int y = std::max(limits.num_y_cells - width, 0);
154  y != limits.num_y_cells; ++y) {
155  cells_[x + (y + width - 1) * stride] =
156  ComputeCellValue(current_values.GetMaximum());
157  current_values.RemoveValue(intermediate[x + y * stride]);
158  }
159  current_values.CheckIsEmpty();
160  }
161 }
162 
163 uint8 PrecomputationGrid2D::ComputeCellValue(const float probability) const {
164  const int cell_value = common::RoundToInt(
165  (probability - min_score_) * (255.f / (max_score_ - min_score_)));
166  CHECK_GE(cell_value, 0);
167  CHECK_LE(cell_value, 255);
168  return cell_value;
169 }
170 
172  const Grid2D& grid,
173  const proto::FastCorrelativeScanMatcherOptions2D& options) {
174  CHECK_GE(options.branch_and_bound_depth(), 1);
175  const int max_width = 1 << (options.branch_and_bound_depth() - 1);
176  precomputation_grids_.reserve(options.branch_and_bound_depth());
177  std::vector<float> reusable_intermediate_grid;
178  const CellLimits limits = grid.limits().cell_limits();
179  reusable_intermediate_grid.reserve((limits.num_x_cells + max_width - 1) *
180  limits.num_y_cells);
181  for (int i = 0; i != options.branch_and_bound_depth(); ++i) {
182  const int width = 1 << i;
183  precomputation_grids_.emplace_back(grid, limits, width,
184  &reusable_intermediate_grid);
185  }
186 }
187 
189  const Grid2D& grid,
190  const proto::FastCorrelativeScanMatcherOptions2D& options)
191  : options_(options),
192  limits_(grid.limits()),
193  precomputation_grid_stack_(
194  common::make_unique<PrecomputationGridStack2D>(grid, options)) {}
195 
197 
199  const transform::Rigid2d& initial_pose_estimate,
200  const sensor::PointCloud& point_cloud, const float min_score, float* score,
201  transform::Rigid2d* pose_estimate) const {
202  const SearchParameters search_parameters(options_.linear_search_window(),
203  options_.angular_search_window(),
204  point_cloud, limits_.resolution());
205  return MatchWithSearchParameters(search_parameters, initial_pose_estimate,
206  point_cloud, min_score, score,
207  pose_estimate);
208 }
209 
211  const sensor::PointCloud& point_cloud, float min_score, float* score,
212  transform::Rigid2d* pose_estimate) const {
213  // Compute a search window around the center of the submap that includes it
214  // fully.
215  const SearchParameters search_parameters(
216  1e6 * limits_.resolution(), // Linear search window, 1e6 cells/direction.
217  M_PI, // Angular search window, 180 degrees in both directions.
218  point_cloud, limits_.resolution());
220  limits_.max() - 0.5 * limits_.resolution() *
221  Eigen::Vector2d(limits_.cell_limits().num_y_cells,
223  return MatchWithSearchParameters(search_parameters, center, point_cloud,
224  min_score, score, pose_estimate);
225 }
226 
228  SearchParameters search_parameters,
229  const transform::Rigid2d& initial_pose_estimate,
230  const sensor::PointCloud& point_cloud, float min_score, float* score,
231  transform::Rigid2d* pose_estimate) const {
232  CHECK_NOTNULL(score);
233  CHECK_NOTNULL(pose_estimate);
234 
235  const Eigen::Rotation2Dd initial_rotation = initial_pose_estimate.rotation();
236  const sensor::PointCloud rotated_point_cloud = sensor::TransformPointCloud(
237  point_cloud,
238  transform::Rigid3f::Rotation(Eigen::AngleAxisf(
239  initial_rotation.cast<float>().angle(), Eigen::Vector3f::UnitZ())));
240  const std::vector<sensor::PointCloud> rotated_scans =
241  GenerateRotatedScans(rotated_point_cloud, search_parameters);
242  const std::vector<DiscreteScan2D> discrete_scans = DiscretizeScans(
243  limits_, rotated_scans,
244  Eigen::Translation2f(initial_pose_estimate.translation().x(),
245  initial_pose_estimate.translation().y()));
246  search_parameters.ShrinkToFit(discrete_scans, limits_.cell_limits());
247 
248  const std::vector<Candidate2D> lowest_resolution_candidates =
249  ComputeLowestResolutionCandidates(discrete_scans, search_parameters);
250  const Candidate2D best_candidate = BranchAndBound(
251  discrete_scans, search_parameters, lowest_resolution_candidates,
252  precomputation_grid_stack_->max_depth(), min_score);
253  if (best_candidate.score > min_score) {
254  *score = best_candidate.score;
255  *pose_estimate = transform::Rigid2d(
256  {initial_pose_estimate.translation().x() + best_candidate.x,
257  initial_pose_estimate.translation().y() + best_candidate.y},
258  initial_rotation * Eigen::Rotation2Dd(best_candidate.orientation));
259  return true;
260  }
261  return false;
262 }
263 
264 std::vector<Candidate2D>
266  const std::vector<DiscreteScan2D>& discrete_scans,
267  const SearchParameters& search_parameters) const {
268  std::vector<Candidate2D> lowest_resolution_candidates =
269  GenerateLowestResolutionCandidates(search_parameters);
272  discrete_scans, search_parameters, &lowest_resolution_candidates);
273  return lowest_resolution_candidates;
274 }
275 
276 std::vector<Candidate2D>
278  const SearchParameters& search_parameters) const {
279  const int linear_step_size = 1 << precomputation_grid_stack_->max_depth();
280  int num_candidates = 0;
281  for (int scan_index = 0; scan_index != search_parameters.num_scans;
282  ++scan_index) {
283  const int num_lowest_resolution_linear_x_candidates =
284  (search_parameters.linear_bounds[scan_index].max_x -
285  search_parameters.linear_bounds[scan_index].min_x + linear_step_size) /
286  linear_step_size;
287  const int num_lowest_resolution_linear_y_candidates =
288  (search_parameters.linear_bounds[scan_index].max_y -
289  search_parameters.linear_bounds[scan_index].min_y + linear_step_size) /
290  linear_step_size;
291  num_candidates += num_lowest_resolution_linear_x_candidates *
292  num_lowest_resolution_linear_y_candidates;
293  }
294  std::vector<Candidate2D> candidates;
295  candidates.reserve(num_candidates);
296  for (int scan_index = 0; scan_index != search_parameters.num_scans;
297  ++scan_index) {
298  for (int x_index_offset = search_parameters.linear_bounds[scan_index].min_x;
299  x_index_offset <= search_parameters.linear_bounds[scan_index].max_x;
300  x_index_offset += linear_step_size) {
301  for (int y_index_offset =
302  search_parameters.linear_bounds[scan_index].min_y;
303  y_index_offset <= search_parameters.linear_bounds[scan_index].max_y;
304  y_index_offset += linear_step_size) {
305  candidates.emplace_back(scan_index, x_index_offset, y_index_offset,
306  search_parameters);
307  }
308  }
309  }
310  CHECK_EQ(candidates.size(), num_candidates);
311  return candidates;
312 }
313 
315  const PrecomputationGrid2D& precomputation_grid,
316  const std::vector<DiscreteScan2D>& discrete_scans,
317  const SearchParameters& search_parameters,
318  std::vector<Candidate2D>* const candidates) const {
319  for (Candidate2D& candidate : *candidates) {
320  int sum = 0;
321  for (const Eigen::Array2i& xy_index :
322  discrete_scans[candidate.scan_index]) {
323  const Eigen::Array2i proposed_xy_index(
324  xy_index.x() + candidate.x_index_offset,
325  xy_index.y() + candidate.y_index_offset);
326  sum += precomputation_grid.GetValue(proposed_xy_index);
327  }
328  candidate.score = precomputation_grid.ToScore(
329  sum / static_cast<float>(discrete_scans[candidate.scan_index].size()));
330  }
331  std::sort(candidates->begin(), candidates->end(),
332  std::greater<Candidate2D>());
333 }
334 
336  const std::vector<DiscreteScan2D>& discrete_scans,
337  const SearchParameters& search_parameters,
338  const std::vector<Candidate2D>& candidates, const int candidate_depth,
339  float min_score) const {
340  if (candidate_depth == 0) {
341  // Return the best candidate.
342  return *candidates.begin();
343  }
344 
345  Candidate2D best_high_resolution_candidate(0, 0, 0, search_parameters);
346  best_high_resolution_candidate.score = min_score;
347  for (const Candidate2D& candidate : candidates) {
348  if (candidate.score <= min_score) {
349  break;
350  }
351  std::vector<Candidate2D> higher_resolution_candidates;
352  const int half_width = 1 << (candidate_depth - 1);
353  for (int x_offset : {0, half_width}) {
354  if (candidate.x_index_offset + x_offset >
355  search_parameters.linear_bounds[candidate.scan_index].max_x) {
356  break;
357  }
358  for (int y_offset : {0, half_width}) {
359  if (candidate.y_index_offset + y_offset >
360  search_parameters.linear_bounds[candidate.scan_index].max_y) {
361  break;
362  }
363  higher_resolution_candidates.emplace_back(
364  candidate.scan_index, candidate.x_index_offset + x_offset,
365  candidate.y_index_offset + y_offset, search_parameters);
366  }
367  }
368  ScoreCandidates(precomputation_grid_stack_->Get(candidate_depth - 1),
369  discrete_scans, search_parameters,
370  &higher_resolution_candidates);
371  best_high_resolution_candidate = std::max(
372  best_high_resolution_candidate,
373  BranchAndBound(discrete_scans, search_parameters,
374  higher_resolution_candidates, candidate_depth - 1,
375  best_high_resolution_candidate.score));
376  }
377  return best_high_resolution_candidate;
378 }
379 
380 } // namespace scan_matching
381 } // namespace mapping
382 } // namespace cartographer
std::vector< DiscreteScan2D > DiscretizeScans(const MapLimits &map_limits, const std::vector< sensor::PointCloud > &scans, const Eigen::Translation2f &initial_translation)
PointCloud TransformPointCloud(const PointCloud &point_cloud, const transform::Rigid3f &transform)
Definition: point_cloud.cc:25
const Eigen::Vector2d & max() const
Definition: map_limits.h:61
static Rigid3 Rotation(const AngleAxis &angle_axis)
bool MatchFullSubmap(const sensor::PointCloud &point_cloud, float min_score, float *score, transform::Rigid2d *pose_estimate) const
proto::FastCorrelativeScanMatcherOptions2D CreateFastCorrelativeScanMatcherOptions2D(common::LuaParameterDictionary *const parameter_dictionary)
_Unique_if< T >::_Single_object make_unique(Args &&... args)
Definition: make_unique.h:46
std::deque< float > non_ascending_maxima_
void ShrinkToFit(const std::vector< DiscreteScan2D > &scans, const CellLimits &cell_limits)
int RoundToInt(const float x)
Definition: port.h:41
std::map< CellId, StoredType > cells_
void ScoreCandidates(const PrecomputationGrid2D &precomputation_grid, const std::vector< DiscreteScan2D > &discrete_scans, const SearchParameters &search_parameters, std::vector< Candidate2D > *const candidates) const
Rigid2< double > Rigid2d
bool MatchWithSearchParameters(SearchParameters search_parameters, const transform::Rigid2d &initial_pose_estimate, const sensor::PointCloud &point_cloud, float min_score, float *score, transform::Rigid2d *pose_estimate) const
bool Match(const transform::Rigid2d &initial_pose_estimate, const sensor::PointCloud &point_cloud, float min_score, float *score, transform::Rigid2d *pose_estimate) const
std::vector< sensor::PointCloud > GenerateRotatedScans(const sensor::PointCloud &point_cloud, const SearchParameters &search_parameters)
FastCorrelativeScanMatcher2D(const Grid2D &grid, const proto::FastCorrelativeScanMatcherOptions2D &options)
proto::ProbabilityGridRangeDataInserterOptions2D options_
std::vector< Candidate2D > GenerateLowestResolutionCandidates(const SearchParameters &search_parameters) const
std::vector< Eigen::Vector3f > PointCloud
Definition: point_cloud.h:32
PrecomputationGridStack2D(const Grid2D &grid, const proto::FastCorrelativeScanMatcherOptions2D &options)
PrecomputationGrid2D(const Grid2D &grid, const CellLimits &limits, int width, std::vector< float > *reusable_intermediate_grid)
float GetCorrespondenceCost(const Eigen::Array2i &cell_index) const
Definition: grid_2d.cc:86
const Vector & translation() const
Eigen::Vector2d offset_
Candidate2D BranchAndBound(const std::vector< DiscreteScan2D > &discrete_scans, const SearchParameters &search_parameters, const std::vector< Candidate2D > &candidates, int candidate_depth, float min_score) const
const MapLimits & limits() const
Definition: grid_2d.h:41
uint8_t uint8
Definition: port.h:34
const CellLimits & cell_limits() const
Definition: map_limits.h:64
static Rigid2 Translation(const Vector &vector)
std::vector< Candidate2D > ComputeLowestResolutionCandidates(const std::vector< DiscreteScan2D > &discrete_scans, const SearchParameters &search_parameters) const


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