首页 关于
树枝想去撕裂天空 / 却只戳了几个微小的窟窿 / 它透出天外的光亮 / 人们把它叫做月亮和星星
目录

map_builder的接口实现

上一章中,我们简单的浏览了map_builder对象,对它有了一个总体的认识。 也了解到它继承自接口类MapBuilderInterface, 下表总结了各个接口函数及其相关的功能。本文中,我们来讨论map_builder对象的接口函数的实现。

接口函数 接口功能
AddTrajectoryBuilder 创建一个新的轨迹跟踪器并返回该跟踪器的索引。
AddTrajectoryForDeserialization 也是一个用于新建轨迹跟踪器的接口。
GetTrajectoryBuilder 获取一个索引为trajectory_id的轨迹跟踪器对象。
FinishTrajectory 关闭trajectory_id对应的轨迹跟踪器。
SubmapToProto 将submap_id所对应的子图信息填充到proto流中。
SerializeState 将系统状态转换为proto流,进行序列化。
LoadState 从proto流中加载系统状态。
num_trajectory_builders 获取当前轨迹跟踪器的数量。
pose_graph 获取用于实现闭环检测的PoseGraph对象。
GetAllTrajectoryBuilderOptions 获取所有的轨迹跟踪器的配置。

在这些众多的接口当中,我们最主要关心的是AddTrajectoryBuilder因为它创建了一个新的轨迹跟踪器,用于进行局部的SLAM。其次关心FinishTrajectory,它用于结束一段轨迹。 至于那些与proto流相关的接口,有的实现还很长,比如说LoadState,但是它们与实际的建图过程没有关系,所以本文不分析它们。还有一类Get类的接口,用于获取map_builder的一些属性或者对象, 代码很短,我们将一带而过。

1. AddTrajectoryBuilder

下面是函数AddTrajectoryBuilder的代码片段。它有三个参数,其中expected_sensor_ids中记录了用于建图的所有传感器名称和类型,trajectory_options是新建的轨迹跟踪器的配置, 最后的local_slam_result_callback则是一个回调函数对象,用于响应局部地图构建完成的事件。

        int MapBuilder::AddTrajectoryBuilder(const std::set<SensorId>& expected_sensor_ids,
                                             const proto::TrajectoryBuilderOptions& trajectory_options,
                                             LocalSlamResultCallback local_slam_result_callback) {

在函数的一开始,先通过容器trajectory_builders_获取轨迹跟踪器的数量。如果构建成功,该值将作为新的跟踪器的索引。

            const int trajectory_id = trajectory_builders_.size();

然后根据配置是否进行三维建图构建跟踪器。由于我们的demo只是二维建图,所以这里省略了条件判定为真的分支。在二维建图的分支中,一开始先创建了一个LocalTrajectoryBuilder2D类型的对象。这个对象并不是我们一直说的轨迹跟踪器,但它应该是轨迹跟踪器的核心。因为它几乎完成了一个局部SLAM的所有功能,包括位姿估计、扫描匹配等,就是没有闭环检测。

接口还会检查一下是否有关于2D的轨迹跟踪器的配置项,如果有的话,就根据其配置项以及传感器配置具例化对象local_trajectory_builder。

            if (options_.use_trajectory_builder_3d()) {
                // 省略
            } else {
                std::unique_ptr<LocalTrajectoryBuilder2D> local_trajectory_builder;
                if (trajectory_options.has_trajectory_builder_2d_options())
                    local_trajectory_builder = common::make_unique<LocalTrajectoryBuilder2D>(trajectory_options.trajectory_builder_2d_options(),SelectRangeSensorIds(expected_sensor_ids));

接着通过dynamic_cast将pose_graph_对象强制转换为PoseGraph2D,并检查数据类型是否正确。

                DCHECK(dynamic_cast<PoseGraph2D*>(pose_graph_.get()));

接下来的语句所构建的CollatedTrajectoryBuilder类型的对象才是所谓的轨迹跟踪器, 它继承自接口类TrajectoryBuilderInterface, 这个接口适用于2D和3D建图的对象,如此上层的代码就不必关心具体的建图内核,使用同一个容器trajectory_builders_就可以保存两种local SLAM的对象。构建好的对象就直接被塞进了容器中。

此外CollatedTrajectoryBuilder类型还使用sensor::CollatorInterface接口来收集传感器数据,所以我们可以在它的构造参数列表中看到对象sensor_collator_。在构建轨迹跟踪器的时候, 还通过函数CreateGlobalTrajectoryBuilder2D构建了一个GlobalTrajectoryBuilder类型的对象, 从它的参数列表中来看,除了含有刚刚构建的local_trajectory_builder对象之外,还引入了位姿图对象pose_graph_。个人猜测这是一个具有闭环功能的SLAM对象,所以称之为Global的。

                trajectory_builders_.push_back(common::make_unique<CollatedTrajectoryBuilder>(
                                               sensor_collator_.get(), trajectory_id, expected_sensor_ids,
                                               CreateGlobalTrajectoryBuilder2D(std::move(local_trajectory_builder), trajectory_id,
                                                                               static_cast<PoseGraph2D*>(pose_graph_.get()),
                                                                               local_slam_result_callback)));

针对二维建图还有一个配置项overlapping_submaps_trimmer_2d, 决定是否为pose_graph_对象添加一个OverlappingSubmapsTrimmer2D类型的修饰器, 用于根据子图之间重叠的部分修饰地图。

                if (trajectory_options.has_overlapping_submaps_trimmer_2d()) {
                    const auto& trimmer_options = trajectory_options.overlapping_submaps_trimmer_2d();
                    pose_graph_->AddTrimmer(common::make_unique<OverlappingSubmapsTrimmer2D>(trimmer_options.fresh_submaps_count(),
                                            trimmer_options.min_covered_area() / common::Pow2(trajectory_options.trajectory_builder_2d_options().submaps_options().grid_options_2d().resolution()),
                                            trimmer_options.min_added_submaps_count()));
                }
            }

如果配置项要求只进行定位,那么可以在pose_graph_中添加一个PureLocalizationTrimmer类型修饰器,来完成这一功能。

            if (trajectory_options.pure_localization()) {
                constexpr int kSubmapsToKeep = 3;
                pose_graph_->AddTrimmer(common::make_unique<PureLocalizationTrimmer>(trajectory_id, kSubmapsToKeep));
            }

如果开始建图之前已经有了初始位置,那么我们可以将初始位置提供给pose_graph_对象。

            if (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()));
            }

接下来,将轨迹跟踪器的配置信息和传感器配置信息保存到容器all_trajectory_builder_options_中。

            proto::TrajectoryBuilderOptionsWithSensorIds options_with_sensor_ids_proto;
            for (const auto& sensor_id : expected_sensor_ids)
                *options_with_sensor_ids_proto.add_sensor_id() = ToProto(sensor_id);
            *options_with_sensor_ids_proto.mutable_trajectory_builder_options() = trajectory_options;
            all_trajectory_builder_options_.push_back(options_with_sensor_ids_proto);

最后检查一下轨迹跟踪器对象及其配置的数量,确保两者相等之后返回新建的跟踪器对象的索引。

            CHECK_EQ(trajectory_builders_.size(), all_trajectory_builder_options_.size());
            return trajectory_id;
        }

2. FinishTrajectory和那些一带而过的接口

下面是FinishTrajectory的代码片段,内容简单的有些枯燥,就是通知sensor_collator_和pose_graph_两个对象终止trajectory_id所对应的轨迹处理。

        void MapBuilder::FinishTrajectory(const int trajectory_id) {
            sensor_collator_->FinishTrajectory(trajectory_id);
            pose_graph_->FinishTrajectory(trajectory_id);
        }

剩下的一些接口,我们也不再细说了,都只有一句话。

        mapping::PoseGraphInterface *pose_graph() override {
            return pose_graph_.get();
        }

        int num_trajectory_builders() const override {
            return trajectory_builders_.size();
        }

        mapping::TrajectoryBuilderInterface *GetTrajectoryBuilder(int trajectory_id) const override {
            return trajectory_builders_.at(trajectory_id).get();
        }

        const std::vector<proto::TrajectoryBuilderOptionsWithSensorIds>& GetAllTrajectoryBuilderOptions() const override {
            return all_trajectory_builder_options_;
        }

3. 完

本文中我们简单看了一下map_builder对象的接口实现,重点分析了AddTrajectoryBuilder函数,看到实际完成局部建图任务的是一个LocalTrajectoryBuilder2D类型的对象, 而与轨迹规划器相关的类型还有CollatedTrajectoryBuilder、GlobalTrajectoryBuilder、TrajectoryBuilderInterface它们应该说是一种封装。




Copyright @ 高乙超. All Rights Reserved. 京ICP备16033081号-1