我还没写你们就开始点赞了,有点过意不去啊……
题主说MATLAB,主要原因是大多数人本科阶段接触的都是MATLAB,所以希望之后研究SLAM也用它。
MATLAB确实有很多优点:语法简单,开发速度快,调试方便,功能丰富。然而,在SLAM领域,MATLAB缺点也很明显,主要是这两个:
而相对的,C++的优势在于直接使用,有很高的运行效率,不过开发速度和调试方面慢于MATLAB。不过光运行效率这一条,就够许多SLAM方案选择C++作为开发语言了,因为运行效率真的很重要。同一个算法,拿MATLAB写出来实现不能实时,拿C++写的能实时,你说用哪个?
当然MATLAB也有一些用武之地。我见过一些SLAM相关的公开课程,让学生用MATLAB做仿真,交作业,这没有问题,比如SLAM toolbox 。同样的,比较类似于MATLAB的Python(以及octave)亦常被用于此道。它们在开发上的快捷带来了很多便利,当你想要验证一些数学理论、思想时,这些都是不错的工具。所谓技多不压身,题主掌握MATLAB和Python当然是很棒的。
但是一牵涉到实用,你会发现几乎所有的方案都在用C++。 因为运行效率实在是太重要了。
那既然有心思学MATLAB,为什么不学好C++呢?
---------------------分割线------------------
接下来说说C++大概要学到什么程度。用程序员的话说,C++语言比较特殊,你可以说自己精通了Java,但千万不要说自己精通了C++。C++非常之博大精深,有数不清的特性,而且随着时间还会不断变化更新。不过,大多数人都用不着学会所有的C++特性,因为许多东西一辈子都用不到。
作为SLAM研究人员,我们面对的主要是算法层面的开发,所以更关心如何有效地实现各种相关的算法。而相对的,那些复杂的软件架构,设计模式,我个人认为在SLAM中倒是占次要地位的。毕竟您用SLAM的目的是计算一个位置以及建个地图,并不是要去写一套能够自动更新的、多人网上对战功能的机器人大战平台。您的主要精力可能会花在矩阵运算、分块、非线性优化的实现、图像处理上面;您可能对并发、指令集加速、GPU加速等话题感兴趣,也可以花点时间学习;你还可能想用模板来拓展你的算法,也不妨一试。相应的,很多功能性的东西,比如说UI、网络通信等等,当你用到的时候不妨接触一下,但专注于SLAM上时就不必专门去学习了。
话虽如此,SLAM所需的C++水平,大抵要高于你在书本上看到的那些个示例代码。因为那些代码是作者用来向初学者介绍语法的,所以会尽量简单。而实际见到的代码往往结合了各种奇特的技巧,乍看起来会显得高深莫测。比方说你在教科书里看的大概是这样:
int main ( int argc, char** argv )
{
vector<string> vec;
vec.push_back("abc");
for ( int i=0; i<vec.size(); i++ )
{
// ...
}
return 0;
}
你看了C++ Primer Plus,觉得C++也不过如此,并没有啥特别难以理解的地方。然而实际代码大概是这样的:
嵌套的模板类(来自g2o的块求解器):
g2o
::
BlockSolver
<
g2o
::
BlockSolverTraits
<
3
,
1
>
>::
LinearSolverType
*
linearSolver
=
new
g2o
::
LinearSolverDense
<
g2o
::
BlockSolver
<
g2o
::
BlockSolverTraits
<
3
,
1
>
>::
PoseMatrixType
>
();
g2o
::
BlockSolver
<
g2o
::
BlockSolverTraits
<
3
,
1
>
>*
solver_ptr
=
new
g2o
::
BlockSolver
<
g2o
::
BlockSolverTraits
<
3
,
1
>
>
(
linearSolver
);
g2o
::
OptimizationAlgorithmLevenberg
*
solver
=
new
g2o
::
OptimizationAlgorithmLevenberg
(
solver_ptr
);
g2o
::
SparseOptimizer
optimizer
;
optimizer
.
setAlgorithm
(
solver
);
模板元(来自ceres的自动求导):
virtual
bool
Evaluate
(
double
const
*
const
*
parameters
,
double
*
residuals
,
double
**
jacobians
)
const
{
if
(
!
jacobians
)
{
return
internal
::
VariadicEvaluate
<
CostFunctor
,
double
,
N0
,
N1
,
N2
,
N3
,
N4
,
N5
,
N6
,
N7
,
N8
,
N9
>
::
Call
(
*
functor_
,
parameters
,
residuals
);
}
return
internal
::
AutoDiff
<
CostFunctor
,
double
,
N0
,
N1
,
N2
,
N3
,
N4
,
N5
,
N6
,
N7
,
N8
,
N9
>::
Differentiate
(
*
functor_
,
parameters
,
SizedCostFunction
<
kNumResiduals
,
N0
,
N1
,
N2
,
N3
,
N4
,
N5
,
N6
,
N7
,
N8
,
N9
>::
num_residuals
(),
residuals
,
jacobians
);
}
C11新特性(来自SVO特征提取部分)
void
Frame
::
setKeyPoints
()
{
for
(
size_t
i
=
0
;
i
<
5
;
++
i
)
if
(
key_pts_
[
i
]
!=
NULL
)
if
(
key_pts_
[
i
]
->
point
==
NULL
)
key_pts_
[
i
]
=
NULL
;
std
::
for_each
(
fts_
.
begin
(),
fts_
.
end
(),
[
&
](
Feature
*
ftr
){
if
(
ftr
->
point
!=
NULL
)
checkKeyPoints
(
ftr
);
});
}
谜之运算(来自SVO的深度滤波器):
void
DepthFilter
::
updateSeed
(
const
float
x
,
const
float
tau2
,
Seed
*
seed
)
{
float
norm_scale
=
sqrt
(
seed
->
sigma2
+
tau2
);
if
(
std
::
isnan
(
norm_scale
))
return
;
boost
::
math
::
normal_distribution
<
float
>
nd
(
seed
->
mu
,
norm_scale
);
float
s2
=
1.
/
(
1.
/
seed
->
sigma2
+
1.
/
tau2
);
float
m
=
s2
*
(
seed
->
mu
/
seed
->
sigma2
+
x
/
tau2
);
float
C1
=
seed
->
a
/
(
seed
->
a
+
seed
->
b
)
*
boost
::
math
::
pdf
(
nd
,
x
);
float
C2
=
seed
->
b
/
(
seed
->
a
+
seed
->
b
)
*
1.
/
seed
->
z_range
;
float
normalization_constant
=
C1
+
C2
;
C1
/=
normalization_constant
;
C2
/=
normalization_constant
;
float
f
=
C1
*
(
seed
->
a
+
1.
)
/
(
seed
->
a
+
seed
->
b
+
1.
)
+
C2
*
seed
->
a
/
(
seed
->
a
+
seed
->
b
+
1.
);
float
e
=
C1
*
(
seed
->
a
+
1.
)
*
(
seed
->
a
+
2.
)
/
((
seed
->
a
+
seed
->
b
+
1.
)
*
(
seed
->
a
+
seed
->
b
+
2.
))
+
C2
*
seed
->
a
*
(
seed
->
a
+
1.0f
)
/
((
seed
->
a
+
seed
->
b
+
1.0f
)
*
(
seed
->
a
+
seed
->
b
+
2.0f
));
// update parameters
float
mu_new
=
C1
*
m
+
C2
*
seed
->
mu
;
seed
->
sigma2
=
C1
*
(
s2
+
m
*
m
)
+
C2
*
(
seed
->
sigma2
+
seed
->
mu
*
seed
->
mu
)
-
mu_new
*
mu_new
;
seed
->
mu
=
mu_new
;
seed
->
a
=
(
e
-
f
)
/
(
f
-
e
/
f
);
seed
->
b
=
seed
->
a
*
(
1.0f
-
f
)
/
f
;
}
我不知道你们看到这些代码是什么心情,总之我当时内心的感受是:卧槽这怎么和教科书里的完全不一样啊!而且研究了半天发现人家居然是对的啊!
[我不是很擅长贴表情图总之你们脑补一下就好]
总而言之,对C++的水平要求应该是在教科书之上的。而且这个水平的提高,多数时候建立在你不断地看别人代码、码自己代码的过程之上。它是反复练习出来的,并不是仅仅通过看书就能领会的。特别是对于视觉SLAM问题,很多时候你没法照着论文把一套方案实现出来,这很大程度上取决于你的理论和代码功底。
所以,请尽早开始学习C++,尽早开始使用C++,才是研究SLAM的正确之道。不要长期彷徨在自己的舒适区里犹豫不决,这样是没有进步的。(同样的道理亦适用于想研究SLAM但不愿意学习Linux的朋友们)
---------------------分割线------------------
题主还提到闭环检测的库,稍微列几个:
DBoW系列,来自TRO12的一篇文章,里头用k-means++训练的字典树。与OpenCV结合紧密,原理亦比较简单。
GitHub - dorian3d/DBoW2: Enhanced hierarchical bag-of-word library for C++
GitHub - rmsalinas/DBow3: Improved version of DBow2
FABMAP系列,用了Chow-Liu树,来自Cummins的一系列论文。作者自己提供过一个开源版本,OpenCV也有人实现了一个,所以一共两种。
FabMap原版
OpenCV:OpenFABMAP
DLoopDetector:在DBoW2基础上开发的回环检测库
https://github.com/dorian3d/DLoopDetector
建议题主从DBoW2或者DBoW3开始入手研究。原理和实现都相对简单一些,效果也比较好。
我自己也在写一本SLAM的书籍,里头带有大量的算法实验。现在还在草稿期,明年出版后题主可以参考一下。鉴于评论区对新书比较感兴趣,那我贴个草稿的目录吧。(由于是草稿所以最后见到的可能会有出入,而且有几节我也还没写完。)所有写着「实践部分」的都附有实例代码。