有关文件:cartographer/mapping/2d/scan_matching/fast_correlative_scan_matcher_2d.cpp
接下来探讨一下每个函数的具体实现过程:
Match函数分析
/**
* @brief 给定初始位姿时的匹配
* @param initial_pose_estimate 初始位姿估计
* @param point_cloud 将要考察激光点云数据
* @param min_score 回环检测的最小得分,当做阈值使用
* @param score 成功匹配后返回匹配得分
* @param score 成功匹配后返回位姿估计
* @return 如果闭环检测成功则返回ture,否则返回false
*/
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函数分析
/**
* @brief 完成扫描匹配,进而实现闭环检测
* @param search_parameters 搜索窗口的参数
* @param initial_pose_estimate 机器人初始位姿
* @param point_cloud 激光点云数据
* @param min_score 回环检测匹配最小得分
* @param score 记录回环检测匹配成功的得分
* @param pose_estimate 记录回环检测匹配成功的位姿
* @return 如果闭环检测成功则返回ture,否则返回false
*/
bool FastCorrelativeScanMatcher2D::MatchWithSearchParameters(
SearchParameters search_parameters,
const transform::Rigid2d& initial_pose_estimate,
const sensor::PointCloud& point_cloud, float min_score, float* score,
transform::Rigid2d* pose_estimate) const {
CHECK_NOTNULL(score);
CHECK_NOTNULL(pose_estimate);
// 获取初始位姿估计的方向角,将激光点云中的点都绕Z轴转动相应的角度。
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())));
// 获取搜索窗口下机器人朝向各个方向角时的点云数据。
const std::vector<sensor::PointCloud> rotated_scans =
GenerateRotatedScans(rotated_point_cloud, search_parameters);
// 完成对旋转后的点云数据离散化操作,即将浮点类型的点云数据转换成整型的栅格单元索引。
const std::vector<DiscreteScan2D> discrete_scans = DiscretizeScans(
limits_, rotated_scans,
Eigen::Translation2f(initial_pose_estimate.translation().x(),
initial_pose_estimate.translation().y()));
// 尽可能的缩小搜索窗口大小,以减少搜索空间,提高搜索效率。
search_parameters.ShrinkToFit(discrete_scans, limits_.cell_limits());
// 完成对搜索空间的第一次分割,得到初始子空间节点集合。
const std::vector<Candidate2D> lowest_resolution_candidates =
ComputeLowestResolutionCandidates(discrete_scans, search_parameters);
// 完成分支定界搜索,搜索结果放在best_candidate中。
const Candidate2D best_candidate = BranchAndBound(
discrete_scans, search_parameters, lowest_resolution_candidates,
precomputation_grid_stack_->max_depth(), min_score);
// 检测最优解的值,如果大于指定阈值min_score就认为匹配成功,修改输入参数指针score和pose_estimate所指对象。
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;
}
ComputeLowestResolutionCandidates函数分析
/**
* @brief 完成第一次分支
* @param discrete_scans 离散化之后的各个搜索方向上的点云数据
* @param search_parameters 搜索窗口的参数
*/
std::vector<Candidate2D>
FastCorrelativeScanMatcher2D::ComputeLowestResolutionCandidates(
const std::vector<DiscreteScan2D>& discrete_scans,
const SearchParameters& search_parameters) const {
// 完成对搜索空间的初始化分割(生成最低分辨率的所有候选解)。
std::vector<Candidate2D> lowest_resolution_candidates =
GenerateLowestResolutionCandidates(search_parameters);
// 计算各个候选点的评分并排序。
ScoreCandidates(
precomputation_grid_stack_->Get(precomputation_grid_stack_->max_depth()),
discrete_scans, search_parameters, &lowest_resolution_candidates);
return lowest_resolution_candidates;
}
GenerateLowestResolutionCandidates函数分析
/**
* @brief 生成最低分辨率的候选解
* @param search_parameters 搜索窗口的参数
*/
std::vector<Candidate2D>
FastCorrelativeScanMatcher2D::GenerateLowestResolutionCandidates(
const SearchParameters& search_parameters) const {
// 根据预算图的层数计算初始分割的粒度z^(depth)
const int linear_step_size = 1 << precomputation_grid_stack_->max_depth();
// 遍历所有搜索方向,累计各个方向下空间的分割数量,得到搜索空间初始分割的子空间数量。
int num_candidates = 0;
for (int scan_index = 0; scan_index != search_parameters.num_scans;
++scan_index) {
const int num_lowest_resolution_linear_x_candidates =
(search_parameters.linear_bounds[scan_index].max_x -
search_parameters.linear_bounds[scan_index].min_x + linear_step_size) /
linear_step_size;
const int num_lowest_resolution_linear_y_candidates =
(search_parameters.linear_bounds[scan_index].max_y -
search_parameters.linear_bounds[scan_index].min_y + linear_step_size) /
linear_step_size;
num_candidates += num_lowest_resolution_linear_x_candidates *
num_lowest_resolution_linear_y_candidates;
}
// 构建各个候选点
std::vector<Candidate2D> candidates;
candidates.reserve(num_candidates);
for (int scan_index = 0; scan_index != search_parameters.num_scans;
++scan_index) {
for (int x_index_offset = search_parameters.linear_bounds[scan_index].min_x;
x_index_offset <= search_parameters.linear_bounds[scan_index].max_x;
x_index_offset += linear_step_size) {
for (int y_index_offset = search_parameters.linear_bounds[scan_index].min_y;
y_index_offset <= search_parameters.linear_bounds[scan_index].max_y;
y_index_offset += linear_step_size) {
candidates.emplace_back(scan_index, x_index_offset, y_index_offset, search_parameters);
}
}
}
CHECK_EQ(candidates.size(), num_candidates);
return candidates;
}
ScoreCandidates函数分析
/**
* @brief 计算当前层的候选解与对应分辨率地图中的匹配得分,并按照得分从大到小的进行排序
* @param precomputation_grid 当前层对应的预计算图
* @param discrete_scans 待匹配的点云数据
* @param search_parameters 搜索窗口的参数
* @param candidates 当前层对应的候选解
*/
void FastCorrelativeScanMatcher2D::ScoreCandidates(
const PrecomputationGrid2D& precomputation_grid,
const std::vector<DiscreteScan2D>& discrete_scans,
const SearchParameters& search_parameters,
std::vector<Candidate2D>* const candidates) const {
// 遍历候选解
for (Candidate2D& candidate : *candidates) {
int sum = 0;
// 遍历所有激光点
for (const Eigen::Array2i& xy_index : discrete_scans[candidate.scan_index]) {
const Eigen::Array2i proposed_xy_index(xy_index.x() + candidate.x_index_offset,
xy_index.y() + candidate.y_index_offset);
//计算累计占用概率log_odd
sum += precomputation_grid.GetValue(proposed_xy_index);
}
// 求平均并转换为概率。
candidate.score = precomputation_grid.ToScore(
sum / static_cast<float>(discrete_scans[candidate.scan_index].size()));
}
// 对评分进行降序排序
std::sort(candidates->begin(), candidates->end(),
std::greater<Candidate2D>());
}
BranchAndBound函数分析
/**
* @brief 分支定界方法实现
* @param discrete_scans 离散化的不同搜索角度下的激光点云数据
* @param search_parameters 搜索窗口的配置参数
* @param candidates 候选点集合
* @param candidate_depth 搜索树高度
* @param min_score 候选点最小评分
* @return 返回当前分支中的最优解(得分最高且大于min_score)
*/
Candidate2D FastCorrelativeScanMatcher2D::BranchAndBound(
const std::vector<DiscreteScan2D>& discrete_scans,
const SearchParameters& search_parameters,
const std::vector<Candidate2D>& candidates, const int candidate_depth,
float min_score) const {
// 如果搜索树高度为0,意味着搜索到叶子节点。
if (candidate_depth == 0) {
// 由于每次迭代过程中对新扩展的候选点进行了降序排序,所以认为队首的这个节点就是最优解,直接返回即可。
return *candidates.begin();
}
// 创建一个临时的候选点对象,并为之赋予最小的评分。
Candidate2D best_high_resolution_candidate(0, 0, 0, search_parameters);
best_high_resolution_candidate.score = min_score;
// 遍历所有候选点,
for (const Candidate2D& candidate : candidates) {
// 如果遇到一个候选点的评分很低,意味着该分支之后的候选点中也没有合适的解。直接跳出循环,说明没有构成回环。
if (candidate.score <= min_score) {
break;
}
// 进行分支, 分解为4个子候选解
std::vector<Candidate2D> higher_resolution_candidates;
const int half_width = 1 << (candidate_depth - 1);
for (int x_offset : {0, half_width}) {
if (candidate.x_index_offset + x_offset >
search_parameters.linear_bounds[candidate.scan_index].max_x) {
break;
}
for (int y_offset : {0, half_width}) {
if (candidate.y_index_offset + y_offset >
search_parameters.linear_bounds[candidate.scan_index].max_y) {
break;
}
higher_resolution_candidates.emplace_back(
candidate.scan_index, candidate.x_index_offset + x_offset,
candidate.y_index_offset + y_offset, search_parameters);
}
}
// 对新扩展的候选解评分(也称为定界)同时对评分进行排序。
ScoreCandidates(precomputation_grid_stack_->Get(candidate_depth - 1),
discrete_scans, search_parameters,
&higher_resolution_candidates);
// 递归调用BranchAndBound对新扩展候选解进一步执行分支、定界等搜索过程。
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));
}
return best_high_resolution_candidate;
}
1.第一次分支的数量与所有子图总数相等,之后的每次分支数量都是4。