参考网址:

peitianyu/lite_slam (github.com)

scan_context | 某飞行员的随笔 (peitianyu.github.io)

ScanContext

代码流程

1
2
3
4
5
6
7
8
9
10
11
- 构建KeyFrame(id, pose, key, scan_context)
- 输入(id, pose, scan)MakeScanContext构造scan_context
- 这里key使用scan_context在ring方向上pca构造
- AddKeyFrame添加关键帧用于之后的匹配
- DetectLoopClosure()寻找回环id并输出回环坐标

- 关键:
* scan_context构建
* key构建
* nanoflann构建kdtree并搜索key
* AlignScanContext()对齐scan_context获得先验坐标

原理简述

1
2
3
4
1. 将距离分成60份,角度分成60份,组成60*60矩阵,然后对距离pca化得到60维向量
2. 通过kdtree搜索key找到回环
3. 通过对齐60*60矩阵找到粗匹配,距离偏差与角度偏差,得到d_pose
4. 在loop_pose基础变换得到粗匹配值

代码体现

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// AddKeyFrame()
void ScanContext::AddKeyFrame(const size_t &id, const Eigen::Vector3f& curr_pose, const std::vector<Eigen::Vector2f> &scan)
{
Eigen::MatrixXf scan_context = MakeScanContext(scan);
KeyFrame key_frame = KeyFrame{id, curr_pose, scan_context};
m_key_frames->push_back(key_frame);
}

// 构建pca_key
Eigen::VectorXf CalculatePcaForKeyResult()
{
Eigen::MatrixXf tmp_scan_context = scan_context;
tmp_scan_context.colwise() -= tmp_scan_context.rowwise().mean();
Eigen::MatrixXf cov = tmp_scan_context.transpose() * tmp_scan_context * (1 / tmp_scan_context.rows() - 1);
Eigen::SelfAdjointEigenSolver<Eigen::MatrixXf> eigen_solver(cov);

return eigen_solver.eigenvectors().rightCols(1);
}

// 回环流程
const int ScanContext::DetectLoopClosure(const std::vector<Eigen::Vector2f> &scan, Eigen::Vector3f &loop_pose)
{
Eigen::MatrixXf scan_context = MakeScanContext(scan);
KeyFrame key_frame = KeyFrame{std::numeric_limits<size_t>::max(), Eigen::Vector3f::Zero(), scan_context};

if(m_key_frames->size() < m_params.num_exclude_recent + 1){ // 排除最接近的一定数量特征匹配
return -1;
}

std::vector<size_t> indexes(m_params.leaf_max_size);
FindNearestNeighbor(key_frame, *m_key_frames, indexes); // nanoflann搜索

float min_dist = std::numeric_limits<float>::max(); // 求最近的一个
size_t nearest_id = 0;
for(size_t &index: indexes){
float dist = (m_key_frames->key_frames[index].key_pose.head(2) - key_frame.key_pose.head(2)).norm();
if(dist < min_dist){
min_dist = dist; nearest_id = index;
}
}

if(min_dist < m_params.min_dist){
Eigen::Vector3f delta_pose = AlignScanContext(scan_context, m_key_frames->key_frames[nearest_id].scan_context);
loop_pose = TransformAdd(m_key_frames->key_frames[nearest_id].key_pose, delta_pose); //通过对齐输出回环先验坐标
NormalizePose(loop_pose);
return m_key_frames->key_frames[nearest_id].id;
}

return -1;
}

// 构建scan_context
Eigen::MatrixXf ScanContext::MakeScanContext(const std::vector<Eigen::Vector2f> &scan)
{
Eigen::MatrixXf scan_context(m_params.num_ring, m_params.num_sector);
scan_context.setZero();

for(auto point: scan)
{
float angle = std::atan2(point(0), point(1)) / M_PI * 180.0f + 180.0f;
float dist = std::hypot(point(0), point(1));

if(dist >= m_params.min_range && dist <= m_params.max_range){
int ring = std::max(std::min(m_params.num_ring - 1, static_cast<int>(round((dist / m_params.max_range) * m_params.num_ring))), 0);
int sector = std::max(std::min(m_params.num_sector - 1, static_cast<int>(round((angle / 360.0f) * m_params.num_sector))), 0);

scan_context(ring, sector) += 1;
}
}
return scan_context;
}

// 对齐scan_context用于输出先验坐标
Eigen::Vector3f ScanContext::AlignScanContext(const Eigen::MatrixXf &curr_scan_context, const Eigen::MatrixXf &loop_scan_context)
{
Eigen::MatrixXf tmp_scan_context = loop_scan_context;
float sum = std::numeric_limits<float>::max();
int d_sector = 0;
for(int i = 0; i < m_params.num_sector; i++) // 对列进行查找最接近的,求解偏差角度
{
tmp_scan_context.setZero();
tmp_scan_context.rightCols(i) = loop_scan_context.leftCols(i);
tmp_scan_context.leftCols(m_params.num_sector - i) = loop_scan_context.rightCols(m_params.num_sector - i);

float d_sum = 0;
for(int j = 0; j < m_params.num_sector; j++){
d_sum += fabs(tmp_scan_context.col(j).sum() - curr_scan_context.col(j).sum());
}

if( sum > d_sum){
sum = d_sum; d_sector = i;
}
}

sum = std::numeric_limits<float>::max();
int d_ring = 0;
for(int i = 0; i < m_params.num_ring; i++)// 对行进行查找最接近的,求解偏差距离
{
tmp_scan_context.setZero();
tmp_scan_context.topRows(i) = loop_scan_context.bottomRows(i);
tmp_scan_context.bottomRows(m_params.num_ring - i) = loop_scan_context.topRows(m_params.num_ring - i);

float d_sum = 0;
for(int j = 0; j < m_params.num_ring; j++){
d_sum += fabs(tmp_scan_context.row(j).sum() - curr_scan_context.row(j).sum());
}

if( sum > d_sum){
sum = d_sum; d_ring = i;
}
}

int tmp_d_ring = m_params.num_ring - d_ring;
if(tmp_d_ring > m_params.num_ring / 2) tmp_d_ring - m_params.num_ring; // 求解距离偏差id


float angle = static_cast<float>((d_sector + 0.5) * 360 / m_params.num_sector) / 180.0f * M_PI; // 求解预测偏差角度
float min_dist = static_cast<float>(tmp_d_ring + 0.5) * m_params.max_range / m_params.num_ring; // 求解预测偏差距离
if(angle > M_PI) angle -= 2 * M_PI;

return Eigen::Vector3f(min_dist * cos(angle), min_dist * sin(angle), angle); // 输出d_pose
}