接着上一篇的分析, 主要分析ceres_scan_matcher_.Match() 与 sparse_pose_graph_->AddScan()。
先看ceres_scan_matcher_.Match() ,转到mapping_2d\scan_matching\ 里找它的头文件与源文件。
CeresScanMatcher::Match()主要就是构造约束条件,然后调用ceres库完成优化,如何优化不讲,讲讲约束条件是如何构造的。这个函数里有3个约束条件的构造。
//激光点云占据栅格的代价函数,当点云匹配到原地图中的自由状态的栅格时,代价增加;当匹配到占据状态的栅格时,代价减小。同时还有一个点云占据系数,可在配置文件中设置。
new OccupiedSpaceCostFunctor( options_.occupied_space_weight() / std::sqrt(static_cast<double>(point_cloud.size())), point_cloud, probability_grid);// 位置代价函数,当估计位置与初始给定位置距离增大时,代价也增大,同时还有一个平移权重系数,在配置文件中可设定。
new TranslationDeltaCostFunctor( options_.translation_weight(), previous_pose);//旋转代价函数,但估计角度与初始给定位姿的角度相差增大时,代价增大,同时还有一个旋转权重系数,在配置文件中可设定。
new RotationDeltaCostFunctor( options_.rotation_weight(), ceres_pose_estimate[2]);所谓优化问题,一般都是将某一问题抽象(就是构造约束条件,也就是构造残差表达式),然后转换为优化线性或非线性最小二乘问题。最小二乘问题求解的方法也无非就那几种(ceres库中均应有实现,没看源码,不确定),请自行百度。 至此,局部地图全部优化完毕,再看全局优化过程sparse_pose_graph_->AddScan()。 在mapping_2d\sparse_pose_graph.cc 中
(我好心痛啊,快写完了,结果csdn崩溃了,写的东西全没了,又得再来一遍,香菇蓝瘦,之前写的比较详细,这里就凭印象简写了)
正如前面所说,AddScan的核心是利用分支定界进行全局的闭环检测完成优化,那重点自然就是如何进行分支定界了。 函数最后调用了ComputeConstraintsForScan(), 里面调用了constraint_builder_.MaybeAddGlobalConstraint() 或 constraint_builder_.MaybeAddConstraint() 这两个函数在mapping_2d\sparse_pose_graph\中。 它们都首先调用 ScheduleSubmapScanMatcherConstructionAndQueueWorkItem(),里面有个变量thread_pool,用于多线程的操作。 然后调用了ComputeConstraint(),这个函数重点就是调用 submap_scan_matcher->fast_correlative_scan_matcher->MatchFullSubmap() 与 submap_scan_matcher->fast_correlative_scan_matcher->Match()。 (这之间的过程是,代码是写的天花乱坠,各种技巧,我也没看太懂,反正重点就是这两货了) 这两个函数一个是用于全局的扫描匹配,一个是在全局地图中开个窗口,然后在窗口内进行扫描匹配。(PS:如果有小伙伴想进行基于离线地图的机器人全局定位,可以用该函数计算机器人的初始位姿哦,但不推荐其用于连续的机器人轨迹跟踪,毕竟计算量大啊) 这两个函数最终都是调用 MatchWithSearchParameters()
(划重点了,分支定界就在这个函数里面!!!)
1、首先生成以一定的角步长生成旋转候选粒子, GenerateRotatedScans ,即给定一个激光扫描,以及角步长,角度从0到360°,生成旋转候选粒子; 2、将旋转候选粒子离散化, DiscretizeScans,即原始的激光扫描是由浮点的(角度,距离值)组成,离散化后变成整型的(地图索引x,地图索引y); 3、收紧搜索边界,ShrinkToFit,边界参数存放在search_parameters中; 4、计算低分辨率地图中的候选粒子ComputeLowestResolutionCandidates,这里着重讲一下,这种方法之所以能够快速进行匹配,也是利用了多分辨地图这个特点,分支定界就是在多分辨率地图中完成的。首先在低分辨率地图中,将旋转候选粒子以一定的平移补偿(x,y两个方向),放在整个搜索空间中(简单粗暴啊,三维空间中的搜索),然后按照每个候选粒子的平均分,按又高到低进行排序,得到排序后的lowest_resolution_candidates。 5、分支定界,BranchAndBound,下界min_score ,小于该得分的候选粒子直接就pass了。计算高一层分辨率地图所需要的粒子位置偏移量,然后在高一层分辨率地图中,再次计算每个粒子的平均得分并排序,最后递归
best_high_resolution_candidate = std::max( best_high_resolution_candidate, BranchAndBound(discrete_scans, search_parameters, higher_resolution_candidates, candidate_depth - 1, best_high_resolution_candidate.score));可以看出递归采取深度优先的方式,先在某一个区域块(对应的就是低分辨率地图),进行四分查找(二维,所以是四分,四分之后,地图分辨率提高一个层级),计算该区域块的最高得分的粒子,然后递归下去,直到最高分辨率的地图,选择最高分辨率地图中,得分最高的粒子,其分数作为分支定界的下界,然后再在其它区域进行这样的操作,直到扫描完整张地图。
好了,这篇博客介绍的比较粗糙,中间具体的实现细节我也没看太懂,好多语法也不懂,只能看个大概,所以,这里就只是抛砖引玉了吧!