3d/submaps.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 <cmath>
20 #include <limits>
21 
24 #include "glog/logging.h"
25 
26 namespace cartographer {
27 namespace mapping_3d {
28 
29 namespace {
30 
31 constexpr float kSliceHalfHeight = 0.1f;
32 
33 struct RaySegment {
34  Eigen::Vector2f from;
35  Eigen::Vector2f to;
36  bool hit; // Whether there is a hit at 'to'.
37 };
38 
39 struct PixelData {
40  int min_z = INT_MAX;
41  int max_z = INT_MIN;
42  int count = 0;
43  float probability_sum = 0.f;
44  float max_probability = 0.5f;
45 };
46 
47 // We compute a slice around the xy-plane. 'transform' is applied to the rays in
48 // global map frame to allow choosing an arbitrary slice.
49 void GenerateSegmentForSlice(const sensor::RangeData& range_data,
50  const transform::Rigid3f& pose,
51  const transform::Rigid3f& transform,
52  std::vector<RaySegment>* segments) {
53  const sensor::RangeData transformed_range_data =
54  sensor::TransformRangeData(range_data, transform * pose);
55  segments->reserve(transformed_range_data.returns.size());
56  for (const Eigen::Vector3f& hit : transformed_range_data.returns) {
57  const Eigen::Vector2f origin_xy = transformed_range_data.origin.head<2>();
58  const float origin_z = transformed_range_data.origin.z();
59  const float delta_z = hit.z() - origin_z;
60  const Eigen::Vector2f delta_xy = hit.head<2>() - origin_xy;
61  if (origin_z < -kSliceHalfHeight) {
62  // Ray originates below the slice.
63  if (hit.z() > kSliceHalfHeight) {
64  // Ray is cutting through the slice.
65  segments->push_back(RaySegment{
66  origin_xy + (-kSliceHalfHeight - origin_z) / delta_z * delta_xy,
67  origin_xy + (kSliceHalfHeight - origin_z) / delta_z * delta_xy,
68  false});
69  } else if (hit.z() > -kSliceHalfHeight) {
70  // Hit is inside the slice.
71  segments->push_back(RaySegment{
72  origin_xy + (-kSliceHalfHeight - origin_z) / delta_z * delta_xy,
73  hit.head<2>(), true});
74  }
75  } else if (origin_z < kSliceHalfHeight) {
76  // Ray originates inside the slice.
77  if (hit.z() < -kSliceHalfHeight) {
78  // Hit is below.
79  segments->push_back(RaySegment{
80  origin_xy,
81  origin_xy + (-kSliceHalfHeight - origin_z) / delta_z * delta_xy,
82  false});
83  } else if (hit.z() < kSliceHalfHeight) {
84  // Full ray is inside the slice.
85  segments->push_back(RaySegment{origin_xy, hit.head<2>(), true});
86  } else {
87  // Hit is above.
88  segments->push_back(RaySegment{
89  origin_xy,
90  origin_xy + (kSliceHalfHeight - origin_z) / delta_z * delta_xy,
91  false});
92  }
93  } else {
94  // Ray originates above the slice.
95  if (hit.z() < -kSliceHalfHeight) {
96  // Ray is cutting through the slice.
97  segments->push_back(RaySegment{
98  origin_xy + (kSliceHalfHeight - origin_z) / delta_z * delta_xy,
99  origin_xy + (-kSliceHalfHeight - origin_z) / delta_z * delta_xy,
100  false});
101  } else if (hit.z() < kSliceHalfHeight) {
102  // Hit is inside the slice.
103  segments->push_back(RaySegment{
104  origin_xy + (kSliceHalfHeight - origin_z) / delta_z * delta_xy,
105  hit.head<2>(), true});
106  }
107  }
108  }
109 }
110 
111 void UpdateFreeSpaceFromSegment(const RaySegment& segment,
112  const std::vector<uint16>& miss_table,
113  mapping_2d::ProbabilityGrid* result) {
114  Eigen::Array2i from = result->limits().GetXYIndexOfCellContainingPoint(
115  segment.from.x(), segment.from.y());
116  Eigen::Array2i to = result->limits().GetXYIndexOfCellContainingPoint(
117  segment.to.x(), segment.to.y());
118  bool large_delta_y =
119  std::abs(to.y() - from.y()) > std::abs(to.x() - from.x());
120  if (large_delta_y) {
121  std::swap(from.x(), from.y());
122  std::swap(to.x(), to.y());
123  }
124  if (from.x() > to.x()) {
125  std::swap(from, to);
126  }
127  const int dx = to.x() - from.x();
128  const int dy = std::abs(to.y() - from.y());
129  int error = dx / 2;
130  const int direction = (from.y() < to.y()) ? 1 : -1;
131 
132  for (; from.x() < to.x(); ++from.x()) {
133  if (large_delta_y) {
134  result->ApplyLookupTable(Eigen::Array2i(from.y(), from.x()), miss_table);
135  } else {
136  result->ApplyLookupTable(from, miss_table);
137  }
138  error -= dy;
139  if (error < 0) {
140  from.y() += direction;
141  error += dx;
142  }
143  }
144 }
145 
146 void InsertSegmentsIntoProbabilityGrid(const std::vector<RaySegment>& segments,
147  const std::vector<uint16>& hit_table,
148  const std::vector<uint16>& miss_table,
149  mapping_2d::ProbabilityGrid* result) {
150  result->StartUpdate();
151  if (segments.empty()) {
152  return;
153  }
154  Eigen::Vector2f min = segments.front().from;
155  Eigen::Vector2f max = min;
156  for (const RaySegment& segment : segments) {
157  min = min.cwiseMin(segment.from);
158  min = min.cwiseMin(segment.to);
159  max = max.cwiseMax(segment.from);
160  max = max.cwiseMax(segment.to);
161  }
162  const float padding = 10. * result->limits().resolution();
163  max += Eigen::Vector2f(padding, padding);
164  min -= Eigen::Vector2f(padding, padding);
165  result->GrowLimits(min.x(), min.y());
166  result->GrowLimits(max.x(), max.y());
167 
168  for (const RaySegment& segment : segments) {
169  if (segment.hit) {
170  result->ApplyLookupTable(result->limits().GetXYIndexOfCellContainingPoint(
171  segment.to.x(), segment.to.y()),
172  hit_table);
173  }
174  }
175  for (const RaySegment& segment : segments) {
176  UpdateFreeSpaceFromSegment(segment, miss_table, result);
177  }
178 }
179 
180 // Filters 'range_data', retaining only the returns that have no more than
181 // 'max_range' distance from the origin. Removes misses and reflectivity
182 // information.
183 sensor::RangeData FilterRangeDataByMaxRange(const sensor::RangeData& range_data,
184  const float max_range) {
185  sensor::RangeData result{range_data.origin, {}, {}};
186  for (const Eigen::Vector3f& hit : range_data.returns) {
187  if ((hit - range_data.origin).norm() <= max_range) {
188  result.returns.push_back(hit);
189  }
190  }
191  return result;
192 }
193 
194 std::vector<PixelData> AccumulatePixelData(
195  const int width, const int height, const Eigen::Array2i& min_index,
196  const Eigen::Array2i& max_index,
197  const std::vector<Eigen::Array4i>& voxel_indices_and_probabilities) {
198  std::vector<PixelData> accumulated_pixel_data(width * height);
199  for (const Eigen::Array4i& voxel_index_and_probability :
200  voxel_indices_and_probabilities) {
201  const Eigen::Array2i pixel_index = voxel_index_and_probability.head<2>();
202  if ((pixel_index < min_index).any() || (pixel_index > max_index).any()) {
203  // Out of bounds. This could happen because of floating point inaccuracy.
204  continue;
205  }
206  const int x = max_index.x() - pixel_index[0];
207  const int y = max_index.y() - pixel_index[1];
208  PixelData& pixel = accumulated_pixel_data[x * width + y];
209  ++pixel.count;
210  pixel.min_z = std::min(pixel.min_z, voxel_index_and_probability[2]);
211  pixel.max_z = std::max(pixel.max_z, voxel_index_and_probability[2]);
212  const float probability =
213  mapping::ValueToProbability(voxel_index_and_probability[3]);
214  pixel.probability_sum += probability;
215  pixel.max_probability = std::max(pixel.max_probability, probability);
216  }
217  return accumulated_pixel_data;
218 }
219 
220 // The first three entries of each returned value are a cell_index and the
221 // last is the corresponding probability value. We batch them together like
222 // this to only have one vector and have better cache locality.
223 std::vector<Eigen::Array4i> ExtractVoxelData(
224  const HybridGrid& hybrid_grid, const transform::Rigid3f& transform,
225  Eigen::Array2i* min_index, Eigen::Array2i* max_index) {
226  std::vector<Eigen::Array4i> voxel_indices_and_probabilities;
227  const float resolution_inverse = 1.f / hybrid_grid.resolution();
228 
229  constexpr float kXrayObstructedCellProbabilityLimit = 0.501f;
230  for (auto it = HybridGrid::Iterator(hybrid_grid); !it.Done(); it.Next()) {
231  const uint16 probability_value = it.GetValue();
232  const float probability = mapping::ValueToProbability(probability_value);
233  if (probability < kXrayObstructedCellProbabilityLimit) {
234  // We ignore non-obstructed cells.
235  continue;
236  }
237 
238  const Eigen::Vector3f cell_center_submap =
239  hybrid_grid.GetCenterOfCell(it.GetCellIndex());
240  const Eigen::Vector3f cell_center_global = transform * cell_center_submap;
241  const Eigen::Array4i voxel_index_and_probability(
242  common::RoundToInt(cell_center_global.x() * resolution_inverse),
243  common::RoundToInt(cell_center_global.y() * resolution_inverse),
244  common::RoundToInt(cell_center_global.z() * resolution_inverse),
245  probability_value);
246 
247  voxel_indices_and_probabilities.push_back(voxel_index_and_probability);
248  const Eigen::Array2i pixel_index = voxel_index_and_probability.head<2>();
249  *min_index = min_index->cwiseMin(pixel_index);
250  *max_index = max_index->cwiseMax(pixel_index);
251  }
252  return voxel_indices_and_probabilities;
253 }
254 
255 // Builds texture data containing interleaved value and alpha for the
256 // visualization from 'accumulated_pixel_data'.
257 string ComputePixelValues(
258  const std::vector<PixelData>& accumulated_pixel_data) {
259  string cell_data;
260  cell_data.reserve(2 * accumulated_pixel_data.size());
261  constexpr float kMinZDifference = 3.f;
262  constexpr float kFreeSpaceWeight = 0.15f;
263  for (const PixelData& pixel : accumulated_pixel_data) {
264  // TODO(whess): Take into account submap rotation.
265  // TODO(whess): Document the approach and make it more independent from the
266  // chosen resolution.
267  const float z_difference = pixel.count > 0 ? pixel.max_z - pixel.min_z : 0;
268  if (z_difference < kMinZDifference) {
269  cell_data.push_back(0); // value
270  cell_data.push_back(0); // alpha
271  continue;
272  }
273  const float free_space = std::max(z_difference - pixel.count, 0.f);
274  const float free_space_weight = kFreeSpaceWeight * free_space;
275  const float total_weight = pixel.count + free_space_weight;
276  const float free_space_probability = 1.f - pixel.max_probability;
277  const float average_probability = mapping::ClampProbability(
278  (pixel.probability_sum + free_space_probability * free_space_weight) /
279  total_weight);
280  const int delta =
281  128 - mapping::ProbabilityToLogOddsInteger(average_probability);
282  const uint8 alpha = delta > 0 ? 0 : -delta;
283  const uint8 value = delta > 0 ? delta : 0;
284  cell_data.push_back(value); // value
285  cell_data.push_back((value || alpha) ? alpha : 1); // alpha
286  }
287  return cell_data;
288 }
289 
290 } // namespace
291 
293  const sensor::RangeData& range_data, const transform::Rigid3f& pose,
294  const float slice_z,
295  const mapping_2d::RangeDataInserter& range_data_inserter,
296  mapping_2d::ProbabilityGrid* result) {
297  std::vector<RaySegment> segments;
298  GenerateSegmentForSlice(
299  range_data, pose,
300  transform::Rigid3f::Translation(-slice_z * Eigen::Vector3f::UnitZ()),
301  &segments);
302  InsertSegmentsIntoProbabilityGrid(segments, range_data_inserter.hit_table(),
303  range_data_inserter.miss_table(), result);
304 }
305 
306 proto::SubmapsOptions CreateSubmapsOptions(
307  common::LuaParameterDictionary* parameter_dictionary) {
308  proto::SubmapsOptions options;
309  options.set_high_resolution(
310  parameter_dictionary->GetDouble("high_resolution"));
311  options.set_high_resolution_max_range(
312  parameter_dictionary->GetDouble("high_resolution_max_range"));
313  options.set_low_resolution(parameter_dictionary->GetDouble("low_resolution"));
314  options.set_num_range_data(
315  parameter_dictionary->GetNonNegativeInt("num_range_data"));
316  *options.mutable_range_data_inserter_options() =
318  parameter_dictionary->GetDictionary("range_data_inserter").get());
319  CHECK_GT(options.num_range_data(), 0);
320  return options;
321 }
322 
323 Submap::Submap(const float high_resolution, const float low_resolution,
324  const transform::Rigid3d& local_pose)
325  : mapping::Submap(local_pose),
326  high_resolution_hybrid_grid_(high_resolution),
327  low_resolution_hybrid_grid_(low_resolution) {}
328 
330  const transform::Rigid3d& global_submap_pose,
331  mapping::proto::SubmapQuery::Response* const response) const {
332  response->set_submap_version(num_range_data());
333  // Generate an X-ray view through the 'hybrid_grid', aligned to the xy-plane
334  // in the global map frame.
335  const float resolution = high_resolution_hybrid_grid_.resolution();
336  response->set_resolution(resolution);
337 
338  // Compute a bounding box for the texture.
339  Eigen::Array2i min_index(INT_MAX, INT_MAX);
340  Eigen::Array2i max_index(INT_MIN, INT_MIN);
341  const std::vector<Eigen::Array4i> voxel_indices_and_probabilities =
342  ExtractVoxelData(high_resolution_hybrid_grid_,
343  global_submap_pose.cast<float>(), &min_index,
344  &max_index);
345 
346  const int width = max_index.y() - min_index.y() + 1;
347  const int height = max_index.x() - min_index.x() + 1;
348  response->set_width(width);
349  response->set_height(height);
350 
351  const std::vector<PixelData> accumulated_pixel_data = AccumulatePixelData(
352  width, height, min_index, max_index, voxel_indices_and_probabilities);
353  const string cell_data = ComputePixelValues(accumulated_pixel_data);
354 
355  common::FastGzipString(cell_data, response->mutable_cells());
356  *response->mutable_slice_pose() = transform::ToProto(
357  global_submap_pose.inverse() *
358  transform::Rigid3d::Translation(Eigen::Vector3d(
359  max_index.x() * resolution, max_index.y() * resolution,
360  global_submap_pose.translation().z())));
361 }
362 
363 Submaps::Submaps(const proto::SubmapsOptions& options)
364  : options_(options),
365  range_data_inserter_(options.range_data_inserter_options()) {
366  // We always want to have at least one submap which we can return and will
367  // create it at the origin in absence of a better choice.
368  //
369  // TODO(whess): Start with no submaps, so that all of them can be
370  // approximately gravity aligned.
372 }
373 
374 const Submap* Submaps::Get(int index) const {
375  CHECK_GE(index, 0);
376  CHECK_LT(index, size());
377  return submaps_[index].get();
378 }
379 
380 int Submaps::size() const { return submaps_.size(); }
381 
383  const Eigen::Quaterniond& gravity_alignment) {
384  for (const int index : insertion_indices()) {
385  Submap* submap = submaps_[index].get();
386  const sensor::RangeData transformed_range_data = sensor::TransformRangeData(
387  range_data, submap->local_pose().inverse().cast<float>());
389  FilterRangeDataByMaxRange(transformed_range_data,
390  options_.high_resolution_max_range()),
392  range_data_inserter_.Insert(transformed_range_data,
393  &submap->low_resolution_hybrid_grid_);
394  ++submap->num_range_data_;
395  }
396  const Submap* const last_submap = Get(size() - 1);
397  if (last_submap->num_range_data_ == options_.num_range_data()) {
398  AddSubmap(transform::Rigid3d(range_data.origin.cast<double>(),
399  gravity_alignment));
400  }
401 }
402 
403 void Submaps::AddSubmap(const transform::Rigid3d& local_pose) {
404  if (size() > 1) {
405  Submap* submap = submaps_[size() - 2].get();
406  CHECK(!submap->finished_);
407  submap->finished_ = true;
408  }
409  submaps_.emplace_back(new Submap(options_.high_resolution(),
410  options_.low_resolution(), local_pose));
411  LOG(INFO) << "Added submap " << size();
412 }
413 
414 } // namespace mapping_3d
415 } // namespace cartographer
int count
Definition: 3d/submaps.cc:42
size_t num_range_data() const
Definition: submaps.h:68
proto::RangeDataInserterOptions options_
void FastGzipString(const string &uncompressed, string *compressed)
Definition: port.h:50
float ClampProbability(const float probability)
proto::RangeDataInserterOptions CreateRangeDataInserterOptions(common::LuaParameterDictionary *parameter_dictionary)
void ToResponseProto(const transform::Rigid3d &global_submap_pose, mapping::proto::SubmapQuery::Response *response) const override
Definition: 3d/submaps.cc:329
int RoundToInt(const float x)
Definition: port.h:42
uint8_t uint8
Definition: port.h:32
RangeDataInserter range_data_inserter_
Definition: 3d/submaps.h:101
transform::Rigid3d local_pose() const
Definition: submaps.h:65
void AddSubmap(const transform::Rigid3d &local_pose)
Definition: 3d/submaps.cc:403
Submap(float high_resolution, float low_resolution, const transform::Rigid3d &local_pose)
Definition: 3d/submaps.cc:323
int max_z
Definition: 3d/submaps.cc:41
std::vector< int > insertion_indices() const
Definition: submaps.cc:38
Rigid3< OtherType > cast() const
float ValueToProbability(const uint16 value)
int min_z
Definition: 3d/submaps.cc:40
RangeData TransformRangeData(const RangeData &range_data, const transform::Rigid3f &transform)
Definition: range_data.cc:41
transform::Rigid3d pose
float probability_sum
Definition: 3d/submaps.cc:43
proto::Rigid2d ToProto(const transform::Rigid2d &transform)
Definition: transform.cc:44
std::vector< std::unique_ptr< Submap > > submaps_
Definition: 3d/submaps.h:100
proto::SubmapsOptions CreateSubmapsOptions(common::LuaParameterDictionary *parameter_dictionary)
Definition: 3d/submaps.cc:306
Eigen::Vector2f to
Definition: 3d/submaps.cc:35
Eigen::Vector2f from
Definition: 3d/submaps.cc:34
uint8 ProbabilityToLogOddsInteger(const float probability)
Definition: submaps.h:46
typename Grid< uint16 >::Iterator Iterator
Definition: hybrid_grid.h:416
void InsertRangeData(const sensor::RangeData &range_data, const Eigen::Quaterniond &gravity_alignment)
Definition: 3d/submaps.cc:382
const Vector & translation() const
const std::vector< uint16 > & hit_table() const
const Submap * Get(int index) const override
Definition: 3d/submaps.cc:374
bool hit
Definition: 3d/submaps.cc:36
float max_probability
Definition: 3d/submaps.cc:44
const proto::SubmapsOptions options_
Definition: 3d/submaps.h:96
const std::vector< uint16 > & miss_table() const
void InsertIntoProbabilityGrid(const sensor::RangeData &range_data, const transform::Rigid3f &pose, const float slice_z, const mapping_2d::RangeDataInserter &range_data_inserter, mapping_2d::ProbabilityGrid *result)
Definition: 3d/submaps.cc:292
float value
uint16_t uint16
Definition: port.h:33
static Rigid3 Translation(const Vector &vector)
std::unique_ptr< RangeDataInserter > range_data_inserter_
std::unique_ptr< LuaParameterDictionary > GetDictionary(const string &key)
void Insert(const sensor::RangeData &range_data, HybridGrid *hybrid_grid) const


cartographer
Author(s):
autogenerated on Wed Jun 5 2019 21:57:59