接上一篇,看两次计算约束的函数 ComputeConstraint
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
| void PoseGraph2D::ComputeConstraint(const NodeId& node_id, const SubmapId& submap_id) { bool maybe_add_local_constraint = false; bool maybe_add_global_constraint = false; const TrajectoryNode::Data* constant_data; const Submap2D* submap; { absl::MutexLock locker(&mutex_); CHECK(data_.submap_data.at(submap_id).state == SubmapState::kFinished); if (!data_.submap_data.at(submap_id).submap->insertion_finished() ) { return; } const common::Time node_time = GetLatestNodeTime(node_id, submap_id); const common::Time last_connection_time = data_.trajectory_connectivity_state.LastConnectionTime( node_id.trajectory_id, submap_id.trajectory_id ); if (node_id.trajectory_id == submap_id.trajectory_id || node_time < last_connection_time + common::FromSeconds( options_.global_constraint_search_after_n_seconds()) ) { maybe_add_local_constraint = true; } else if (global_localization_samplers_[node_id.trajectory_id]->Pulse() ) { maybe_add_global_constraint = true; } constant_data = data_.trajectory_nodes.at(node_id).constant_data.get(); submap = static_cast<const Submap2D*>( data_.submap_data.at(submap_id).submap.get() ); }
|
建图模式,节点与submap在同一轨迹内 或者 存在一个最近的全局约束把节点的轨迹和子图的轨迹连接起来时,使用local search window
计算局部约束;
纯定位模式,节点与submap不在同一轨迹,使用全局搜索窗口计算约束(对整体子图进行回环检测) 。纯定位进行慢,主要就是 global_constraint_search_after_n_seconds
较大导致,迟迟不能确认maybe_add_global_constraint
为true
local约束在求解时,搜索窗口小,有初值; global约束在求解时,搜索窗口大,没有初值。 记住二者都是计算 Constraint::INTER_SUBMAP

准备计算局部和全局约束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| if (maybe_add_local_constraint) { const transform::Rigid2d initial_relative_pose = optimization_problem_->submap_data() .at(submap_id) .global_pose.inverse() * optimization_problem_->node_data().at(node_id).global_pose_2d; constraint_builder_.MaybeAddConstraint( submap_id, submap, node_id, constant_data, initial_relative_pose); } else if (maybe_add_global_constraint) constraint_builder_.MaybeAddGlobalConstraint( submap_id, submap, node_id, constant_data);
|
前端得到节点相对于世界的位姿,也可以得到某个子图的世界位姿,因此得到这个节点相对于这个子图的相对位姿,把这个位姿称为 初始位姿 1。 之所以要用世界坐标系作为桥梁,是因为子图和这个节点并不一定在在同一条轨迹坐标系中(local map坐标系)
这里主要是ConstraintBuilder2D
类的两个函数: MaybeAddConstraint
和 MaybeAddGlobalConstraint
,它们只有细微不同,前者的开头有这样两句:
1 2 3
| if (initial_relative_pose.translation().norm() > options_.max_constraint_distance() ) return;
|
然后就是其中调用的
ConstraintBuilder2D::ComputeConstraint
不同,局部约束的是
ComputeConstraint(submap_id, submap, node_id, false, constant_data, initial_relative_pose, *scan_matcher, constraint);
全局约束的是 ComputeConstraint( submap_id, submap, node_id, true, constant_data, transform::Rigid2d::Identity(), *scan_matcher, constraint);
,也就是 match full submap
局部约束
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
| void ConstraintBuilder2D::MaybeAddConstraint( const SubmapId& submap_id, const Submap2D* const submap, const NodeId& node_id, const TrajectoryNode::Data* const constant_data, const transform::Rigid2d& initial_relative_pose) { if (initial_relative_pose.translation().norm() > options_.max_constraint_distance() ) return;
if (!sampler_.Pulse()) return;
absl::MutexLock locker(&mutex_); if (when_done_) { LOG(WARNING) << "MaybeAddConstraint was called while WhenDone was scheduled."; } constraints_.emplace_back(); kQueueLengthMetric->Set(constraints_.size() ); auto* const constraint = &constraints_.back(); const auto* scan_matcher = DispatchScanMatcherConstruction(submap_id, submap->grid() ); }
|
参数max_constraint_distance
很重要,如果建图回到同一位置,但没有出现回环,可能是因为过程中的累计误差过大了,大于这个参数,导致没有求 inter 约束。
DispatchScanMatcherConstruction
针对某一个submap_id
的submap构建一个扫描匹配器,先看返回类型
1 2 3 4 5 6 7 8
| struct SubmapScanMatcher { const Grid2D* grid = nullptr; std::unique_ptr<scan_matching::FastCorrelativeScanMatcher2D> fast_correlative_scan_matcher; std::weak_ptr<common::Task> creation_task_handle; };
|
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
| const ConstraintBuilder2D::SubmapScanMatcher* ConstraintBuilder2D::DispatchScanMatcherConstruction( const SubmapId& submap_id, const Grid2D* const grid) { CHECK(grid); if (submap_scan_matchers_.count(submap_id) != 0) return &submap_scan_matchers_.at(submap_id);
auto& submap_scan_matcher = submap_scan_matchers_[submap_id]; submap_scan_matcher.grid = grid; auto& scan_matcher_options = options_.fast_correlative_scan_matcher_options();
auto scan_matcher_task = absl::make_unique<common::Task>(); scan_matcher_task->SetWorkItem( [&submap_scan_matcher, &scan_matcher_options]() { submap_scan_matcher.fast_correlative_scan_matcher = absl::make_unique<scan_matching::FastCorrelativeScanMatcher2D>( *submap_scan_matcher.grid, scan_matcher_options); } );
submap_scan_matcher.creation_task_handle = thread_pool_->Schedule(std::move(scan_matcher_task)); return &submap_scan_matchers_.at(submap_id); }
|
这里就是构造了scan_matcher_task
的 work_item
,返回 scan_matcher
。
继续看MaybeAddConstraint
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| auto constraint_task = absl::make_unique<common::Task>(); constraint_task->SetWorkItem([=]() LOCKS_EXCLUDED(mutex_) { ComputeConstraint(submap_id, submap, node_id, false, constant_data, initial_relative_pose, *scan_matcher, constraint); });
constraint_task->AddDependency(scan_matcher->creation_task_handle); auto constraint_task_handle = thread_pool_->Schedule(std::move(constraint_task));
finish_node_task_->AddDependency(constraint_task_handle);
|
其实
MaybeAddConstraint
做的就是下面的工作,接下来的重点就是
ConstraintBuilder2D::ComputeConstraint