“vector”: 不是“std”的成员_Cartographer源码阅读16—PoseGraph成员函数2

上一篇:Cartographer源码阅读15—PoseGraph的成员函数

下一篇:暂无,敬请期待


这篇文章本应继续更新PoseGraph的成员函数的解读。但是这周末出国的飞机,我得收拾行李,好巧不巧又赶上这阵子重感冒,所以这周暂时停止更新。等我恢复元气了继续更新。谢谢大家的关注!——20181129
刚到国外,这几周忙着办理各种手续并熟悉环境之中,所以没能按时给大家更新。没想到这段时间专栏的关注人数已经增长了一倍,非常欣慰。也有一些同学在催更了,正好这里马上要到圣诞节和元旦假期了,所以先立一个flag,趁这个假期继续更新。——20181221

在上一篇文章中我们已经解读了PoseGraph2D::AddNode函数以及该函数所依赖的AddTrajectoryIfNeeded、AddWorkItem、ComputeConstraintsForNode,以及ComputeConstraintsForNode所依赖的InitializeGlobalSubmapPoses、ComputeConstraint和ComputeConstraintsForOldNodes (该函数调用了ComputeConstraint)等函数。

我们接着看下面两个函数:DispatchOptimization和GetLatestNodeTime。DispatchOptimization在ComputeConstraintsForNode中被调用,而GetLatestNodeTime在ComputeConstraint中被调用。

6. DispatchOptimization()

// Dispatch的含义是分拨、派遣、分拣。这里的主要是将run_loop_closure_置为true,
// 从而使得执行优化操作的代码块知道应该进行全局优化了。
void PoseGraph2D::DispatchOptimization() {
  run_loop_closure_ = true;
  // If there is a 'work_queue_' already, some other thread will take care.
  //如果工作队列还为空指针,需要创建一个工作队列
  if (work_queue_ == nullptr) {
  	//创建一个工作队列的指针
    work_queue_ = common::make_unique<std::deque<std::function<void()>>>();
    //为该工作队列绑定处理函数,即HandleWorkQueue
    constraint_builder_.WhenDone(
        std::bind(&PoseGraph2D::HandleWorkQueue, this, std::placeholders::_1));
  }
}

7. GetLatestNodeTime

// 获取该node和该submap中的node中较新的时间
common::Time PoseGraph2D::GetLatestNodeTime(const NodeId& node_id,
                                            const SubmapId& submap_id) const {
  // 获取指定id的时间
  common::Time time = trajectory_nodes_.at(node_id).constant_data->time;
  // 获取指定id的submap的数据
  const InternalSubmapData& submap_data = submap_data_.at(submap_id);
  if (!submap_data.node_ids.empty()) {//如果该submap相关的node_ids不为空。
  	// 获取指定id的submap相关的node_ids列表中的最后一个元素
    const NodeId last_submap_node_id =
        *submap_data_.at(submap_id).node_ids.rbegin();//c.rbegin() 返回一个逆序迭代器,它指向容器c的最后一个元素
    // 把时间更新为节点建立时间与submap中最后一个节点时间中较晚的那个
    time = std::max(
        time, trajectory_nodes_.at(last_submap_node_id).constant_data->time);
  }
  return time;
}

之后我们依据http://pose_graph_2d.cc中的顺序来依次解读一下各个函数的功能,基本上完成这些函数的解读后我们对Cartographer源码的解读也就基本告成。

8. AddImuData、AddOdometryData、AddFixedFramePoseData、AddLandmarkData

这几个函数的主要作用是在有新的传感器数据传入时进行处理:

void PoseGraph2D::AddImuData(const int trajectory_id,
                             const sensor::ImuData& imu_data) {
  common::MutexLocker locker(&mutex_);//设置互斥锁
  AddWorkItem([=]() REQUIRES(mutex_) {
    optimization_problem_->AddImuData(trajectory_id, imu_data);
  });
}

void PoseGraph2D::AddOdometryData(const int trajectory_id,
                                  const sensor::OdometryData& odometry_data) {
  common::MutexLocker locker(&mutex_);
  AddWorkItem([=]() REQUIRES(mutex_) {
    optimization_problem_->AddOdometryData(trajectory_id, odometry_data);
  });
}

可以看出,IMU和里程计数据有新的测量结果后是调用optimization_problem_进行处理,该函数的处理目标也是优化机器人行进过程中的Node处的位姿以及Submap的绝对位姿,使其更好地符合观测结果。具体的处理方法等我们深入解读OptimizationProblem时再研究。

void PoseGraph2D::AddFixedFramePoseData(
    const int trajectory_id,
    const sensor::FixedFramePoseData& fixed_frame_pose_data) {
  LOG(FATAL) << "Not yet implemented for 2D.";
}

AddFixedFramePoseData是处理GPS等能够测量机器人完整位姿的传感器输入,目前2D情况下并没有用到该函数。

// 添加Landmark数据
void PoseGraph2D::AddLandmarkData(int trajectory_id,
                                  const sensor::LandmarkData& landmark_data)
    EXCLUDES(mutex_) {
  common::MutexLocker locker(&mutex_);
  AddWorkItem([=]() REQUIRES(mutex_) {
    for (const auto& observation : landmark_data.landmark_observations) {//遍历传入的参数中的landmark_data
      //根据数据生成一个LandmarkNode::LandmarkObservation,压入landmark_nodes_这个容器中
      landmark_nodes_[observation.id].landmark_observations.emplace_back(
          LandmarkNode::LandmarkObservation{
              trajectory_id, landmark_data.time,
              observation.landmark_to_tracking_transform,
              observation.translation_weight, observation.rotation_weight});
    }
  });
}

AddLandmarkData则是处理有Landmark的Observation的情况。Landmark的处理与IMU和里程计数据的处理不太一样。对于IMU和里程计数据而言,他们的局部信息是比较可靠的,所以在每次有新的观察结果时立刻交给OptimizationProblem进行处理。但对于Landmark的数据而言,cartographer认为Landmark的绝对位姿是已知的,而机器人在行进过程中可能会多次观察landmark,因此,landmark并不是有观测后就立刻处理,而是在进行Loop Closure之前才调用,在调用时可能观察到多次,遍历这些观测数据,并把他们放到landmark_nodes_这个容器中,然后在Loop Closure时一起优化。

由于我已经开始有新的科研任务了,所以对cartographer源码的解读只能利用节假日或其他休息时间进行更新。与之前全职工作时相比,更新速度肯定会下降很多,请大家多多见谅。
20190929:继续更新PoseGraph2D的源码解读。发现长时间不看,很多内容都已经忘了,还得重头开始看。

9. UpdateTrajectoryConnectivity(const Constraint& constraint)

UpdateTrajectoryConnectivity(const Constraint& constraint)是在每有一条新的约束constraint加入时,用来更新不同trajectory之间的connectivity的关系。所以,每当有新的约束加入时都会调用该函数更新地图中的trajectory之间的联系。

// 在每有一条新的约束constraint加入时,用来更新不同trajectory之间的connectivity的关系。
void PoseGraph2D::UpdateTrajectoryConnectivity(const Constraint& constraint) {
  //检查constraint的标签类型是否是INTER_SUBMAP类型.
  CHECK_EQ(constraint.tag, Constraint::INTER_SUBMAP);
  //根据constraint中产生约束的一对儿node_id和submap_id获取两者上次建立约束的最晚时间
  const common::Time time =
      GetLatestNodeTime(constraint.node_id, constraint.submap_id);
  //对两者所在trajectory_id建立connectivity
  trajectory_connectivity_state_.Connect(constraint.node_id.trajectory_id,
                                         constraint.submap_id.trajectory_id,
                                         time);
}

10. HandleWorkQueue(const constraints::ConstraintBuilder2D::Result& result)

该函数的输入是由ConstraintBuilder2D建立约束之后的结果。所以,该函数的主要工作就是处理这些建立起来的约束关系。这里我们也可以对Cartographer的全局优化有个大致的框架:PoseGraph2D中存在一个constraints::ConstraintBuilder2D类型的变量constraint_builder_, 它的主要任务就是在新的Node加入时建立起该Node与Submap之间的约束。把这些约束放到一个向量std::vector<Constraint> constraints_中。在满足一定条件时,由OptimizationProblem2D类型的变量optimization_problem_来进行全局优化。

// 输入是由ConstraintBuilder2D建立起来的约束向量
void PoseGraph2D::HandleWorkQueue(
    const constraints::ConstraintBuilder2D::Result& result) {
  {
    // 在处理数据时加上互斥锁,防止出现数据访问错误
    common::MutexLocker locker(&mutex_);
    // 把result中的所有约束加入到constraints_向量的末尾处
    constraints_.insert(constraints_.end(), result.begin(), result.end());
  }
  // 执行优化。这里调用了PoseGraph2D的另一个成员函数RunOptimization()来处理
  RunOptimization();

  // 如果已经设置了全局优化的回调函数。进行一些参数设置后调用该函数。
  if (global_slam_optimization_callback_) {
    // 设置回调函数的两个参数
    std::map<int, NodeId> trajectory_id_to_last_optimized_node_id;
    std::map<int, SubmapId> trajectory_id_to_last_optimized_submap_id;
    {
      common::MutexLocker locker(&mutex_);
      const auto& submap_data = optimization_problem_->submap_data();
      const auto& node_data = optimization_problem_->node_data();
      // 把optimization_problem_中的node和submap的数据拷贝到两个参数中
      for (const int trajectory_id : node_data.trajectory_ids()) {
        trajectory_id_to_last_optimized_node_id[trajectory_id] =
            std::prev(node_data.EndOfTrajectory(trajectory_id))->id;
        trajectory_id_to_last_optimized_submap_id[trajectory_id] =
            std::prev(submap_data.EndOfTrajectory(trajectory_id))->id;
      }
    }
    // 调用该回调函数进行处理。猜测应该是更新一下地图结果之类的操作。
    global_slam_optimization_callback_(
        trajectory_id_to_last_optimized_submap_id,
        trajectory_id_to_last_optimized_node_id);
  }

  // 更新trajectory之间的connectivity信息
  common::MutexLocker locker(&mutex_);
  for (const Constraint& constraint : result) {
    UpdateTrajectoryConnectivity(constraint);
  }
  // 调用trimmers_中每一个trimmer的Trim函数进行处理。但还是不清楚这个trimming的含义是什么。
  TrimmingHandle trimming_handle(this);
  for (auto& trimmer : trimmers_) {
    trimmer->Trim(&trimming_handle);
  }
  // Trim之后把他们从trimmers_这个向量中清除,trimmers_将重新记录等待新一轮的Loop Closure过程中产生的数据
  trimmers_.erase(
      std::remove_if(trimmers_.begin(), trimmers_.end(),
                     [](std::unique_ptr<PoseGraphTrimmer>& trimmer) {
                       return trimmer->IsFinished();
                     }),
      trimmers_.end());

  // 重新把“上次Loop Closure之后新加入的节点数”置为0,run_loop_closure置为false
  num_nodes_since_last_loop_closure_ = 0;
  run_loop_closure_ = false;
  while (!run_loop_closure_) {
    // 如果工作队列为空,重置工作队列,返回
    if (work_queue_->empty()) {
      work_queue_.reset();
      return;
    }
    // 不然的话不停取出队列最前端的任务进行处理,直至所有任务都处理完成
    work_queue_->front()();
    work_queue_->pop_front();
  }
  LOG(INFO) << "Remaining work items in queue: " << work_queue_->size();
  // We have to optimize again.
  // 调用constraint_builder_的WhenDone函数进一步处理
  constraint_builder_.WhenDone(
      std::bind(&PoseGraph2D::HandleWorkQueue, this, std::placeholders::_1));
}

上一篇:Cartographer源码阅读15—PoseGraph的成员函数

下一篇:暂无,敬请期待

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值