cartographer的栅格地图更新

cartographer的栅格地图更新

栅格地图的更新就是把激光雷达数据插入到栅格地图里面
首先找到插入激光雷达到栅格地图的位置,

//Local_trajectory_builder_2d.cc
LocalTrajectoryBuilder2D::AddRangeData(
    const std::string& sensor_id,
    const sensor::TimedPointCloudData& unsynchronized_data) {
.......

     return AddAccumulatedRangeData(
        time,
        
        // 将点云变换到local原点处, 且姿态为0
        //就是把点云转到local原点处,且把点云

        //把激光点云转到base_link原点处,也就是从sensor_bridge传入的状态
        //然后用预测的姿态,把点云转到该姿态下
        TransformToGravityAlignedFrameAndFilter(
            gravity_alignment.cast<float>() * range_data_poses.back().inverse(),
            accumulated_range_data_),
        gravity_alignment, sensor_duration);
 }

Local_trajectory_builder_2d.cc中调用InsertIntoSubmap()函数,将激光雷达数据插入到submap

  // 将校正后的雷达数据写入submap
  //Local_trajectory_builder_2d.cc
  std::unique_ptr<InsertionResult> insertion_result = InsertIntoSubmap(
      time, range_data_in_local, filtered_gravity_aligned_point_cloud,
      pose_estimate, gravity_alignment.rotation());

InsertIntoSubmap()函数定义在Local_trajectory_builder_2d.cc中的LocalTrajectoryBuilder2D类,该函数是LocalTrajectoryBuilder2D与2D栅格地图的桥梁

  std::unique_ptr<InsertionResult> InsertIntoSubmap(
      common::Time time, const sensor::RangeData& range_data_in_local,
      const sensor::PointCloud& filtered_gravity_aligned_point_cloud,
      const transform::Rigid3d& pose_estimate,
      const Eigen::Quaterniond& gravity_alignment);

InsertIntoSubmap()具体实现在Local_trajectory_builder_2d.cc 中,核心功能是调用了InsertRangeData()函数

//Local_trajectory_builder_2d.cc 
  // 将点云数据写入到submap中
  //这个点云已经在local坐标系下 submaps的原点就是local坐标系的原点
  std::vector<std::shared_ptr<const Submap2D>> insertion_submaps =
      active_submaps_.InsertRangeData(range_data_in_local);

InsertRangeData()函数具体实现在sumap_2d.cc,核心功能是调用了 submap->InsertRangeData(range_data, range_data_inserter_.get());

//sumap_2d.cc
// 将点云数据写入到submap中
std::vector<std::shared_ptr<const Submap2D>> ActiveSubmaps2D::InsertRangeData(
    const sensor::RangeData& range_data) {
  // 如果第二个子图插入节点的数据等于num_range_data时,就新建个子图
  // 因为这时第一个子图应该已经处于完成状态了
  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());
  }
  // 第一个子图的节点数量等于2倍的num_range_data时,第二个子图节点数量应该等于num_range_data
  if (submaps_.front()->num_range_data() == 2 * options_.num_range_data()) {
    submaps_.front()->Finish();
  }
  return submaps();
}

submap与submaps_类型一致,submaps_定义在sumap_2d.cc中 的ActiveSubmaps2D类中,

//sumap_2d.cc
std::vector<std::shared_ptr<Submap2D>> submaps_;

同时在ActiveSubmaps2D类中,定义了InsertRangeData()函数,

//sumap_2d.cc
  std::vector<std::shared_ptr<const Submap2D>> InsertRangeData(
      const sensor::RangeData& range_data);

InsertRangeData()函数,具体实现,核心的是 range_data_inserter->Insert()函数

//sumap_2d.cc
// 将雷达数据写到栅格地图中
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());
  // 插入到地图中的雷达数据的个数加1
  set_num_range_data(num_range_data() + 1);
}

跳转到Insert()函数的具体实现,在probability_grid_range_data_inserter_2d.cc中,在ProbabilityGridRangeDataInserter2D类中定义

//probability_grid_range_data_inserter_2d.cc
void ProbabilityGridRangeDataInserter2D::Insert(
    const sensor::RangeData& range_data, GridInterface* const grid) const {
  ProbabilityGrid* const probability_grid = static_cast<ProbabilityGrid*>(grid);
  CHECK(probability_grid != nullptr);
  // By not finishing the update after hits are inserted, we give hits priority
  // (i.e. no hits will be ignored because of a miss in the same cell).
  // param: insert_free_space
  CastRays(range_data, hit_table_, miss_table_, options_.insert_free_space(),
           probability_grid);
  probability_grid->FinishUpdate();
}

分析一下ProbabilityGridRangeDataInserter2D类,ProbabilityGridRangeDataInserter2D继承RangeDataInserterInterface 类

//probability_grid_range_data_inserter_2d.h
class ProbabilityGridRangeDataInserter2D : public RangeDataInserterInterface {
 public:
  explicit ProbabilityGridRangeDataInserter2D(
      const proto::ProbabilityGridRangeDataInserterOptions2D& options);

  ProbabilityGridRangeDataInserter2D(
      const ProbabilityGridRangeDataInserter2D&) = delete;
  ProbabilityGridRangeDataInserter2D& operator=(
      const ProbabilityGridRangeDataInserter2D&) = delete;

  // Inserts 'range_data' into 'probability_grid'.
  virtual void Insert(const sensor::RangeData& range_data,
                      GridInterface* grid) const override;

 private:
  const proto::ProbabilityGridRangeDataInserterOptions2D options_;
  const std::vector<uint16> hit_table_;
  const std::vector<uint16> miss_table_;
};

分析一下ProbabilityGridRangeDataInserter2D类的构造函数,
主要是创建查找表hit_table_与miss_table_,调用了ComputeLookupTableToApplyCorrespondenceCostOdds()函数

//probability_grid_range_data_inserter_2d.cc
// 写入器的构造, 新建了2个查找表
ProbabilityGridRangeDataInserter2D::ProbabilityGridRangeDataInserter2D(
    const proto::ProbabilityGridRangeDataInserterOptions2D& options)
    : options_(options),
      // 生成更新占用栅格时的查找表 // param: hit_probability
      hit_table_(ComputeLookupTableToApplyCorrespondenceCostOdds(
          Odds(options.hit_probability()))),    // 0.55
      // 生成更新空闲栅格时的查找表 // param: miss_probability
      miss_table_(ComputeLookupTableToApplyCorrespondenceCostOdds(
          Odds(options.miss_probability()))) {} // 0.49

ComputeLookupTableToApplyCorrespondenceCostOdds()函数在probability_values.cc中
该函数返回更新查找表,在分析ComputeLookupTableToApplyCorrespondenceCostOdds()函数之前,先分析一下Odds()函数,Odds()函数定义在probability_values.h中,公式
在这里插入图片描述
cell被占用概率越大,odds越大,因为p取值大于0.5,所以odds是大于1
options.hit_probability()为0.55传进来的话, 返回 0.55 / 1-0.55
通过函数Odds转换为更新系数Chit和Cmiss。hit_probability的设置不能小于0.5,miss_probability的设置不能大于0.5
代码如下

//probability_values.h
 // 概率值转Odds
inline float Odds(float probability) {
  return probability / (1.f - probability);
}

在这里插入图片描述
因为odds是大于1,所以Mnew(x)越来越增加

通过odd值转概率,

//probability_values.h
// odd值转概率
inline float ProbabilityFromOdds(const float odds) {
  return odds / (odds + 1.f);
}

小结:使用options.hit_probability()大于0.5,对cell进行更新,则cell对应的占用概率值越来越大
使用options.miss_probability()小于0.5,对cell进行更新,则cell对应的占用概率值越来越小

回到ComputeLookupTableToApplyCorrespondenceCostOdds()函数

//probability_values.cc
// 将栅格是未知状态与odds状态下, 将更新时的所有可能结果预先计算出来
std::vector<uint16> ComputeLookupTableToApplyCorrespondenceCostOdds(
    float odds) {
  std::vector<uint16> result;
  result.reserve(kValueCount); // 32768

  // 当前cell是unknown情况  直接把odds转成value存进来
  result.push_back(CorrespondenceCostToValue(ProbabilityToCorrespondenceCost(
                       ProbabilityFromOdds(odds))) +
                   kUpdateMarker); // 加上kUpdateMarker作为一个标志, 代表这个栅格已经被更新了
  // 计算更新时 从1到32768的所有可能的 更新后的结果 
  for (int cell = 1; cell != kValueCount; ++cell) {
    result.push_back(
        CorrespondenceCostToValue(
            ProbabilityToCorrespondenceCost(ProbabilityFromOdds(
                odds * Odds(CorrespondenceCostToProbability(
                           (*kValueToCorrespondenceCost)[cell]))))) +
        kUpdateMarker);
  }
  return result;
}

接下来一点点分析
创建一个result转换表
预留32768个空间
ProbabilityFromOdds(odds) odds 转成概率值, 也就是options.hit_probability()为0.55传进来的话,odd值为0.55/1-0.55, 该函数返回0.55

//probability_values.h
// odd值转概率   
inline float ProbabilityFromOdds(const float odds) {
  return odds / (odds + 1.f);
}

取反 hit_probability()为0.55,返回0.45

//probability_values.h
// probability与CorrespondenceCost的关系, CorrespondenceCost代表free的概率   hit_probability()为0.55,返回0.45
inline float ProbabilityToCorrespondenceCost(const float probability) {
  return 1.f - probability;
}

CorrespondenceCostToValue()函数,根据概率值去查表,比如 概率0.45去查表得到 369,[0. 1 ~ 0.9 ] 对应 [1, 32767]

//probability_values.h
// 将浮点数correspondence_cost转成[1, 32767]范围内的 uint16 整数
inline uint16 CorrespondenceCostToValue(const float correspondence_cost) {
  return BoundedFloatToValue(correspondence_cost, kMinCorrespondenceCost,
                             kMaxCorrespondenceCost);
}

在probability_values.h中BoundedFloatToValue()函数,该函数就是根据概率值去查表的具体实现, 比如 概率0.45去查表得到 369(不一定对,就是那个意思),value值加上kUpdateMarker放到result

//probability_values.h
// 为了避免浮点运算, 将[0~0.9]的浮点数转成[1~32767]之间的值
inline uint16 BoundedFloatToValue(const float float_value,
                                  const float lower_bound,
                                  const float upper_bound) {
  const int value =
      common::RoundToInt(
          (common::Clamp(float_value, lower_bound, upper_bound) - lower_bound) *
          (32766.f / (upper_bound - lower_bound))) +
      1;
  // DCHECK for performance.
  DCHECK_GE(value, 1);
  DCHECK_LE(value, 32767);
  return value;
}

分析for循环

//probability_values.cc

  // 计算更新时 从1到32768的所有可能的 更新后的结果 
  for (int cell = 1; cell != kValueCount; ++cell) {
    result.push_back(
        CorrespondenceCostToValue(
            ProbabilityToCorrespondenceCost(ProbabilityFromOdds(
                odds * Odds(CorrespondenceCostToProbability(
                           (*kValueToCorrespondenceCost)[cell]))))) +
        kUpdateMarker);
  }

调用CorrespondenceCostToProbability()

//probability_values.h
inline float CorrespondenceCostToProbability(const float correspondence_cost) {
  return 1.f - correspondence_cost;
}

此处应该详细一点的,后面更新

回到Insert(),insert()是虚函数,且override, RangeDataInserterInterface* range_data_inserter这里面实现了多态,父类的指针指向子类的对象

static_cast强制类型转换GridInterface* const grid转成ProbabilityGrid*

//probability_grid_range_data_inserter_2d.cc
void ProbabilityGridRangeDataInserter2D::Insert(
    const sensor::RangeData& range_data, GridInterface* const grid) const {
  ProbabilityGrid* const probability_grid = static_cast<ProbabilityGrid*>(grid);
  CHECK(probability_grid != nullptr);
  // By not finishing the update after hits are inserted, we give hits priority
  // (i.e. no hits will be ignored because of a miss in the same cell).
  // param: insert_free_space
  CastRays(range_data, hit_table_, miss_table_, options_.insert_free_space(),
           probability_grid);
  probability_grid->FinishUpdate();
}

调用函数CastRays(),本篇博客太长了,留在下一篇继续讲解

欢迎大家一起学习交流~

每天进步一点点,时光不负有心人❤

  • 24
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值