Local SLAM生成一系列子图时,一个global优化(通常被称为“优化问题”或者“sparse pose adjustment”)在后台运行,其主要工作是找到回环约束。它是通过scan-matching的方法用节点中收集到的scans与子地图进行匹配。它用于重新整理子地图,以便他们可以内联成一个全局地图。比如改变当前建成的轨迹以适应于回环检测的子地图对齐(align submaps with regards to loop closures)
- optimize_every_n_nodes: 一旦一定的trajectory nodes插入,就会分批执行优化,由你决定多么频繁的运行优化和调整大小。如果设置为0,是手动关闭全局SLAM,主要精力集中于局部SLAM。这是调试cartographer的第一件事情。
optimize_every_n_nodes
里对应优化标志kRunOptimization
,这个标志一共出现三次,还有FinishTrajectory
和RunFinalOptimization
。 前者的根源是node.FinishAllTrajectories();
, 后者的根源在node.RunFinalOptimization();
全局SLAM是一种基于图优化的SLAM,本质上是位姿图优化,其是通过构建节点之间的约束,子地图之间的约束,然后优化产生的约束图。约束可以认为是一根连接所有节点的绳子。sparse pose adjustment
加速这些绳子的结合。产生的网称为位姿图(Pose Graph)
约束可以在rviz中观察,这样调整global SLAM很方便
constraint_builder
1 | POSE_GRAPH = { |
- sampling_ratio: 就是个采样器,用于
MaybeAddConstraint
,值越小, 计算约束的频率越小 max_constraint_distance: 非常重要 局部子图进行回环检测时,能成为约束的距离阈值,在
MaybeAddConstraint
开头。如果设置过小,即使建图时走回同一个位置,也不会计算回环,函数里return。如果设置太大,计算量会增大很多,因为节点会和所有完成的子图匹配。log_matches: 得到 constraints builder(回环约束)的日志,默认是true。 在
ConstraintBuilder2D::ComputeConstraint
min_score: 局部子图进行回环检测时,扫描匹配分数的阈值,低于该阈值时不考虑匹配。 匹配得分不要太高,不然在有的地方不能回环。决定了哪些constraint添加到哪些节点间,这个会很大影响CPU。可以稍微增大到0.75 The histograms printed while Cartographer is running shows that you have a ton of loop closures constraints already, so lowering the score (which will raise the number of constraints) is probably not too useful. I’d rather ramp it up.
global_localization_min_score: 用于有多条轨迹的情况,比如纯定位。不能设置太小,否则会出现超出地图的约束;也不能太大,否则难以出现约束。一般设置为 0.6
非全局约束(也称为子地图内部约束)是在节点之间自动建立,这些节点是轨迹上相对较近的节点。直观上,这些非全局约束保持了轨迹的内连关系。
全局约束(也称为回环检测约束或者子地图之间的约束)通常是在一个新的子地图和先前的节点之间进行搜索,那些先前的节点在空间上是足够的近(部分是在某个搜索window)。直观上,这些全局约束在结构上引进了一个结(打结),固定把两股子地图靠拢。
以下几项均为constraint_builder
的子项
fast_correlative_scan_matcher
分支定界1
2
3
4
5
6
7fast_correlative_scan_matcher =
{
# 这里的window大小明显比前端 real_time_correlative_scan_matcher 的大
linear_search_window = 7., # 依靠“分支定界”机制在不同的网格分辨率下工作,并有效地消除不正确的匹配
angular_search_window = math.rad(30.), # 一旦找到足够好的分数(高于最低匹配分数),它(scan)就会被送入Ceres扫描匹配器以优化姿势。
branch_and_bound_depth = 7, # 至少为1,应当是值越大,后端的作用越强
},branch_and_bound_depth
必须大于3,否则相当于暴力搜索,没有效果。和分辨率也有关系,0.05就用6和7,也就是可以不改。
ceres_scan_matcher
闭环检测的第二步,优化位姿图1
2
3
4
5
6
7
8
9
10
11ceres_scan_matcher =
{
occupied_space_weight = 20.,
translation_weight = 10.,
rotation_weight = 1.,
ceres_solver_options = {
use_nonmonotonic_steps = true,
max_num_iterations = 10,
num_threads = 1,
},
},
这里和前端的csm不同了,occupied_space_weight
较大,位移权重较小,旋转权重更小。
use_nonmonotonic_steps
:是否允许计算cost时,短暂增大。false会计算到局部最小; true则增加计算量,可越过局部最小。
1 | matcher_translation_weight = 5e2, |
global_sampling_ratio
只在PoseGraph2D::AddTrajectoryIfNeeded
中的FixedRatioSampler
机制使用
overlapping_submaps_trimmer_2d 机制
源码在PoseGraph2D
的构造函数里,对应has_overlapping_submaps_trimmer_2d
参数
Trimmer是一个删除子图的操作,其具体参数在cartographer/configuration_files/pose_graph.lua中1
2
3
4
5
6overlapping_submaps_trimmer_2d =
{
fresh_submaps_count = 1,
min_covered_area = 2,
min_added_submaps_count = 5,
}
这段被注释的部分,如果放开就会发现,建图时重复走一条路,submap会不连续,很多相似的被删除了。上述参数主要是调整参数某数submap是否达到被删除的阈值。具体实现在overlapping_submaps_trimmer_2d.cc
,可以简单理解为,删除与最新的1个submaps覆盖后剩余栅格小于2个的子图submap。
全局优化中的里程计
如果local SLAM 使用了单独的里程计(use_odometry
= true), 我们可以相应地调整全局SLAM
有四个参数允许我们在优化中调整局部SLAM和里程计的各个权重:1
2
3
4POSE_GRAPH.optimization_problem.local_slam_pose_translation_weight
POSE_GRAPH.optimization_problem.local_slam_pose_rotation_weight
POSE_GRAPH.optimization_problem.odometry_translation_weight
POSE_GRAPH.optimization_problem.odometry_rotation_weight
可以根据我们对本地SLAM或odometry的信任程度来设置这些权重。默认情况下,里程计被加权到类似于本地slam(扫描匹配)姿势的全局优化中。然而,来自车轮里程计的旋转通常具有很高的不确定性,因此,旋转权重可以减小,甚至降低到0
参考:后端测试报告