一、前言
前面在分析LocalTrajectoryBuilder的时候没有太关注点云地图、栅格地图的建立,以及在ceres建立最小二乘问题后,有一个ScoreCandidates的打分过程,涉及到GridType::PROBABILITY_GRID概率栅格地图的打分,本文将对这些内容作出补充
二、submap_2d地图类型及维护方式
在之前Node类构建的时候还没有涉及到地图,LocalTrajectoryBuilder的时候才开始维护地图,首先来到LocalTrajectoryBuilder2d.h文件,struct InsertionResult、InsertIntoSubmap函数是Submaps2D类型,不过他们都是直接从active_submaps_里拿数据的,应该用的也是active_submaps_的类 私有类中维护的active_submaps_是ActiveSubmaps2D类型,顺着这个类来到cartographer::mapping::2d::submap2d.h文件里
//读取配置,主要是地图插入器的选项,栅格地图的类型
proto::SubmapsOptions2D CreateSubmapsOptions2D(
common::LuaParameterDictionary* parameter_dictionary);
class Submap2D : public Submap {
public:
Submap2D(const Eigen::Vector2f& origin, std::unique_ptr<Grid2D> grid,
ValueConversionTables* conversion_tables);
//防止隐式转换,第二个参数是默认值?
explicit Submap2D(const proto::Submap2D& proto,
ValueConversionTables* conversion_tables);
//
proto::Submap ToProto(bool include_grid_data) const override;
void UpdateFromProto(const proto::Submap& proto) override;
void ToResponseProto(const transform::Rigid3d& global_submap_pose,
proto::SubmapQuery::Response* response) const override;
const Grid2D* grid() const { return grid_.get(); }
// Insert 'range_data' into this submap using 'range_data_inserter'. The
// submap must not be finished yet.
void InsertRangeData(const sensor::RangeData& range_data,
const RangeDataInserterInterface* range_data_inserter);
void Finish();
private:
std::unique_ptr<Grid2D> grid_;
ValueConversionTables* conversion_tables_;
};
class ActiveSubmaps2D {
public:
//构造函数
explicit ActiveSubmaps2D(const proto::SubmapsOptions2D& options);
//屏蔽拷贝构造函数和拷贝赋值运算符
ActiveSubmaps2D(const ActiveSubmaps2D&) = delete;
ActiveSubmaps2D& operator=(const ActiveSubmaps2D&) = delete;
// Inserts 'range_data' into the Submap collection.
//LocalTrajectoryBuilder2D::InsertIntoSubmap调用此函数
//实现插入子图功能
std::vector<std::shared_ptr<const Submap2D>> InsertRangeData(
const sensor::RangeData& range_data);
//LocalTrajectoryBuilder2D::ScanMatch调用此函数
std::vector<std::shared_ptr<const Submap2D>> submaps() const;
private:
std::unique_ptr<RangeDataInserterInterface> CreateRangeDataInserter();
std::unique_ptr<GridInterface> CreateGrid(const Eigen::Vector2f& origin);
void FinishSubmap();
void AddSubmap(const Eigen::Vector2f& origin);
//子图的配置选项
const proto::SubmapsOptions2D options_;
//子图的容器,以每张子图为一个成员
std::vector<std::shared_ptr<Submap2D>> submaps_;
//传感器信息插入器
std::unique_ptr<RangeDataInserterInterface> range_data_inserter_;
ValueConversionTables conversion_tables_;
};
} // namespace mapping
} // namespace cartographer
proto::SubmapsOptions2D CreateSubmapsOptions2D(
common::LuaParameterDictionary* const parameter_dictionary) {
proto::SubmapsOptions2D options;
options.set_num_range_data(
parameter_dictionary->GetNonNegativeInt("num_range_data"));
*options.mutable_grid_options_2d() = CreateGridOptions2D(
parameter_dictionary->GetDictionary("grid_options_2d").get());
*options.mutable_range_data_inserter_options() =
CreateRangeDataInserterOptions(
parameter_dictionary->GetDictionary("range_data_inserter").get());
bool valid_range_data_inserter_grid_combination = false;
const proto::GridOptions2D_GridType& grid_type =
options.grid_options_2d().grid_type();
const proto::RangeDataInserterOptions_RangeDataInserterType&
range_data_inserter_type =
options.range_data_inserter_options().range_data_inserter_type();
if (grid_type == proto::GridOptions2D::PROBABILITY_GRID &&
range_data_inserter_type ==
proto::RangeDataInserterOptions::PROBABILITY_GRID_INSERTER_2D) {
valid_range_data_inserter_grid_combination = true;
}
if (grid_type == proto::GridOptions2D::TSDF &&
range_data_inserter_type ==
proto::RangeDataInserterOptions::TSDF_INSERTER_2D) {
valid_range_data_inserter_grid_combination = true;
}
CHECK(valid_range_data_inserter_grid_combination)
<< "Invalid combination grid_type " << grid_type
<< " with range_data_inserter_type " << range_data_inserter_type;
CHECK_GT(options.num_range_data(), 0);
return options;
}
//构造函数
Submap2D::Submap2D(const Eigen::Vector2f& origin, std::unique_ptr<Grid2D> grid,
ValueConversionTables* conversion_tables)
: Submap(transform::Rigid3d::Translation(
Eigen::Vector3d(origin.x(), origin.y(), 0.))),
conversion_tables_(conversion_tables) {
//构造grid_
grid_ = std::move(grid);
}
//第二个构造函数,防止隐式转换的版本?
Submap2D::Submap2D(const proto::Submap2D& proto,
ValueConversionTables* conversion_tables)
: Submap(transform::ToRigid3(proto.local_pose())),
conversion_tables_(conversion_tables) {
//从proto拿数据,按照概率栅格地图和tsdf图,将数据传到grid_
if (proto.has_grid()) {
if (proto.grid().has_probability_grid_2d()) {
grid_ =
absl::make_unique<ProbabilityGrid>(proto.grid(), conversion_tables_);
} else if (proto.grid().has_tsdf_2d()) {
grid_ = absl::make_unique<TSDF2D>(proto.grid(), conversion_tables_);
} else {
LOG(FATAL) << "proto::Submap2D has grid with unknown type.";
}
}
set_num_range_data(proto.num_range_data());
set_insertion_finished(proto.finished());
}
//将grid_转化为proto
proto::Submap Submap2D::ToProto(const bool include_grid_data) const {
proto::Submap proto;
auto* const submap_2d = proto.mutable_submap_2d();
*submap_2d->mutable_local_pose() = transform::ToProto(local_pose());
submap_2d->set_num_range_data(num_range_data());
submap_2d->set_finished(insertion_finished());
if (include_grid_data) {
CHECK(grid_);
*submap_2d->mutable_grid() = grid_->ToProto();
}
return proto;
}
//从proto拿数据更新grid_,但是看着和第二个构造函数很像
void Submap2D::UpdateFromProto(const proto::Submap& proto) {
CHECK(proto.has_submap_2d());
const auto& submap_2d = proto.submap_2d();
set_num_range_data(submap_2d.num_range_data());
set_insertion_finished(submap_2d.finished());
if (proto.submap_2d().has_grid()) {
if (proto.submap_2d().grid().has_probability_grid_2d()) {
grid_ = absl::make_unique<ProbabilityGrid>(proto.submap_2d().grid(),
conversion_tables_);
} else if (proto.submap_2d().grid().has_tsdf_2d()) {
grid_ = absl::make_unique<TSDF2D>(proto.submap_2d().grid(),
conversion_tables_);
} else {
LOG(FATAL) << "proto::Submap2D has grid with unknown type.";
}
}
}
void Submap2D::ToResponseProto(
const transform::Rigid3d&,
proto::SubmapQuery::Response* const response) const {
if (!grid_) return;
response->set_submap_version(num_range_data());
proto::SubmapQuery::Response::SubmapTexture* const texture =
response->add_textures();
grid()->DrawToSubmapTexture(texture, local_pose());
}
//将激光的扫描数据插入到grid_对象中
void Submap2D::InsertRangeData(
const sensor::RangeData& range_data,
const RangeDataInserterInterface* range_data_inserter) {
CHECK(grid_);
CHECK(!insertion_finished());
range_data_inserter->Insert(range_data, grid_.get());
set_num_range_data(num_range_data() + 1);
}
void Submap2D::Finish() {
CHECK(grid_);
CHECK(!insertion_finished());
grid_ = grid_->ComputeCroppedGrid();
set_insertion_finished(true);
}
//构造函数
//主要是私有成员函数在工作,读入了options_,然后利用CreateRangeDataInserter构建了插入器对象,给了range_data_inserter_
ActiveSubmaps2D::ActiveSubmaps2D(const proto::SubmapsOptions2D& options)
: options_(options), range_data_inserter_(CreateRangeDataInserter()) {}
std::vector<std::shared_ptr<const Submap2D>> ActiveSubmaps2D::submaps() const {
return std::vector<std::shared_ptr<const Submap2D>>(submaps_.begin(),
submaps_.end());
}
//将扫描数据插入子图
std::vector<std::shared_ptr<const Submap2D>> ActiveSubmaps2D::InsertRangeData(
const sensor::RangeData& range_data) {
//第一张图,用AddSubmap构建新图
if (submaps_.empty() ||
submaps_.back()->num_range_data() == options_.num_range_data()) {
AddSubmap(range_data.origin.head<2>());
}
//依次将扫描数据插入到子图里
for (auto& submap : submaps_) {
submap->InsertRangeData(range_data, range_data_inserter_.get());
}
//检查较早的图里面插入的数据量
if (submaps_.front()->num_range_data() == 2 * options_.num_range_data()) {
submaps_.front()->Finish();
}
return submaps();
}
//构建插入器对象
std::unique_ptr<RangeDataInserterInterface>
ActiveSubmaps2D::CreateRangeDataInserter() {
//概率图和tsdf图两种
switch (options_.range_data_inserter_options().range_data_inserter_type()) {
case proto::RangeDataInserterOptions::PROBABILITY_GRID_INSERTER_2D:
//返回一个概率栅格地图插入器
return absl::make_unique<ProbabilityGridRangeDataInserter2D>(
options_.range_data_inserter_options()
.probability_grid_range_data_inserter_options_2d());
case proto::RangeDataInserterOptions::TSDF_INSERTER_2D:
return absl::make_unique<TSDFRangeDataInserter2D>(
options_.range_data_inserter_options()
.tsdf_range_data_inserter_options_2d());
default:
LOG(FATAL) << "Unknown RangeDataInserterType.";
}
}
std::unique_ptr<GridInterface> ActiveSubmaps2D::CreateGrid(
const Eigen::Vector2f& origin) {
//子图大小及分辨率
constexpr int kInitialSubmapSize = 100;
float resolution = options_.grid_options_2d().resolution();
//概率栅格地图和tsdf图
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_);
case proto::GridOptions2D::TSDF:
return absl::make_unique<TSDF2D>(
MapLimits(resolution,
origin.cast<double>() + 0.5 * kInitialSubmapSize *
resolution *
Eigen::Vector2d::Ones(),
CellLimits(kInitialSubmapSize, kInitialSubmapSize)),
options_.range_data_inserter_options()
.tsdf_range_data_inserter_options_2d()
.truncation_distance(),
options_.range_data_inserter_options()
.tsdf_range_data_inserter_options_2d()
.maximum_weight(),
&conversion_tables_);
default:
LOG(FATAL) << "Unknown GridType.";
}
}
void ActiveSubmaps2D::AddSubmap(const Eigen::Vector2f& origin) {
//如果旧图还在里面,保证旧图插入结束后,把旧图弹出去
if (submaps_.size() >= 2) {
// This will crop the finished Submap before inserting a new Submap to
// reduce peak memory usage a bit.
CHECK(submaps_.front()->insertion_finished());
submaps_.erase(submaps_.begin());
}
//构建一个新的Submap2D类型的对象放置到容器submaps_中
//调用了函数CreateGrid函数为该对象提供了一个保存栅格占用信息的存储结构
submaps_.push_back(absl::make_unique<Submap2D>(
origin,
std::unique_ptr<Grid2D>(
static_cast<Grid2D*>(CreateGrid(origin).release())),
&conversion_tables_));
}
把.h和.cc文件里面关于Submap和ActiveSubmaps2D的部分拿了过来,Submap类里边大多数函数都是关于proto的交互处理,ActiveSubmaps2D最大的函数是InsertRangeData,其余的函数大都在这个函数里得到了调用,主要是新图和旧图的管理,栅格地图的构建
私有类里有一个FinishSubmap函数没找到,应该是这东西还有个父类 没找着 全局搜这个函数也只搜出来这一个地方,实在是奇怪的很 估计是私有类里写了,.cc文件忘了写了,或者最后没用到这个功能
三、占据栅格地图格式与维护
在LocalTrajectoryBuilder中,还有一类比较重要的地图的使用,第一次出现在ScanMatch这个大函数里,为const Grid2D& grid,来自cartographer/mapping/2d/grid_2d.h,以及概率栅格地图const ProbabilityGrid& probability_grid,来自cartographer/mapping/2d/probability_grid.h,观察文件结构可以看出probability_grid是由grid派生而来的
//构造函数,MapLimits来自同文件架下的map_limits.h文件,内容主要有分辨率、x和y方向的栅格大小
//内部成员包括分辨率、xy方向栅格的最大最小值等
ProbabilityGrid::ProbabilityGrid(const MapLimits& limits,
ValueConversionTables* conversion_tables)
: Grid2D(limits, kMinCorrespondenceCost, kMaxCorrespondenceCost,
conversion_tables),
conversion_tables_(conversion_tables) {}
//构造函数,数据来源proto
ProbabilityGrid::ProbabilityGrid(const proto::Grid2D& proto,
ValueConversionTables* conversion_tables)
: Grid2D(proto, conversion_tables), conversion_tables_(conversion_tables) {
CHECK(proto.has_probability_grid_2d());
}
// Sets the probability of the cell at 'cell_index' to the given
// 'probability'. Only allowed if the cell was unknown before.
//给单元格赋概率值
void ProbabilityGrid::SetProbability(const Eigen::Array2i& cell_index,
const float probability) {
//获取目标栅格单元
uint16& cell =
(*mutable_correspondence_cost_cells())[ToFlatIndex(cell_index)];
//只有单元格概率未知的时候才给赋值
CHECK_EQ(cell, kUnknownProbabilityValue);
cell =
//调用probability_values.h里的CorrespondenceCostToValue函数,给概率栅格地图赋新值
//CorrespondenceCostToValue将float类型的数据转换为uint16类型,并将输入从区间[kMinCorrespondenceCost,kMaxCorrespondenceCost]映射到[1,32767]
//函数ProbabilityToCorrespondenceCost返回1-probability的值
CorrespondenceCostToValue(ProbabilityToCorrespondenceCost(probability));
//调用父类Grid2D的接口,记录栅格单元索引
mutable_known_cells_box()->extend(cell_index.matrix());
}
// Applies the 'odds' specified when calling ComputeLookupTableToApplyOdds()
// to the probability of the cell at 'cell_index' if the cell has not already
// been updated. Multiple updates of the same cell will be ignored until
// FinishUpdate() is called. Returns true if the cell was updated.
//
// If this is the first call to ApplyOdds() for the specified cell, its value
// will be set to probability corresponding to 'odds'.
//通过查表来更新栅格单元的占用概率
bool ProbabilityGrid::ApplyLookupTable(const Eigen::Array2i& cell_index,
const std::vector<uint16>& table) {
DCHECK_EQ(table.size(), kUpdateMarker);
//将栅格索引cell_index转化为flat_index
const int flat_index = ToFlatIndex(cell_index);
uint16* cell = &(*mutable_correspondence_cost_cells())[flat_index];
// 确保没有超出查表范围
if (*cell >= kUpdateMarker) {
return false;
}
// 通过父类的接口记录下当前更新的栅格单元的存储索引flat_index
mutable_update_indices()->push_back(flat_index);
// 通过查表更新栅格单元
*cell = table[*cell];
DCHECK_GE(*cell, kUpdateMarker);
// 最后在通过父类标记cell_index所对应的栅格的占用概率已知
mutable_known_cells_box()->extend(cell_index.matrix());
return true;
}
GridType ProbabilityGrid::GetGridType() const {
return GridType::PROBABILITY_GRID;
}
// Returns the probability of the cell with 'cell_index'.
//返回栅格被占据的概率
float ProbabilityGrid::GetProbability(const Eigen::Array2i& cell_index) const {
//如果要判断的栅格不在limits里面,就返回kMinProbability
if (!limits().Contains(cell_index)) return kMinProbability;
//和上面的函数刚好相反,把unit16转换成float
return CorrespondenceCostToProbability(ValueToCorrespondenceCost(
correspondence_cost_cells()[ToFlatIndex(cell_index)]));
}
proto::Grid2D ProbabilityGrid::ToProto() const {
proto::Grid2D result;
result = Grid2D::ToProto();
result.mutable_probability_grid_2d();
return result;
}
//裁剪子图
std::unique_ptr<Grid2D> ProbabilityGrid::ComputeCroppedGrid() const {
Eigen::Array2i offset;
CellLimits cell_limits;
ComputeCroppedLimits(&offset, &cell_limits);
//接着获取子图的分辨率和最大的xy索引,并构建一个新的ProbabilityGrid对象cropped_grid。
const double resolution = limits().resolution();
const Eigen::Vector2d max =
limits().max() - resolution * Eigen::Vector2d(offset.y(), offset.x());
std::unique_ptr<ProbabilityGrid> cropped_grid =
absl::make_unique<ProbabilityGrid>(
MapLimits(resolution, max, cell_limits), conversion_tables_);
//遍历了对象cropped_grid中的所有栅格,拷贝占用概率之后,返回对象退出
for (const Eigen::Array2i& xy_index : XYIndexRangeIterator(cell_limits)) {
if (!IsKnown(xy_index + offset)) continue;
cropped_grid->SetProbability(xy_index, GetProbability(xy_index + offset));
}
return std::unique_ptr<Grid2D>(cropped_grid.release());
}
//用于序列化
bool ProbabilityGrid::DrawToSubmapTexture(
proto::SubmapQuery::Response::SubmapTexture* const texture,
transform::Rigid3d local_pose) const {
Eigen::Array2i offset;
CellLimits cell_limits;
ComputeCroppedLimits(&offset, &cell_limits);
std::string cells;
for (const Eigen::Array2i& xy_index : XYIndexRangeIterator(cell_limits)) {
if (!IsKnown(xy_index + offset)) {
cells.push_back(0 /* unknown log odds value */);
cells.push_back(0 /* alpha */);
continue;
}
// We would like to add 'delta' but this is not possible using a value and
// alpha. We use premultiplied alpha, so when 'delta' is positive we can
// add it by setting 'alpha' to zero. If it is negative, we set 'value' to
// zero, and use 'alpha' to subtract. This is only correct when the pixel
// is currently white, so walls will look too gray. This should be hard to
// detect visually for the user, though.
const int delta =
128 - ProbabilityToLogOddsInteger(GetProbability(xy_index + offset));
const uint8 alpha = delta > 0 ? 0 : -delta;
const uint8 value = delta > 0 ? delta : 0;
cells.push_back(value);
cells.push_back((value || alpha) ? alpha : 1);
}
common::FastGzipString(cells, texture->mutable_cells());
texture->set_width(cell_limits.num_x_cells);
texture->set_height(cell_limits.num_y_cells);
const double resolution = limits().resolution();
texture->set_resolution(resolution);
const double max_x = limits().max().x() - resolution * offset.y();
const double max_y = limits().max().y() - resolution * offset.x();
*texture->mutable_slice_pose() = transform::ToProto(
local_pose.inverse() *
transform::Rigid3d::Translation(Eigen::Vector3d(max_x, max_y, 0.)));
return true;
}
//从proro取数据,栅格地图不被占据的概率最小值和最大值
float MinCorrespondenceCostFromProto(const proto::Grid2D& proto) {
if (proto.min_correspondence_cost() == 0.f &&
proto.max_correspondence_cost() == 0.f) {
LOG(WARNING) << "proto::Grid2D: min_correspondence_cost "
"is initialized with 0 indicating an older version of the "
"protobuf format. Loading default values.";
return kMinCorrespondenceCost;
} else {
return proto.min_correspondence_cost();
}
}
float MaxCorrespondenceCostFromProto(const proto::Grid2D& proto) {
if (proto.min_correspondence_cost() == 0.f &&
proto.max_correspondence_cost() == 0.f) {
LOG(WARNING) << "proto::Grid2D: max_correspondence_cost "
"is initialized with 0 indicating an older version of the "
"protobuf format. Loading default values.";
return kMaxCorrespondenceCost;
} else {
return proto.max_correspondence_cost();
}
}
} // namespace
//从proto拿地图属性,主要是栅格地图类型和分辨率
proto::GridOptions2D CreateGridOptions2D(
common::LuaParameterDictionary* const parameter_dictionary) {
proto::GridOptions2D options;
const std::string grid_type_string =
parameter_dictionary->GetString("grid_type");
proto::GridOptions2D_GridType grid_type;
CHECK(proto::GridOptions2D_GridType_Parse(grid_type_string, &grid_type))
<< "Unknown GridOptions2D_GridType kind: " << grid_type_string;
options.set_grid_type(grid_type);
options.set_resolution(parameter_dictionary->GetDouble("resolution"));
return options;
}
//构造函数,数据来源是栅格地图
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_);
}
//构造函数,数据来源是proto
Grid2D::Grid2D(const proto::Grid2D& proto,
ValueConversionTables* conversion_tables)
: limits_(proto.limits()),
correspondence_cost_cells_(),
min_correspondence_cost_(MinCorrespondenceCostFromProto(proto)),
max_correspondence_cost_(MaxCorrespondenceCostFromProto(proto)),
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_);
if (proto.has_known_cells_box()) {
const auto& box = proto.known_cells_box();
known_cells_box_ =
Eigen::AlignedBox2i(Eigen::Vector2i(box.min_x(), box.min_y()),
Eigen::Vector2i(box.max_x(), box.max_y()));
}
correspondence_cost_cells_.reserve(proto.cells_size());
for (const auto& cell : proto.cells()) {
CHECK_LE(cell, std::numeric_limits<uint16>::max());
correspondence_cost_cells_.push_back(cell);
}
}
// Finishes the update sequence.
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();
}
}
// Fills in 'offset' and 'limits' to define a subregion of that contains all
// known cells.
//修改offset和limits,使其能够容纳所有的栅格
void Grid2D::ComputeCroppedLimits(Eigen::Array2i* const offset,
CellLimits* const limits) const {
if (known_cells_box_.isEmpty()) {
*offset = Eigen::Array2i::Zero();
*limits = CellLimits(1, 1);
return;
}
*offset = known_cells_box_.min().array();
*limits = CellLimits(known_cells_box_.sizes().x() + 1,
known_cells_box_.sizes().y() + 1);
}
// Grows the map as necessary to include 'point'. This changes the meaning of
// these coordinates going forward. This method must be called immediately
// after 'FinishUpdate', before any calls to 'ApplyLookupTable'.
//增长地图,使其能够容纳point
void Grid2D::GrowLimits(const Eigen::Vector2f& point) {
GrowLimits(point, {mutable_correspondence_cost_cells()},
{kUnknownCorrespondenceValue});
}
//根据新加点的需求,创建地图,新旧地图中心一致,长宽变为二倍
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());
//如果地图没有包含索引为point的点
while (!limits_.Contains(limits_.GetCellIndex(point))) {
//x,y各自为当前地图长宽的一半
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));
}
}
}
proto::Grid2D Grid2D::ToProto() const {
proto::Grid2D result;
*result.mutable_limits() = mapping::ToProto(limits_);
*result.mutable_cells() = {correspondence_cost_cells_.begin(),
correspondence_cost_cells_.end()};
CHECK(update_indices().empty()) << "Serializing a grid during an update is "
"not supported. Finish the update first.";
if (!known_cells_box().isEmpty()) {
auto* const box = result.mutable_known_cells_box();
box->set_max_x(known_cells_box().max().x());
box->set_max_y(known_cells_box().max().y());
box->set_min_x(known_cells_box().min().x());
box->set_min_y(known_cells_box().min().y());
}
result.set_min_correspondence_cost(min_correspondence_cost_);
result.set_max_correspondence_cost(max_correspondence_cost_);
return result;
}
上面是probability_grid.cc和grid_2d.cc的文件,probability_grid是grid_2d的子类,主要是给栅格地图赋概率值、读取概率这些功能,grid_2d是父类,主要负责构建和修剪、增长栅格地图,使其能够适应新增的点,存储单元为uint16整型数,即0~65535,而不关心具体实际意义,体现为CorrespondenceCost,概率栅格地图里面使用的float型的probability,两者在互相转化后是互补的关系,和为1(probability下)
四、关于ScoreCandidates
这个时候再回来看ScoreCandidates的打分过程就简单很多了
void RealTimeCorrelativeScanMatcher2D::ScoreCandidates(
const Grid2D& grid, const std::vector<DiscreteScan2D>& discrete_scans,
const SearchParameters& search_parameters,
std::vector<Candidate2D>* const candidates) const {
for (Candidate2D& candidate : *candidates) {
switch (grid.GetGridType()) {
//如果是概率图
case GridType::PROBABILITY_GRID:
candidate.score = ComputeCandidateScore(
static_cast<const ProbabilityGrid&>(grid),
discrete_scans[candidate.scan_index], candidate.x_index_offset,
candidate.y_index_offset);
break;
//如果是TSDF地图
case GridType::TSDF:
candidate.score = ComputeCandidateScore(
static_cast<const TSDF2D&>(grid),
discrete_scans[candidate.scan_index], candidate.x_index_offset,
candidate.y_index_offset);
break;
}
candidate.score *=
std::exp(-common::Pow2(std::hypot(candidate.x, candidate.y) *
options_.translation_delta_cost_weight() +
std::abs(candidate.orientation) *
options_.rotation_delta_cost_weight()));
}
}
float ComputeCandidateScore(const ProbabilityGrid& probability_grid,
const DiscreteScan2D& discrete_scan,
int x_index_offset, int y_index_offset) {
float candidate_score = 0.f;
for (const Eigen::Array2i& xy_index : discrete_scan) {
const Eigen::Array2i proposed_xy_index(xy_index.x() + x_index_offset,
xy_index.y() + y_index_offset);
const float probability =
probability_grid.GetProbability(proposed_xy_index);
candidate_score += probability;
}
candidate_score /= static_cast<float>(discrete_scan.size());
CHECK_GT(candidate_score, 0.f);
return candidate_score;
}
discrete_scans是经过角分辨率旋转之后的点云组,直接塞到ComputeCandidateScore里面进行评分,将二维索引x_index_offset和y_index_offset转化为一维索引proposed_xy_index,直接根据概率栅格地图拿到这个索引对应的概率值
五、后话
简单分析了一下cartographer用的点云地图和栅格地图,算是把LocalTrajectoryBuilder这部分正式结束了,从传感器数据的获取到定位部分的接口和数据处理,帧间匹配,地图插入都分析了一遍,局部定位的工作差不多就到这里了,后面的话是全局定位,回环等等的工作,会在后面的文章进行分析