ActiveSubmaps2D::AddSubmap 添加子图的过程中,需要新建一个子图(类Submap2D)然后进行添加。创建Submap2D则需要一个创建Grid2D
在ActiveSubmaps2D::CreateGrid函数中(cartographer/mapping/2d/submap2d.h),可以创建不同类型的Grid2D:PROBABILITY_GRID与TSDF
类ProbabilityGrid 和 TSDF2D 均继承于 Grid2D,Grid2D 继承于 GridInterface
Grid2D
该类表示一个2D的网格地图
保存的值包括 所有可能的栅格值以及其对应的free 概率的值,即转换表
成员变量
1 2 3 4 5 6 7 8 9 10 11 12
| MapLimits limits_;
std::vector<uint16> correspondence_cost_cells_; float min_correspondence_cost_; float max_correspondence_cost_; std::vector<int> update_indices_;
Eigen::AlignedBox2i known_cells_box_;
const std::vector<float>* value_to_correspondence_cost_table_;
|
说明!
probability: 栅格被占据的概率
kMinProbability = 0.1, kMaxProbability = 0.9, kUnknownProbabilityValue = 0
Odds: probability / (1.0f - probability)
CorrespondenceCost: 栅格是free的概率; CorrespondenceCost + probability = 1
kMinCorrespondenceCost = 0.1, kMaxCorrespondenceCost = 0.9
kUnknownCorrespondenceValue = 0, kUpdateMarker = 32768
Value: 代码里存储的栅格值, 是[0, 32767]范围内的 uint16 整数
valueto_correspondence_cost_table: 将[0, 1~32767] 映射到 [0, 0.1~0.9] 的转换表
hit_table 计算[0, 1~32767] 按照占用概率0.55更新之后的值
misstable 计算[0, 1~32767] 按照空闲概率0.49更新之后的值
构造函数
基本上就是简单粗暴的赋值操作
correspondencecost_cells : 初始化为一个长度为 地图长 地图宽的,默认值为0的vector。*保存地图栅格值!
valueto_correspondence_cost_table :一个转换表,存储着栅格值[0, 1~32767] 映射到 [0, 0.1~0.9] 的表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
Grid2D::Grid2D(const MapLimits& limits, float min_correspondence_cost, float max_correspondence_cost, ValueConversionTables* conversion_tables) : limits_(limits), correspondence_cost_cells_( limits_.cell_limits().num_x_cells * limits_.cell_limits().num_y_cells, kUnknownCorrespondenceValue), min_correspondence_cost_(min_correspondence_cost), max_correspondence_cost_(max_correspondence_cost), value_to_correspondence_cost_table_(conversion_tables->GetConversionTable( max_correspondence_cost, min_correspondence_cost, max_correspondence_cost)) { CHECK_LT(min_correspondence_cost_, max_correspondence_cost_); }
|
GetConversionTable 返回的内容是一个vector,长度为65536 + 1,内容为如下的第二行
1 2 3
| [0, 1, ..., 32767, 0, 1, ..., 32767] 通过映射转换如下,除了0直接变成0.9, 其他的数都缓缓往上均匀增加 [0.9, 0.1, ..., 0.9, 0.9, 0.1,..., 0.9 ]
|
结果为存储的值相对应的free的概率。
注意,这里conversion_tables 经过调用,成员变量已经被赋值了。
因此,调用的Grid2D的ProbabilityGrid的构造函数中, conversiontables 也就有值了给conversion_tables 了
1 2 3 4 5
| ProbabilityGrid::ProbabilityGrid(const MapLimits& limits, ValueConversionTables* conversion_tables) : Grid2D(limits, kMinCorrespondenceCost, kMaxCorrespondenceCost, conversion_tables), conversion_tables_(conversion_tables) {}
|
成员函数
FinishUpdate
雷达的点是散射的,因此有的栅格会被多次打到,为了防止多次更新,可以设置结束更新。
在更新的一次,都会加一次kUpdateMarker,32768。 和转换表内的两次0-32767对应
1 2 3 4 5 6 7 8 9
| void Grid2D::FinishUpdate() { while (!update_indices_.empty()) { DCHECK_GE(correspondence_cost_cells_[update_indices_.back()], kUpdateMarker); correspondence_cost_cells_[update_indices_.back()] -= kUpdateMarker; update_indices_.pop_back(); } }
|
GrowLimits
地图每一次扩大都是两倍的进行扩大。
bounding box平移,从左上角移动到以old为左上角的一个box
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| void Grid2D::GrowLimits(const Eigen::Vector2f& point, const std::vector<std::vector<uint16>*>& grids, const std::vector<uint16>& grids_unknown_cell_values) { CHECK(update_indices_.empty()); while (!limits_.Contains(limits_.GetCellIndex(point))) { const int x_offset = limits_.cell_limits().num_x_cells / 2; const int y_offset = limits_.cell_limits().num_y_cells / 2; const MapLimits new_limits( limits_.resolution(), limits_.max() + limits_.resolution() * Eigen::Vector2d(y_offset, x_offset), CellLimits(2 * limits_.cell_limits().num_x_cells, 2 * limits_.cell_limits().num_y_cells)); const int stride = new_limits.cell_limits().num_x_cells; const int offset = x_offset + stride * y_offset; const int new_size = new_limits.cell_limits().num_x_cells * new_limits.cell_limits().num_y_cells;
for (size_t grid_index = 0; grid_index < grids.size(); ++grid_index) { std::vector<uint16> new_cells(new_size, grids_unknown_cell_values[grid_index]); for (int i = 0; i < limits_.cell_limits().num_y_cells; ++i) { for (int j = 0; j < limits_.cell_limits().num_x_cells; ++j) { new_cells[offset + j + i * stride] = (*grids[grid_index])[j + i * limits_.cell_limits().num_x_cells]; } } *grids[grid_index] = new_cells; } limits_ = new_limits; if (!known_cells_box_.isEmpty()) { known_cells_box_.translate(Eigen::Vector2i(x_offset, y_offset)); } } }
|
MapLimits
此类就是计算物理坐标和像素坐标之间的一个转换,对地图的大小保存一些元数据
几个地图坐标系
成员变量
- resolution:分辨率。默认0.05,室外可以是0.1,室内精度要高的话可以0.02,需要注意内存
- Eigen::Vector2d max: 左上角的坐标为地图坐标的最大值
- CellLimits celllimits: 一个struct保存着,地图x方向与y方向的格子数
构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
MapLimits(const double resolution, const Eigen::Vector2d& max, const CellLimits& cell_limits) : resolution_(resolution), max_(max), cell_limits_(cell_limits) { CHECK_GT(resolution_, 0.); CHECK_GT(cell_limits.num_x_cells, 0.); CHECK_GT(cell_limits.num_y_cells, 0.); }
|
调用处
于cartographer/mapping/2d/submap_2d.cc
文件中,函数ActiveSubmaps2D::CreateGrid 里,创建了MapLimits
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| std::unique_ptr<GridInterface> ActiveSubmaps2D::CreateGrid( const Eigen::Vector2f& origin) { constexpr int kInitialSubmapSize = 100; float resolution = options_.grid_options_2d().resolution(); switch (options_.grid_options_2d().grid_type()) { case proto::GridOptions2D::PROBABILITY_GRID: return absl::make_unique<ProbabilityGrid>( MapLimits(resolution, origin.cast<double>() + 0.5 * kInitialSubmapSize * resolution * Eigen::Vector2d::Ones(), CellLimits(kInitialSubmapSize, kInitialSubmapSize)), &conversion_tables_);
|
左上角的最大值 = origin坐标点 + 栅格数*分辨率。
如果分辨率越小,地图左上角能覆盖到的距离越短,但是格子多,因此分辨率会很高,很清晰
成员函数
- contain,判断给出的点的下标是否在坐标系内
- GetCellCenter:从下标点转为坐标点
- GetCellIndex:从坐标点转换为下标点