闭合优化问题也是一个非线性最小二乘问题
全局地图是由很多个子图拼接而成的,那么每一个子图在世界坐标系下都有一个位姿,它们的位姿可以用下面的集合表示
前端每完成一次子图更新,会把一帧激光扫描数据插入其维护的子图当中。这个插入结果将被Cartographer看做是构成一条轨迹的节点,并以此时的机器人位姿作为节点的位姿,将其看做是一次激光扫描的参考位姿,所有位姿的集合如下表示
这些被优化的submap位姿和Scan位姿给出了一些constraint(约束)。constraint的表现形式就是位姿 和协方差矩阵 。 位姿 代表 j 帧Scan在子图 i 下的位姿,描述scan和哪个submap匹配
(1)式中的残差E计算公式是
损失函数ρ(例如Huber损失),可以用于减少异常值的影响,而 异常值可能会出现在局部对称的环境(包含隔间的办公室)中
1 | NodeId PoseGraph2D::AppendNode( |
这些对于一条轨迹的情况都不重要,先不深入分析
1 | // 添加scan的node_id,返回 trajectory id 和对应的 scan idex |
data_
是 PoseGraphData data_ GUARDED_BY(mutex_);
trajectory_nodes
的类型是MapById<NodeId, TrajectoryNode>
,对于Append函数,不必关心细节。里面的TrajectoryNode
类型是1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22struct TrajectoryNode
{
// 记录了前端传来的 点云、重力方向、局部位姿等数据
struct Data {
// 扫描数据被插入子图的时刻
common::Time time;
// Transform to approximately gravity align the tracking frame as
// determined by local SLAM.
Eigen::Quaterniond gravity_alignment;
// Used for loop closure in 2D: voxel filtered returns in the
// 'gravity_alignment' frame.
sensor::PointCloud filtered_gravity_aligned_point_cloud;
// 省略用于3D建图时闭环检测的字段
// The node pose in the local SLAM frame.
transform::Rigid3d local_pose;
};
common::Time time() const { return constant_data->time; }
// 实际只有这两个成员
std::shared_ptr<const Data> constant_data;
// The node pose in the global SLAM frame.
transform::Rigid3d global_pose;
最重要的是global_pose
,节点在世界坐标系下的位姿,论文里的
返回去查,发现是GetLocalToGlobalTransform
返回的
1 | // Test if the 'insertion_submap.back()' is one we never saw before. |
增加了该节点在 global map坐标系的全局位姿,也是后期需要优化的位姿。把node加入到trajectory_nodes
列表。 最后返回的位姿图ID为data_
存储的轨迹节点ID。
前端最新的子图与当前 data_
最后一个子图不一致时,给该子图分配id并将其加入其中(其实就是把前端最新子图加入到后端)。注意,这时候的子图 还没有计算global pose,也就是 。 所以,后面要初始化submap的global pose,也就是InitializeGlobalSubmapPoses