纯定位其实就是SLAM,只是不保存所有子图。因为node_main.cc
里会先要运行map_builder
,然后再是node.LoadState
,最后StartTrajectoryWithDefaultTopics
进行建图
Node::LoadState(const std::string& state_filename, const bool load_frozen_state)
—— map_builder_bridge_.LoadState
—— map_builder.LoadState
MapBuilder::AddTrajectoryBuilder
中有一句MaybeAddPureLocalizationTrimmer(trajectory_id, trajectory_options, pose_graph_.get() );
,实际就是1
2
3
4
5
6if (trajectory_options.has_pure_localization_trimmer())
{
pose_graph->AddTrimmer(absl::make_unique<PureLocalizationTrimmer>(
trajectory_id,
trajectory_options.pure_localization_trimmer().max_submaps_to_keep() ) );
}PureLocalizationTrimmer
这个类的解释就是 Keeps the last num_submaps_to_keep
of the trajectory with trajectory_id
to implement localization without mapping.
初值的处理1
2
3
4
5
6
7
8
9if (trajectory_options.has_initial_trajectory_pose())
{
const auto& initial_trajectory_pose =
trajectory_options.initial_trajectory_pose();
pose_graph_->SetInitialTrajectoryPose(
trajectory_id, initial_trajectory_pose.to_trajectory_id(),
transform::ToRigid3(initial_trajectory_pose.relative_pose()),
common::FromUniversal(initial_trajectory_pose.timestamp()) );
}
这里涉及到PoseGraph2D::SetInitialTrajectoryPose
,其实就一句 data_.initial_trajectory_poses[from_trajectory_id] = InitialTrajectoryPose{to_trajectory_id, pose, time};
MapBuilder::LoadState
先是轨迹ID的增加1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20std::map<int, int> trajectory_remapping;
for (int i = 0; i < pose_graph_proto.trajectory_size(); ++i)
{
auto& trajectory_proto = *pose_graph_proto.mutable_trajectory(i);
const auto& options_with_sensor_ids_proto =
all_builder_options_proto.options_with_sensor_ids(i);
// 新的轨迹id
const int new_trajectory_id =
AddTrajectoryForDeserialization(options_with_sensor_ids_proto);
CHECK(trajectory_remapping
.emplace(trajectory_proto.trajectory_id(), new_trajectory_id)
.second)
<< "Duplicate trajectory ID: " << trajectory_proto.trajectory_id();
trajectory_proto.set_trajectory_id(new_trajectory_id);
if (load_frozen_state) {
pose_graph_->FreezeTrajectory(new_trajectory_id);
}
}
然后从pbstream文件添加轨迹,更新约束中节点和子图的轨迹id,从获取的pose graph生成 submap_poses
和 node_poses
,将landmark_poses
添加到pose graph,向pose graph添加各类信息,添加子图的附属节点。
以添加子图为例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
33io::ProtoStreamDeserializer deserializer(reader);
// Create a copy of the pose_graph_proto, such that we can re-write the
// trajectory ids.
proto::PoseGraph pose_graph_proto = deserializer.pose_graph();
const auto& all_builder_options_proto =
// 子图位姿
MapById<SubmapId, transform::Rigid3d> submap_poses;
for (const proto::Trajectory& trajectory_proto :
pose_graph_proto.trajectory())
{
for (const proto::Trajectory::Submap& submap_proto :
trajectory_proto.submap())
{
submap_poses.Insert(SubmapId{trajectory_proto.trajectory_id(),
submap_proto.submap_index()},
transform::ToRigid3(submap_proto.pose()));
}
}
MapById<SubmapId, mapping::proto::Submap> submap_id_to_submap;
// while 读子图数据
proto.mutable_submap()->mutable_submap_id()->set_trajectory_id(
trajectory_remapping.at(
proto.submap().submap_id().trajectory_id()));
submap_id_to_submap.Insert(
SubmapId{proto.submap().submap_id().trajectory_id(),
proto.submap().submap_id().submap_index()},
proto.submap());
// 进入后端
for (const auto& submap_id_submap : submap_id_to_submap) {
pose_graph_->AddSubmapFromProto(submap_poses.at(submap_id_submap.id),
submap_id_submap.data);
}
后端的处理
1 | void PoseGraph2D::AddTrimmer(std::unique_ptr<PoseGraphTrimmer> trimmer) |
这里作为WorkItem加入到了队列里,到了HandleWorkQueue
才处理1
2
3
4
5
6
7
8
9
10
11
12
13
14// 纯定位时,子图的裁剪,如果没有裁剪器就不裁剪
TrimmingHandle trimming_handle(this);
for (auto& trimmer : trimmers_)
{
// 这里实际就是 PureLocalizationTrimmer::Trim
trimmer->Trim(&trimming_handle);
}
// 裁剪器完成状态,删除裁剪器
trimmers_.erase(
std::remove_if(trimmers_.begin(), trimmers_.end(),
[](std::unique_ptr<PoseGraphTrimmer>& trimmer) {
return trimmer->IsFinished();
}),
trimmers_.end());
这里的裁剪子图就是PureLocalizationTrimmer::Trim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23void PureLocalizationTrimmer::Trim(Trimmable* const pose_graph)
{
// trajectory 是FINISHED状态,或者没有 trajectory
if (pose_graph->IsFinished(trajectory_id_)) {
num_submaps_to_keep_ = 0;
}
// 实际调用 TrimmingHandle::GetSubmapIds
auto submap_ids = pose_graph->GetSubmapIds(trajectory_id_);
// 从序列号0开始裁剪子图,并删除相关的约束和节点
// 剩下序列号大的子图
for (std::size_t i = 0; i + num_submaps_to_keep_ < submap_ids.size(); ++i)
{
// 这是个大函数
pose_graph->TrimSubmap(submap_ids.at(i) );
}
if (num_submaps_to_keep_ == 0)
{
finished_ = true;
pose_graph->SetTrajectoryState(
trajectory_id_,
PoseGraphInterface::TrajectoryState::DELETED );
}
}GetSubmapIds
有必要学习1
2
3
4
5
6
7
8
9
10
11
12std::vector<SubmapId> PoseGraph2D::TrimmingHandle::GetSubmapIds(
int trajectory_id) const
{
std::vector<SubmapId> submap_ids;
// PoseGraph2D* const parent_;
const auto& submap_data = parent_->optimization_problem_->submap_data();
for (const auto& it : submap_data.trajectory(trajectory_id) )
{
submap_ids.push_back(it.id);
}
return submap_ids;
}
核心是pose_graph->TrimSubmap
,函数太长了,功能如下:
- 获取除submap_id外的所有子图的所有节点的id(nodes_to_retain),这些节点是需要保留的
- 找到在submap.id的子图内部,同时不在别的子图内的节点(nodes_to.remove),这些节点需要删除
- 删除submap_id相关的约束
- 删除与nodes_to_remove中节点相关联的约束,并对对应的submap_id进行标记
- 对标记的submap_id进行检查,看是否还存在其他子图间约束
- 删除没有子图间约束的标记的子图的扫描匹配器
- 删除submap_id这个子图的指针
- 删除submap_id这个子图的匹配器,与多分辨率地图
- 删除
optimization_problem_
中的submap_id
这个子图 - 删除
nodes_to_remove
中的节点
纯定位时的匹配流程
PoseGraph2D::ComputeConstraint(node_id, submap_id)
—— ConstraintBuilder2D::MaybeAddGlobalConstraint(submap_id, submap, node_id, constant_data)
—— ConstraintBuilder2D::ComputeConstraint
第2步中1
2
3
4// 分别根据 node_id 和 submap_id 获得 节点数据和子图
const TrajectoryNode::Data* constant_data = data_.trajectory_nodes.at(node_id).constant_data.get();
const Submap2D* submap = static_cast<const Submap2D*>(data_.submap_data.at(submap_id).submap.get() );
第3步中,submap
唯一的作用是获得初值:1
2const transform::Rigid2d initial_pose =
ComputeSubmapPose(*submap) * initial_relative_pose;
这里就想到那句话:前端建图,后端并不改变子图本身,只改变位姿。