我還沒寫你們就開始點贊了,有點過意不去啊……
題主說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的書籍,裏頭帶有大量的演算法實驗。現在還在草稿期,明年出版後題主可以參考一下。鑒於評論區對新書比較感興趣,那我貼個草稿的目錄吧。(由於是草稿所以最後見到的可能會有出入,而且有幾節我也還沒寫完。)所有寫著「實踐部份」的都附有例項程式碼。