📝笔记:SLAM常见问题(二):重定位Relocalisation

可以说整个重定位就是一个精心设计的解算当前帧位姿的模块,秉持着不抛弃不放弃的精神,ORB-SLAM的作者简直把特征匹配压榨到了极致,仿佛在说“小伙子你有很多匹配点的,不要放弃,我们优化一下位姿再找找匹配点呗”。

原理如下流程图:

重定位

代码如下:

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
bool Tracking::Relocalization()
{
// Compute Bag of Words Vector
// 步骤1:计算当前帧特征点的Bow映射,能够得到当前帧的词袋向量以及featureVector
// 可用于SearchByBoW寻找匹配特征点
mCurrentFrame.ComputeBoW();

// Relocalization is performed when tracking is lost
// Track Lost: Query KeyFrame Database for keyframe candidates for relocalisation
// 步骤2:找到与当前帧相似的候选关键帧,
// 这里会通过查询关键帧数据库进行快速查找与当前帧相似的候选重定位帧vpCandidateKFs
vector<KeyFrame*> vpCandidateKFs = mpKeyFrameDB->DetectRelocalizationCandidates(&mCurrentFrame);

if(vpCandidateKFs.empty())
return false;

const int nKFs = vpCandidateKFs.size();

// We perform first an ORB matching with each candidate
// If enough matches are found we setup a PnP solver
ORBmatcher matcher(0.75,true);

vector<PnPsolver*> vpPnPsolvers;
vpPnPsolvers.resize(nKFs);

vector<vector<MapPoint*> > vvpMapPointMatches;
vvpMapPointMatches.resize(nKFs);

vector<bool> vbDiscarded;
vbDiscarded.resize(nKFs);

int nCandidates=0;

for(int i=0; i<nKFs; i++)
{
KeyFrame* pKF = vpCandidateKFs[i];
if(pKF->isBad())
vbDiscarded[i] = true;
else
{
// 步骤3:通过BoW进行匹配
// 利用SearchByBoW查找当前帧与关键帧的匹配点vvpMapPointMatches
int nmatches = matcher.SearchByBoW(pKF,mCurrentFrame,vvpMapPointMatches[i]);
// 如果匹配点数小于15个点,跳过
if(nmatches<15)
{
vbDiscarded[i] = true;
continue;
}
// 如果匹配点数大于15个点,建立当前帧与关键帧之间的PNP求解器;
// 仅仅如建立这个求解器,还未求解
else
{
// 初始化PnPsolver
PnPsolver* pSolver = new PnPsolver(mCurrentFrame,vvpMapPointMatches[i]);
pSolver->SetRansacParameters(0.99,10,300,4,0.5,5.991);
vpPnPsolvers[i] = pSolver;
nCandidates++;
}
}
}

// Alternatively perform some iterations of P4P RANSAC
// Until we found a camera pose supported by enough inliers
bool bMatch = false;
ORBmatcher matcher2(0.9,true);

// 如果候选关键帧数大于0且没有重定位成功
while(nCandidates>0 && !bMatch)
{
for(int i=0; i<nKFs; i++)
{
// 若当前候选关键帧与当前帧匹配数量小于15,跳过
if(vbDiscarded[i])
continue;

// Perform 5 Ransac Iterations
vector<bool> vbInliers;
int nInliers;
bool bNoMore;

// 步骤4:通过EPnP算法估计初始位姿
PnPsolver* pSolver = vpPnPsolvers[i];
cv::Mat Tcw = pSolver->iterate(5,bNoMore,vbInliers,nInliers);

// If Ransac reachs max. iterations discard keyframe
// 若RANSAC失败,当前候选关键帧被提出候选帧
if(bNoMore)
{
vbDiscarded[i]=true;
nCandidates--;
}

// If a Camera Pose is computed, optimize
// PNP求解出了一个比较初始的位姿,比较粗糙,需要进一步优化
if(!Tcw.empty())
{
// 把刚刚PNP求解的位姿赋给当前帧位姿
Tcw.copyTo(mCurrentFrame.mTcw);

set<MapPoint*> sFound;

const int np = vbInliers.size();

for(int j=0; j<np; j++)
{
if(vbInliers[j])
{
mCurrentFrame.mvpMapPoints[j]=vvpMapPointMatches[i][j];
sFound.insert(vvpMapPointMatches[i][j]);
}
else
mCurrentFrame.mvpMapPoints[j]=NULL;
}

// 步骤5:通过PoseOptimization对姿态进行优化求解
int nGood = Optimizer::PoseOptimization(&mCurrentFrame);
// 内点小于10,跳过
if(nGood<10)
continue;
// 刚才的PO优化会滤除一些外点
for(int io =0; io<mCurrentFrame.N; io++)
if(mCurrentFrame.mvbOutlier[io])
mCurrentFrame.mvpMapPoints[io]=static_cast<MapPoint*>(NULL);

// If few inliers, search by projection in a coarse window and optimize again
// 步骤6:如果内点较少,则通过投影的方式对之前未匹配的点进行匹配,再进行优化求解
// 作者认为10<=nGood<50时仍有可能重定位成功,由于PO调整了位姿,
// 可以通过位姿投影的方式将候选关键帧上的地图点投影在当前帧上进行搜索匹配点,
// 从而增加匹配,然后再优化以得到足够多的内点
if(nGood<50)
{
int nadditional =matcher2.SearchByProjection(mCurrentFrame,vpCandidateKFs[i],sFound,10,100);
// 新增的点与之前PO内点之和大于50,我们考虑再进行一遍优化
if(nadditional+nGood>=50)
{
nGood = Optimizer::PoseOptimization(&mCurrentFrame);

// If many inliers but still not enough, search by projection again in a narrower window
// the camera has been already optimized with many points
// 不够多呀,不要放弃,再来一遍
if(nGood>30 && nGood<50)
{
sFound.clear();
for(int ip =0; ip<mCurrentFrame.N; ip++)
if(mCurrentFrame.mvpMapPoints[ip])
sFound.insert(mCurrentFrame.mvpMapPoints[ip]);
nadditional =matcher2.SearchByProjection(mCurrentFrame,vpCandidateKFs[i],sFound,3,64);

// Final optimization
// 最后一次优化啦~
if(nGood+nadditional>=50)
{
nGood = Optimizer::PoseOptimization(&mCurrentFrame);

for(int io =0; io<mCurrentFrame.N; io++)
if(mCurrentFrame.mvbOutlier[io])
mCurrentFrame.mvpMapPoints[io]=NULL;
}
}
}
}

// If the pose is supported by enough inliers stop ransacs and continue
// 只要找到一个候选关键帧与当前帧的匹配点数大于50就重定位成功!
if(nGood>=50)
{
bMatch = true;
break;
}
}
}
}

if(!bMatch)
{
return false;
}
else
{
mnLastRelocFrameId = mCurrentFrame.mnId;
return true;
}

}