之前是生成了多分辨率地图,下面介绍如何利用多分辨率地图来进行匹配
在计算子图间约束的过程中,会通过匹配来获取约束
上次总览:
计算子图间,节点和非本子图的约束,分为局部约束和全局约束,
局部约束控制在15米以内的约束添加,通过函数ConstraintBuilder2D::MaybeAddConstraint 进行添加
在MaybeAddConstraint中,为子图新建了匹配器scan_matcher,并且通过匹配器来计算约束
1 2 3 4 5 6
| auto constraint_task = absl::make_unique<common::Task>(); constraint_task->SetWorkItem([=]() LOCKS_EXCLUDED(mutex_) { ComputeConstraint(submap_id, submap, node_id, false, constant_data, initial_relative_pose, *scan_matcher, constraint);
|
ConstraintBuilder2D::ComputeConstraint
根据match_full_submap 布尔值,来决定是否进行全局匹配,
首先使用分支定界算法匹配器进行粗匹配,局部地图匹配,输入的是initial_pose, 即Node在local下的坐标,点云数据,
输出的内容为pose_estimate
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| void ConstraintBuilder2D::ComputeConstraint( const SubmapId& submap_id, const Submap2D* const submap, const NodeId& node_id, bool match_full_submap, const TrajectoryNode::Data* const constant_data, const transform::Rigid2d& initial_relative_pose, const SubmapScanMatcher& submap_scan_matcher, std::unique_ptr<ConstraintBuilder2D::Constraint>* constraint) { const transform::Rigid2d initial_pose = ComputeSubmapPose(*submap) * initial_relative_pose;
if (match_full_submap) { kGlobalConstraintsSearchedMetric->Increment(); if (submap_scan_matcher.fast_correlative_scan_matcher->MatchFullSubmap( constant_data->filtered_gravity_aligned_point_cloud, options_.global_localization_min_score(), &score, &pose_estimate)) { CHECK_GT(score, options_.global_localization_min_score()); CHECK_GE(node_id.trajectory_id, 0); CHECK_GE(submap_id.trajectory_id, 0); kGlobalConstraintsFoundMetric->Increment(); kGlobalConstraintScoresMetric->Observe(score); } else { return; } } else { kConstraintsSearchedMetric->Increment(); if (submap_scan_matcher.fast_correlative_scan_matcher->Match( initial_pose, constant_data->filtered_gravity_aligned_point_cloud, options_.min_score(), &score, &pose_estimate)) { CHECK_GT(score, options_.min_score()); kConstraintsFoundMetric->Increment(); kConstraintScoresMetric->Observe(score); } else { return; } }
|
然后通过ceres进行精匹配
1 2 3 4 5 6
| ceres::Solver::Summary unused_summary; ceres_scan_matcher_.Match(pose_estimate.translation(), pose_estimate, constant_data->filtered_gravity_aligned_point_cloud, *submap_scan_matcher.grid, &pose_estimate, &unused_summary);
|
重新获取submap和Node之间的约束(变换),然后进行返回
1 2 3 4 5 6 7 8 9 10 11
|
const transform::Rigid2d constraint_transform = ComputeSubmapPose(*submap).inverse() * pose_estimate;
constraint->reset(new Constraint{submap_id, node_id, {transform::Embed3D(constraint_transform), options_.loop_closure_translation_weight(), options_.loop_closure_rotation_weight()}, Constraint::INTER_SUBMAP});
|
候选解的生成
生成SearchParameters,根据先验的位姿,在进行进一步匹配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
bool FastCorrelativeScanMatcher2D::Match( const transform::Rigid2d& initial_pose_estimate, const sensor::PointCloud& point_cloud, const float min_score, float* score, transform::Rigid2d* pose_estimate) const { const SearchParameters search_parameters(options_.linear_search_window(), options_.angular_search_window(), point_cloud, limits_.resolution()); return MatchWithSearchParameters(search_parameters, initial_pose_estimate, point_cloud, min_score, score, pose_estimate); }
|
MatchWithSearchParameters
此点云是在在原点,并且位姿为也为(0,0,0)的点云,因此要对点云先旋转后平移
1 2 3 4 5 6
| const Eigen::Rotation2Dd initial_rotation = initial_pose_estimate.rotation(); const sensor::PointCloud rotated_point_cloud = sensor::TransformPointCloud( point_cloud, transform::Rigid3f::Rotation(Eigen::AngleAxisf( initial_rotation.cast<float>().angle(), Eigen::Vector3f::UnitZ())));
|
为了生成较优候选解,生成按照不同角度旋转后的点云集合
根据SearchParameters 中的 angular_search_window,按照角度为[-angular_search_window, angular_search_window],角度差为角度分辨率,生成num_scans 个不同角度的旋转点云
1 2 3
| const std::vector<sensor::PointCloud> rotated_scans = GenerateRotatedScans(rotated_point_cloud, search_parameters);
|
平移,对上面生成的所有的点云都进行平移,并且获取平移后的点在地图中的索引
1 2 3 4 5 6
|
const std::vector<DiscreteScan2D> discrete_scans = DiscretizeScans( limits_, rotated_scans, Eigen::Translation2f(initial_pose_estimate.translation().x(), initial_pose_estimate.translation().y()));
|
现在得到了多个经过平移旋转年之后的点云,注意只是旋转不同,平移距离都一样
减小搜索框
1 2
| search_parameters.ShrinkToFit(discrete_scans, limits_.cell_limits());
|
然后计算最低分辨率的候选解,其流程为
- 遍历所有的旋转程度不同的点云,把每一个点云搜索框内的x和y都作为一个偏移量,作为一个新点云
- 对所有的新点云进行遍历打分
- 按照分数进行排序
vector里面包含的是所有的点云,每一个Candidate2D 都包含这自己的分数
1 2 3 4 5
|
const std::vector<Candidate2D> lowest_resolution_candidates = ComputeLowestResolutionCandidates(discrete_scans, search_parameters);
|
基于分支定界算法获得最优解
获得一个Candidate2D
1 2 3 4
| const Candidate2D best_candidate = BranchAndBound( discrete_scans, search_parameters, lowest_resolution_candidates, precomputation_grid_stack_->max_depth(), min_score);
|
对计算出的偏移量进行校准
1 2 3 4 5 6 7 8 9 10 11
| if (best_candidate.score > min_score) { *score = best_candidate.score; *pose_estimate = transform::Rigid2d( {initial_pose_estimate.translation().x() + best_candidate.x, initial_pose_estimate.translation().y() + best_candidate.y}, initial_rotation * Eigen::Rotation2Dd(best_candidate.orientation)); return true; } return false;
|