Cartographer源码阅读3D-前端-Submap创建
submap创建
接口函数:
std::unique_ptr<LocalTrajectoryBuilder3D::InsertionResult>
LocalTrajectoryBuilder3D::InsertIntoSubmap(
const common::Time time,
const sensor::RangeData& filtered_range_data_in_local,
const sensor::RangeData& filtered_range_data_in_tracking,
const sensor::PointCloud& high_resolution_point_cloud_in_tracking,
const sensor::PointCloud& low_resolution_point_cloud_in_tracking,
const transform::Rigid3d& pose_estimate,
const Eigen::Quaterniond& gravity_alignment) {
// 稀疏化操作
if (motion_filter_.IsSimilar(time, pose_estimate)) {
return nullptr;
}
// Querying the active submaps must be done here before calling
// InsertRangeData() since the queried values are valid for next insertion.
std::vector<std::shared_ptr<const mapping::Submap3D>> insertion_submaps;
for (const std::shared_ptr<mapping::Submap3D>& submap :
active_submaps_.submaps()) {
insertion_submaps.push_back(submap);
}
// 插入submap
active_submaps_.InsertRangeData(filtered_range_data_in_local,
gravity_alignment);
// 与2D不一样的地方,计算Node的直方图
const Eigen::VectorXf rotational_scan_matcher_histogram =
scan_matching::RotationalScanMatcher::ComputeHistogram(
// 只旋转到重力对齐
sensor::TransformPointCloud(
filtered_range_data_in_tracking.returns,
transform::Rigid3f::Rotation(gravity_alignment.cast<float>())),
options_.rotational_histogram_size());
return common::make_unique<InsertionResult>(
InsertionResult{std::make_shared<const mapping::TrajectoryNode::Data>(
mapping::TrajectoryNode::Data{
time,
gravity_alignment,
{}, // 'filtered_point_cloud' is only used in 2D.
high_resolution_point_cloud_in_tracking,
low_resolution_point_cloud_in_tracking,
rotational_scan_matcher_histogram,
pose_estimate}),
std::move(insertion_submaps)});
}
计算点云直方图
只是调用了计算点云直方图的函数,其余的并未调用:
class RotationalScanMatcher {
public:
// Computes the histogram for a gravity aligned 'point_cloud'.
static Eigen::VectorXf ComputeHistogram(const sensor::PointCloud& point_cloud,
int histogram_size);
// Creates a matcher from the given histograms rotated by the given angles.
// The angles should be chosen to bring the histograms into approximately the
// same frame.
// pair的第一项为submap包含的每个Node的直方图,第二项为该Node的全局位姿的yaw角
explicit RotationalScanMatcher(
const std::vector<std::pair<Eigen::VectorXf, float>>&
histograms_at_angles);
// Scores how well 'histogram' rotated by 'initial_angle' can be understood as
// further rotated by certain 'angles' relative to the 'nodes'. Each angle
// results in a score between 0 (worst) and 1 (best).
std::vector<float> Match(const Eigen::VectorXf& histogram,
float initial_angle,
const std::vector<float>& angles) const;
private:
// submap的直方图是每个node直方图之和
Eigen::VectorXf histogram_;
};
// 计算直方图:其中输入的点云是重力对齐后的,直方图的size是设定的
Eigen::VectorXf RotationalScanMatcher::ComputeHistogram(
const sensor::PointCloud& point_cloud, const int histogram_size) {
Eigen::VectorXf histogram = Eigen::VectorXf::Zero(histogram_size);
std::map<int, sensor::PointCloud> slices;
// 沿着重力方向对点云数据切片,切片的厚度为kSliceHeight(0.2m)
for (const Eigen::Vector3f& point : point_cloud) {
slices[common::RoundToInt(point.z() / kSliceHeight)].push_back(point);
}
for (const auto& slice : slices) {
// 先对点云排序,然后计算每个切片的直方图
AddPointCloudSliceToHistogram(SortSlice(slice.second), &histogram);
}
return histogram;
}
// 点云排序
sensor::PointCloud SortSlice(const sensor::PointCloud& slice) {
// 角度从小到大排序
struct SortableAnglePointPair {
bool operator<(const SortableAnglePointPair& rhs) const {
return angle < rhs.angle;
}
float angle;
Eigen::Vector3f point;
};
// 计算切片质心
const Eigen::Vector3f centroid = ComputeCentroid(slice);
std::vector<SortableAnglePointPair> by_angle;
by_angle.reserve(slice.size());
// 遍历所有的点
for (const Eigen::Vector3f& point : slice) {
// 忽略z方向上的偏差,只计算yaw角
const Eigen::Vector2f delta = (point - centroid).head<2>();
// 如果点距离质心太近,则不考虑该点:kMinDistance为0.2m
if (delta.norm() < kMinDistance) {
continue;
}
// 将该点及yaw角放入vector
by_angle.push_back(SortableAnglePointPair{common::atan2(delta), point});
}
// vector安装角度排序,从小到大
std::sort(by_angle.begin(), by_angle.end());
sensor::PointCloud result;
// 取出排序后的点放到点云中
for (const auto& pair : by_angle) {
result.push_back(pair.point);
}
return result;
}
// 计算切片的质心
Eigen::Vector3f ComputeCentroid(const sensor::PointCloud& slice) {
CHECK(!slice.empty());
Eigen::Vector3f sum = Eigen::Vector3f::Zero();
for (const Eigen::Vector3f& point : slice) {
sum += point;
}
return sum / static_cast<float>(slice.size());
}
// 排序后的点云生成直方图
void AddPointCloudSliceToHistogram(const sensor::PointCloud& slice,
Eigen::VectorXf* const histogram) {
// 有可能为空,因为在排序的时候已经丢了一些点
if (slice.empty()) {
return;
}
// We compute the angle of the ray from a point to the centroid of the whole
// point cloud. If it is orthogonal to the angle we compute between points, we
// will add the angle between points to the histogram with the maximum weight.
// This is to reject, e.g., the angles observed on the ceiling and floor.
// 计算切片的质心c
const Eigen::Vector3f centroid = ComputeCentroid(slice);
// 取出第一个点,yaw角最小的点a
Eigen::Vector3f last_point = slice.front();
for (const Eigen::Vector3f& point : slice) {
// 当前点为b
// 计算向量ab
const Eigen::Vector2f delta = (point - last_point).head<2>();
// 计算向量cb
const Eigen::Vector2f direction = (point - centroid).head<2>();
// 计算向量ab之间的距离
const float distance = delta.norm();
// 距离ab和bc都不能太小,太小相当于同一个点
if (distance < kMinDistance || direction.norm() < kMinDistance) {
continue;
}
// 如果当前点与参考点(第一个点)之间的距离太大超过阈值(kMaxDistance:0.9m),则重置参考点为当前点,保证参考点和当前点在同一个邻域内
if (distance > kMaxDistance) {
last_point = point;
continue;
}
// 计算当前点和参考点之间的yaw角,用作寻找该角度所属的区间
const float angle = common::atan2(delta);
// 计算权重,用作赋值该角度所属区间的权重
// delta.normalized().dot(direction.normalized()计算两个向量的点乘得到cos(theta),两个向量的夹角越小,该值越大,即a和b点几乎在同一条直线上,在实际上,该种情况不应该发生,应该是点云采集sensor的噪声造成的,或者激光雷达扫描到玻璃、地面等造成的,该点的权重应该尽量低,因此,权重value=1-cos(theta)
const float value = std::max(
0.f, 1.f - std::abs(delta.normalized().dot(direction.normalized())));
AddValueToHistogram(angle, value, histogram);
}
}
// 找到该点所属区间,并更新区间权重
void AddValueToHistogram(float angle, const float value,
Eigen::VectorXf* histogram) {
// Map the angle to [0, pi), i.e. a vector and its inverse are considered to
// represent the same angle.
// 归一化到[0, pi)区间
while (angle > static_cast<float>(M_PI)) {
angle -= static_cast<float>(M_PI);
}
while (angle < 0.f) {
angle += static_cast<float>(M_PI);
}
// 该角度所属的区间为(histogram->size() * zero_to_one-0.5),减去0.5是为了向下取整
const float zero_to_one = angle / static_cast<float>(M_PI);
const int bucket = common::Clamp<int>(
common::RoundToInt(histogram->size() * zero_to_one - 0.5f), 0,
histogram->size() - 1);
// 所属区间权重加上该点权重
(*histogram)(bucket) += value;
}
插入submap
与2D基本差不多,区别在于:
(1)Grid为HybridGrid,一个submap3D里,维护了高分辨率和低分辨率的两个网格。
(2)插入器RangeDataInserter3D
proto::SubmapsOptions3D CreateSubmapsOptions3D(
common::LuaParameterDictionary* parameter_dictionary);
class Submap3D : public Submap {
public:
Submap3D(float high_resolution, float low_resolution,
const transform::Rigid3d& local_submap_pose);
explicit Submap3D(const proto::Submap3D& proto);
void ToProto(proto::Submap* proto,
bool include_probability_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 HybridGrid& high_resolution_hybrid_grid() const {
return *high_resolution_hybrid_grid_;
}
const HybridGrid& low_resolution_hybrid_grid() const {
return *low_resolution_hybrid_grid_;
}
// 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 RangeDataInserter3D& range_data_inserter,
int high_resolution_max_range);
void Finish();
private:
std::unique_ptr<HybridGrid> high_resolution_hybrid_grid_;
std::unique_ptr<HybridGrid> low_resolution_hybrid_grid_;
};
// Except during initialization when only a single submap exists, there are
// always two submaps into which range data is inserted: an old submap that is
// used for matching, and a new one, which will be used for matching next, that
// is being initialized.
//
// Once a certain number of range data have been inserted, the new submap is
// considered initialized: the old submap is no longer changed, the "new" submap
// is now the "old" submap and is used for scan-to-map matching. Moreover, a
// "new" submap gets created. The "old" submap is forgotten by this object.
class ActiveSubmaps3D {
public:
explicit ActiveSubmaps3D(const proto::SubmapsOptions3D& options);
ActiveSubmaps3D(const ActiveSubmaps3D&) = delete;
ActiveSubmaps3D& operator=(const ActiveSubmaps3D&) = delete;
// Returns the index of the newest initialized Submap which can be
// used for scan-to-map matching.
int matching_index() const;
// Inserts 'range_data' into the Submap collection. 'gravity_alignment' is
// used for the orientation of new submaps so that the z axis approximately
// aligns with gravity.
void InsertRangeData(const sensor::RangeData& range_data,
const Eigen::Quaterniond& gravity_alignment);
std::vector<std::shared_ptr<Submap3D>> submaps() const;
private:
void AddSubmap(const transform::Rigid3d& local_submap_pose);
const proto::SubmapsOptions3D options_;
int matching_submap_index_ = 0;
std::vector<std::shared_ptr<Submap3D>> submaps_;
RangeDataInserter3D range_data_inserter_;
};
// 入口
void ActiveSubmaps3D::InsertRangeData(
const sensor::RangeData& range_data,
const Eigen::Quaterniond& gravity_alignment) {
for (auto& submap : submaps_) {
// submap插入点云
submap->InsertRangeData(range_data, range_data_inserter_,
options_.high_resolution_max_range());
}
if (submaps_.back()->num_range_data() == options_.num_range_data()) {
// 生成submap,submap的local pose的平移是第一帧点云的origin,旋转时重力方向
AddSubmap(transform::Rigid3d(range_data.origin.cast<double>(),
gravity_alignment));
}
}
// submap插入点云
void Submap3D::InsertRangeData(const sensor::RangeData& range_data,
const RangeDataInserter3D& range_data_inserter,
const int high_resolution_max_range) {
CHECK(!finished());
// 这一块和2D不一样,在生成submap时,先将点云转换到第一帧的坐标系下,即local pose下
const sensor::RangeData transformed_range_data = sensor::TransformRangeData(
range_data, local_pose().inverse().cast<float>());
// 将submap坐标系下的点云进行距离滤波,插入高分辨率网格中
range_data_inserter.Insert(
FilterRangeDataByMaxRange(transformed_range_data,
high_resolution_max_range),
high_resolution_hybrid_grid_.get());
// 不滤波,直接插入低分辨率的网格中
range_data_inserter.Insert(transformed_range_data,
low_resolution_hybrid_grid_.get());
set_num_range_data(num_range_data() + 1);
}
// 距离滤波,去掉超过范围的点
sensor::RangeData FilterRangeDataByMaxRange(const sensor::RangeData& range_data,
const float max_range) {
sensor::RangeData result{range_data.origin, {}, {}};
for (const Eigen::Vector3f& hit : range_data.returns) {
if ((hit - range_data.origin).norm() <= max_range) {
result.returns.push_back(hit);
}
}
return result;
}
插入器:RangeDataInserter3D
和2D不同的是,更新的是odd,而不是costodd,即更新的是hit概率,而不是miss概率。
插入的时候,也不是调用的RayCasting方法,更简单粗暴,直接计算 光线的向量,沿着向量搜索经过的网格,并更新经过的网格的概率。
class RangeDataInserter3D {
public:
explicit RangeDataInserter3D(
const proto::RangeDataInserterOptions3D& options);
RangeDataInserter3D(const RangeDataInserter3D&) = delete;
RangeDataInserter3D& operator=(const RangeDataInserter3D&) = delete;
// Inserts 'range_data' into 'hybrid_grid'.
void Insert(const sensor::RangeData& range_data,
HybridGrid* hybrid_grid) const;
private:
const proto::RangeDataInserterOptions3D options_;
const std::vector<uint16> hit_table_;
const std::vector<uint16> miss_table_;
};
// 在构造函数里同样计算了两个表hit_table和miss_table,与2D不一样的是,该表里存放的是点的hit概率,而2D算的是free概率
RangeDataInserter3D::RangeDataInserter3D(
const proto::RangeDataInserterOptions3D& options)
: options_(options),
hit_table_(
ComputeLookupTableToApplyOdds(Odds(options_.hit_probability()))),
miss_table_(
ComputeLookupTableToApplyOdds(Odds(options_.miss_probability()))) {}
// 插入点云数据
void RangeDataInserter3D::Insert(const sensor::RangeData& range_data,
HybridGrid* hybrid_grid) const {
CHECK_NOTNULL(hybrid_grid);
for (const Eigen::Vector3f& hit : range_data.returns) {
// 找到Index
const Eigen::Array3i hit_cell = hybrid_grid->GetCellIndex(hit);
// 更新cell的hit概率
hybrid_grid->ApplyLookupTable(hit_cell, hit_table_);
}
// By not starting a new update after hits are inserted, we give hits priority
// (i.e. no hits will be ignored because of a miss in the same cell).
// 相当于2D的ray_casting,计算miss光线经过的cell,并更新cell的miss概率
InsertMissesIntoGrid(miss_table_, range_data.origin, range_data.returns,
hybrid_grid, options_.num_free_space_voxels());
hybrid_grid->FinishUpdate();
}
void InsertMissesIntoGrid(const std::vector<uint16>& miss_table,
const Eigen::Vector3f& origin,
const sensor::PointCloud& returns,
HybridGrid* hybrid_grid,
const int num_free_space_voxels) {
// 计算起点cell的index
const Eigen::Array3i origin_cell = hybrid_grid->GetCellIndex(origin);
for (const Eigen::Vector3f& hit : returns) {
// 计算终点cell的index
const Eigen::Array3i hit_cell = hybrid_grid->GetCellIndex(hit);
// 计算起点和终点cell的向量
const Eigen::Array3i delta = hit_cell - origin_cell;
// 计算起点到终点之间的最大值,cwiseAbs()求绝对值,maxCoeff()求最大值
const int num_samples = delta.cwiseAbs().maxCoeff();
CHECK_LT(num_samples, 1 << 15);
// 'num_samples' is the number of samples we equi-distantly place on the
// line between 'origin' and 'hit'. (including a fractional part for sub-
// voxels) It is chosen so that between two samples we change from one voxel
// to the next on the fastest changing dimension.
//
// Only the last 'num_free_space_voxels' are updated for performance.
// 沿着向量,寻找miss的cell,并更新cell的miss概率
for (int position = std::max(0, num_samples - num_free_space_voxels);
position < num_samples; ++position) {
const Eigen::Array3i miss_cell =
origin_cell + delta * position / num_samples;
hybrid_grid->ApplyLookupTable(miss_cell, miss_table);
}
}
}
Hybrid Grid网格:
// Converts an 'index' with each dimension from 0 to 2^'bits' - 1 to a flat
// z-major index.
inline int ToFlatIndex(const Eigen::Array3i& index, const int bits) {
DCHECK((index >= 0).all() && (index < (1 << bits)).all()) << index;
return (((index.z() << bits) + index.y()) << bits) + index.x();
}
// Converts a flat z-major 'index' to a 3-dimensional index with each dimension
// from 0 to 2^'bits' - 1.
inline Eigen::Array3i To3DIndex(const int index, const int bits) {
DCHECK_LT(index, 1 << (3 * bits));
const int mask = (1 << bits) - 1;
return Eigen::Array3i(index & mask, (index >> bits) & mask,
(index >> bits) >> bits);
}
// A function to compare value to the default value. (Allows specializations).
template <typename TValueType>
bool IsDefaultValue(const TValueType& v) {
return v == TValueType();
}
// Specialization to compare a std::vector to the default value.
template <typename TElementType>
bool IsDefaultValue(const std::vector<TElementType>& v) {
return v.empty();
}
// 一个voxel的表示xyz或xy
template <typename TValueType, int kBits>
class FlatGrid {
public:
using ValueType = TValueType;
// Creates a new flat grid with all values being default constructed.
FlatGrid() {
for (ValueType& value : cells_) {
value = ValueType();
}
}
FlatGrid(const FlatGrid&) = delete;
FlatGrid& operator=(const FlatGrid&) = delete;
// Returns the number of voxels per dimension.
static int grid_size() { return 1 << kBits; }
// Returns the value stored at 'index', each dimension of 'index' being
// between 0 and grid_size() - 1.
ValueType value(const Eigen::Array3i& index) const {
return cells_[ToFlatIndex(index, kBits)];
}
// Returns a pointer to a value to allow changing it.
ValueType* mutable_value(const Eigen::Array3i& index) {
return &cells_[ToFlatIndex(index, kBits)];
}
// An iterator for iterating over all values not comparing equal to the
// default constructed value.
class Iterator {
public:
Iterator() : current_(nullptr), end_(nullptr) {}
explicit Iterator(const FlatGrid& flat_grid)
: current_(flat_grid.cells_.data()),
end_(flat_grid.cells_.data() + flat_grid.cells_.size()) {
while (!Done() && IsDefaultValue(*current_)) {
++current_;
}
}
void Next() {
DCHECK(!Done());
do {
++current_;
} while (!Done() && IsDefaultValue(*current_));
}
bool Done() const { return current_ == end_; }
Eigen::Array3i GetCellIndex() const {
DCHECK(!Done());
const int index = (1 << (3 * kBits)) - (end_ - current_);
return To3DIndex(index, kBits);
}
const ValueType& GetValue() const {
DCHECK(!Done());
return *current_;
}
private:
const ValueType* current_;
const ValueType* end_;
};
private:
std::array<ValueType, 1 << (3 * kBits)> cells_;
};
// 一个维度上的voxel管理
template <typename WrappedGrid, int kBits>
class NestedGrid {
public:
using ValueType = typename WrappedGrid::ValueType;
// Returns the number of voxels per dimension.
static int grid_size() { return WrappedGrid::grid_size() << kBits; }
// Returns the value stored at 'index', each dimension of 'index' being
// between 0 and grid_size() - 1.
ValueType value(const Eigen::Array3i& index) const {
const Eigen::Array3i meta_index = GetMetaIndex(index);
const WrappedGrid* const meta_cell =
meta_cells_[ToFlatIndex(meta_index, kBits)].get();
if (meta_cell == nullptr) {
return ValueType();
}
const Eigen::Array3i inner_index =
index - meta_index * WrappedGrid::grid_size();
return meta_cell->value(inner_index);
}
// Returns a pointer to the value at 'index' to allow changing it. If
// necessary a new wrapped grid is constructed to contain that value.
ValueType* mutable_value(const Eigen::Array3i& index) {
const Eigen::Array3i meta_index = GetMetaIndex(index);
std::unique_ptr<WrappedGrid>& meta_cell =
meta_cells_[ToFlatIndex(meta_index, kBits)];
if (meta_cell == nullptr) {
meta_cell = common::make_unique<WrappedGrid>();
}
const Eigen::Array3i inner_index =
index - meta_index * WrappedGrid::grid_size();
return meta_cell->mutable_value(inner_index);
}
// An iterator for iterating over all values not comparing equal to the
// default constructed value.
class Iterator {
public:
Iterator() : current_(nullptr), end_(nullptr), nested_iterator_() {}
explicit Iterator(const NestedGrid& nested_grid)
: current_(nested_grid.meta_cells_.data()),
end_(nested_grid.meta_cells_.data() + nested_grid.meta_cells_.size()),
nested_iterator_() {
AdvanceToValidNestedIterator();
}
void Next() {
DCHECK(!Done());
nested_iterator_.Next();
if (!nested_iterator_.Done()) {
return;
}
++current_;
AdvanceToValidNestedIterator();
}
bool Done() const { return current_ == end_; }
Eigen::Array3i GetCellIndex() const {
DCHECK(!Done());
const int index = (1 << (3 * kBits)) - (end_ - current_);
return To3DIndex(index, kBits) * WrappedGrid::grid_size() +
nested_iterator_.GetCellIndex();
}
const ValueType& GetValue() const {
DCHECK(!Done());
return nested_iterator_.GetValue();
}
private:
void AdvanceToValidNestedIterator() {
for (; !Done(); ++current_) {
if (*current_ != nullptr) {
nested_iterator_ = typename WrappedGrid::Iterator(**current_);
if (!nested_iterator_.Done()) {
break;
}
}
}
}
const std::unique_ptr<WrappedGrid>* current_;
const std::unique_ptr<WrappedGrid>* end_;
typename WrappedGrid::Iterator nested_iterator_;
};
private:
// Returns the Eigen::Array3i (meta) index of the meta cell containing
// 'index'.
Eigen::Array3i GetMetaIndex(const Eigen::Array3i& index) const {
DCHECK((index >= 0).all()) << index;
const Eigen::Array3i meta_index = index / WrappedGrid::grid_size();
DCHECK((meta_index < (1 << kBits)).all()) << index;
return meta_index;
}
std::array<std::unique_ptr<WrappedGrid>, 1 << (3 * kBits)> meta_cells_;
};
// 多个维度的网格组成整个地图的表示
template <typename WrappedGrid>
class DynamicGrid {
public:
using ValueType = typename WrappedGrid::ValueType;
DynamicGrid() : bits_(1), meta_cells_(8) {}
DynamicGrid(DynamicGrid&&) = default;
DynamicGrid& operator=(DynamicGrid&&) = default;
// Returns the current number of voxels per dimension.
int grid_size() const { return WrappedGrid::grid_size() << bits_; }
// Returns the value stored at 'index'.
ValueType value(const Eigen::Array3i& index) const {
const Eigen::Array3i shifted_index = index + (grid_size() >> 1);
// The cast to unsigned is for performance to check with 3 comparisons
// shifted_index.[xyz] >= 0 and shifted_index.[xyz] < grid_size.
if ((shifted_index.cast<unsigned int>() >= grid_size()).any()) {
return ValueType();
}
const Eigen::Array3i meta_index = GetMetaIndex(shifted_index);
const WrappedGrid* const meta_cell =
meta_cells_[ToFlatIndex(meta_index, bits_)].get();
if (meta_cell == nullptr) {
return ValueType();
}
const Eigen::Array3i inner_index =
shifted_index - meta_index * WrappedGrid::grid_size();
return meta_cell->value(inner_index);
}
// Returns a pointer to the value at 'index' to allow changing it, dynamically
// growing the DynamicGrid and constructing new WrappedGrids as needed.
ValueType* mutable_value(const Eigen::Array3i& index) {
const Eigen::Array3i shifted_index = index + (grid_size() >> 1);
// The cast to unsigned is for performance to check with 3 comparisons
// shifted_index.[xyz] >= 0 and shifted_index.[xyz] < grid_size.
if ((shifted_index.cast<unsigned int>() >= grid_size()).any()) {
Grow();
return mutable_value(index);
}
const Eigen::Array3i meta_index = GetMetaIndex(shifted_index);
std::unique_ptr<WrappedGrid>& meta_cell =
meta_cells_[ToFlatIndex(meta_index, bits_)];
if (meta_cell == nullptr) {
meta_cell = common::make_unique<WrappedGrid>();
}
const Eigen::Array3i inner_index =
shifted_index - meta_index * WrappedGrid::grid_size();
return meta_cell->mutable_value(inner_index);
}
// An iterator for iterating over all values not comparing equal to the
// default constructed value.
class Iterator {
public:
explicit Iterator(const DynamicGrid& dynamic_grid)
: bits_(dynamic_grid.bits_),
current_(dynamic_grid.meta_cells_.data()),
end_(dynamic_grid.meta_cells_.data() +
dynamic_grid.meta_cells_.size()),
nested_iterator_() {
AdvanceToValidNestedIterator();
}
void Next() {
DCHECK(!Done());
nested_iterator_.Next();
if (!nested_iterator_.Done()) {
return;
}
++current_;
AdvanceToValidNestedIterator();
}
bool Done() const { return current_ == end_; }
Eigen::Array3i GetCellIndex() const {
DCHECK(!Done());
const int outer_index = (1 << (3 * bits_)) - (end_ - current_);
const Eigen::Array3i shifted_index =
To3DIndex(outer_index, bits_) * WrappedGrid::grid_size() +
nested_iterator_.GetCellIndex();
return shifted_index - ((1 << (bits_ - 1)) * WrappedGrid::grid_size());
}
const ValueType& GetValue() const {
DCHECK(!Done());
return nested_iterator_.GetValue();
}
void AdvanceToEnd() { current_ = end_; }
const std::pair<Eigen::Array3i, ValueType> operator*() const {
return std::pair<Eigen::Array3i, ValueType>(GetCellIndex(), GetValue());
}
Iterator& operator++() {
Next();
return *this;
}
bool operator!=(const Iterator& it) const {
return it.current_ != current_;
}
private:
void AdvanceToValidNestedIterator() {
for (; !Done(); ++current_) {
if (*current_ != nullptr) {
nested_iterator_ = typename WrappedGrid::Iterator(**current_);
if (!nested_iterator_.Done()) {
break;
}
}
}
}
int bits_;
const std::unique_ptr<WrappedGrid>* current_;
const std::unique_ptr<WrappedGrid>* const end_;
typename WrappedGrid::Iterator nested_iterator_;
};
private:
// Returns the Eigen::Array3i (meta) index of the meta cell containing
// 'index'.
Eigen::Array3i GetMetaIndex(const Eigen::Array3i& index) const {
DCHECK((index >= 0).all()) << index;
const Eigen::Array3i meta_index = index / WrappedGrid::grid_size();
DCHECK((meta_index < (1 << bits_)).all()) << index;
return meta_index;
}
// 每个方向2倍扩展
// Grows this grid by a factor of 2 in each of the 3 dimensions.
void Grow() {
const int new_bits = bits_ + 1;
CHECK_LE(new_bits, 8);
std::vector<std::unique_ptr<WrappedGrid>> new_meta_cells_(
8 * meta_cells_.size());
for (int z = 0; z != (1 << bits_); ++z) {
for (int y = 0; y != (1 << bits_); ++y) {
for (int x = 0; x != (1 << bits_); ++x) {
const Eigen::Array3i original_meta_index(x, y, z);
const Eigen::Array3i new_meta_index =
original_meta_index + (1 << (bits_ - 1));
new_meta_cells_[ToFlatIndex(new_meta_index, new_bits)] =
std::move(meta_cells_[ToFlatIndex(original_meta_index, bits_)]);
}
}
}
meta_cells_ = std::move(new_meta_cells_);
bits_ = new_bits;
}
int bits_;
std::vector<std::unique_ptr<WrappedGrid>> meta_cells_;
};
template <typename ValueType>
using GridBase = DynamicGrid<NestedGrid<FlatGrid<ValueType, 3>, 3>>;
template <typename ValueType>
class HybridGridBase : public GridBase<ValueType> {
public:
using Iterator = typename GridBase<ValueType>::Iterator;
// Creates a new tree-based probability grid with voxels having edge length
// 'resolution' around the origin which becomes the center of the cell at
// index (0, 0, 0).
explicit HybridGridBase(const float resolution) : resolution_(resolution) {}
float resolution() const { return resolution_; }
// Returns the index of the cell containing the 'point'. Indices are integer
// vectors identifying cells, for this the coordinates are rounded to the next
// multiple of the resolution.
Eigen::Array3i GetCellIndex(const Eigen::Vector3f& point) const {
Eigen::Array3f index = point.array() / resolution_;
return Eigen::Array3i(common::RoundToInt(index.x()),
common::RoundToInt(index.y()),
common::RoundToInt(index.z()));
}
// Returns one of the octants, (0, 0, 0), (1, 0, 0), ..., (1, 1, 1).
static Eigen::Array3i GetOctant(const int i) {
DCHECK_GE(i, 0);
DCHECK_LT(i, 8);
return Eigen::Array3i(static_cast<bool>(i & 1), static_cast<bool>(i & 2),
static_cast<bool>(i & 4));
}
// Returns the center of the cell at 'index'.
Eigen::Vector3f GetCenterOfCell(const Eigen::Array3i& index) const {
return index.matrix().cast<float>() * resolution_;
}
// Iterator functions for range-based for loops.
Iterator begin() const { return Iterator(*this); }
Iterator end() const {
Iterator it(*this);
it.AdvanceToEnd();
return it;
}
private:
// Edge length of each voxel.
const float resolution_;
};
class HybridGrid : public HybridGridBase<uint16> {
public:
explicit HybridGrid(const float resolution)
: HybridGridBase<uint16>(resolution) {}
explicit HybridGrid(const proto::HybridGrid& proto)
: HybridGrid(proto.resolution()) {
CHECK_EQ(proto.values_size(), proto.x_indices_size());
CHECK_EQ(proto.values_size(), proto.y_indices_size());
CHECK_EQ(proto.values_size(), proto.z_indices_size());
for (int i = 0; i < proto.values_size(); ++i) {
// SetProbability does some error checking for us.
SetProbability(Eigen::Vector3i(proto.x_indices(i), proto.y_indices(i),
proto.z_indices(i)),
ValueToProbability(proto.values(i)));
}
}
// Sets the probability of the cell at 'index' to the given 'probability'.
void SetProbability(const Eigen::Array3i& index, const float probability) {
*mutable_value(index) = ProbabilityToValue(probability);
}
// Finishes the update sequence.
void FinishUpdate() {
while (!update_indices_.empty()) {
DCHECK_GE(*update_indices_.back(), kUpdateMarker);
// 和2D的一样
*update_indices_.back() -= kUpdateMarker;
update_indices_.pop_back();
}
}
// Applies the 'odds' specified when calling ComputeLookupTableToApplyOdds()
// to the probability of the cell at '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'.
// 查表更新odd,不是costodd和2D不一样
bool ApplyLookupTable(const Eigen::Array3i& index,
const std::vector<uint16>& table) {
DCHECK_EQ(table.size(), kUpdateMarker);
uint16* const cell = mutable_value(index);
if (*cell >= kUpdateMarker) {
return false;
}
update_indices_.push_back(cell);
*cell = table[*cell];
DCHECK_GE(*cell, kUpdateMarker);
return true;
}
// Returns the probability of the cell with 'index'.
float GetProbability(const Eigen::Array3i& index) const {
return ValueToProbability(value(index));
}
// Returns true if the probability at the specified 'index' is known.
bool IsKnown(const Eigen::Array3i& index) const { return value(index) != 0; }
proto::HybridGrid ToProto() const {
CHECK(update_indices_.empty()) << "Serializing a grid during an update is "
"not supported. Finish the update first.";
proto::HybridGrid result;
result.set_resolution(resolution());
for (const auto it : *this) {
result.add_x_indices(it.first.x());
result.add_y_indices(it.first.y());
result.add_z_indices(it.first.z());
result.add_values(it.second);
}
return result;
}
private:
// Markers at changed cells.
std::vector<ValueType*> update_indices_;
};