ORB-SLAM2跟蹤線程對相機輸入的每一幀圖像進行跟蹤處理,如下圖所示,主要包括4步,提取ORB特征、從上一幀或者重定位來估計初始位姿、局部地圖跟蹤和關鍵幀處理。

以下結合相關理論知識,閱讀ORB-SLAM2源代碼,從而理解ORB-SLAM2算法中ORB特征提取過程。
ORB(Oriented FAST and Rotated BRIEF)
基於特征點的方法是SLAM的前端VO的主流方法,因為其運行穩定,對光照、運動物體不敏感。特征點由關鍵點(Key-point)和描述子(Descriptor)兩部分組成。比如,當說到SIFT特征時,是指“提取SIFT關鍵點,並計算SIFT描述子”。關鍵點是指該特征點在圖像里的位置,有些特征點還具有朝向、大小等信息。描述子通常是一個向量,按照某種人為設計的方式,描述了該關鍵點周圍像素的信息。描述子是按照“外觀相似的特征應該有相似的描述子”的原則設計的。因此,只要兩個特征點的描述子在向量空間上的距離相近,就可以人為它們是同樣的特征點。
常見的特征有SIFT特征,SURF特征等。那么為什么ORB-SLAM2選擇ORB特征呢?
這是因為雖然SIFT考慮了圖像變換過程中出現的光照、尺寸、旋轉等變化,但需要較大計算量,在沒有GPU加速的情況下,很難在SLAM這種系統中進行實時計算,另一方面,SIFT特征和SURF特征是受到專利保護的,需要付費使用。
ORB特征由Ethan Rublee, Vincent Rabaud, Kurt Konolige和Gary R. Bradski在他們2011年的論文《ORB: An efficient alternative to SIFT or SURF》提出,如論文題目所述,ORB特征在計算速度、匹配性能,以及在專利要求上都可以替代SIFT和SURF。
ORB取名已經反映出其是一個結合了改良后的FAST角點提取和BRIEF描述子的算法,提取ORB特征分為兩步:
-
FAST關鍵點提取:找出圖像中的FAST角點,相較於原版的FAST,ORB中計算了特征點的主方向,為后續的BRIEF描述子增加了旋轉不變性;
-
BRIEF描述子:對上一步提取出關鍵點的周圍圖像區域進行描述。
FAST關鍵點
FAST是一種角點,主要檢測局部像素灰度變化明顯的地方,以速度快著稱。FAST只需要比較像素亮度大小,速度很快,它的檢測過程如下:
-
在圖像中選取像素p,假設它的亮度為Ip;
-
設置一個閾值T(比如Ip的20%);
-
以像素p為中心,選取半徑為3的圓上的16個像素點;
-
假如選取的圓上有連續的N個點的亮度大於Ip+T或者小於Ip−T,那么像素p可以被認為是特征點(N通常取12,即FAST-12)。
-
循環以上四步,對每一個像素執行相同的操作。
FAST角點檢測雖然速度很快,但是它存在一些問題。首先是FAST角點數量很大且不確定,因此ORB對其進行改進。ORB指定最終要提取的角點數量N,對原始FAST角點分別計算Harris響應值,然后選取前N個具有最大值的角點作為最終的角點集合。
其次,FAST不具有尺寸,因此ORB構建圖像金字塔,對圖像進行不同層次的降采樣,獲得不同分辨率的圖像,並在金字塔的每一層上檢測角點,從而獲得多尺寸特征。
FAST沒有計算旋轉,因此ORB通過計算以FAST角點O為中心的圖像塊的質心C,那么向量OC→的方向就是特征點的方向,具體值通過圖像塊的矩得到。
通過各種改進,FAST特征具有了尺寸和旋轉的描述,在ORB中,把這種改進后的FAST稱為oFAST。
BRIEF描述子
BRIEF描述子是一種二進制字符描述子,其描述向量定義如下:
p(x) 是圖像塊p中點x的強度。τ的選擇有很多種,常見的選擇方式是圍繞圖像塊中心的高斯分布。n選為256的話,fn(p)就是256維的向量。BRIEF由於使用了二進制表達,存儲起來十分方便,適用於實時的圖像匹配。原始的BRIEF描述子不具有旋轉不變性,因此在圖片發生旋轉時,匹配性能會急速下降。ORB根據之前關鍵點的方向來旋轉圖像塊,得到“steer BRIEF”。
BRIEF具有每個bit的方差很大,均值約為0.5的特性,但是“steer BRIEF”喪失了這種特性,其均值不再集中在0.5左右。可以理解為特定方向的角點關鍵點使得其產生發散。這樣會導致使用“steer BRIEF”進行匹配時的錯誤率變高,因為“steer BRIEF”的方差發生了虧損,彼此之間區分度降低。同時我們希望每個τ彼此不相干,這樣得到的BRIEF更加有區分度。
為了解決上述問題,BRIEF采用了貪婪搜索,對所有可能的τ進行搜索,找出既具有高方差,均值約為0.5,同時又不相干的τ,最終結果稱為rBRIEF。
由於考慮了旋轉和縮放,使得ORB在平移、旋轉和縮放的變換下仍具有良好的表現。同時,FAST和BRIEF的計算非常高效,使得ORB特征在實時SLAM系統中得以應用。
以下閱讀ORB-SLAM2的源代碼,理清其跟蹤線程中對ORB特征的提取過程。
函數入口
ORB-SLAM2跟蹤運行在主線程,是整個SLAM系統的基礎。主程序在初始化SLAM系統后,
// Examples/Monocular/mono_kitti.cc line:53 // Create SLAM system. It initializes all system threads and gets ready to process frames. ORB_SLAM2::System SLAM(argv[1],argv[2],ORB_SLAM2::System::MONOCULAR,true);
就可以將每一幀圖像送往跟蹤函數,如下是單目SLAM主函數調用跟蹤函數的代碼:
// Examples/Monocular/mono_kitti.cc line:84 // Pass the image to the SLAM system SLAM.TrackMonocular(im,tframe);
TrackMonocular()函數調用GrabImageMonocular()函數實現跟蹤功能:
// System.cc line:260 cv::Mat Tcw = mpTracker->GrabImageMonocular(im,timestamp);
雙目和RGB-D調用方式類似,分別是SLAM.TrackStereo(imLeftRect,imRightRect,tframe);和SLAM.TrackRGBD(imRGB,imD,tframe);
mpTracker是System類中的成員,是Tracking類的指針。mpTracker對輸入的每一幀圖像計算出對應的相機位姿,同時決定何時插入新的關鍵幀,創建新的地圖點,並且在跟蹤失效時進行重定位。mpTracker的初始化在System 類的對象SLAM初始化的構造函數中進行:
//System.cc line:86~87 //Initialize the Tracking thread //(it will live in the main thread of execution, the one that called this constructor) mpTracker = new Tracking(this, mpVocabulary, mpFrameDrawer, mpMapDrawer, mpMap, mpKeyFrameDatabase, strSettingsFile, mSensor);
那么mpTracker是如何實現上述功能的呢?我們來看Tracking類,其頭文件為Tracking.h,其定義了接口如下:
// Tracking.h line:61 // Preprocess the input and call Track(). Extract features and performs stereo matching. cv::Mat GrabImageMonocular(const cv::Mat &im, const double ×tamp);
ORB特征提取
ORB-SLAM2是一個基於特征的方法,它對輸入的圖像提取出角點的特征,如下圖所示: 
在提取出特征后,所有輸入的圖片都會刪除,系統剩下的處理流程都是基於這些特征進行的,和相機類型無關。
單目的預處理流程實現過程在cv::Mat GrabImageMonocular(const cv::Mat &im, const double ×tamp)函數中體現為:首先將im轉換為灰度圖mImGray,然后預處理提取ORB特征:
// Tracking.cc line:257~260 if(mState==NOT_INITIALIZED || mState==NO_IMAGES_YET) mCurrentFrame = Frame(mImGray,timestamp,mpIniORBextractor,mpORBVocabulary,mK,mDistCoef,mbf,mThDepth); else mCurrentFrame = Frame(mImGray,timestamp,mpORBextractorLeft,mpORBVocabulary,mK,mDistCoef,mbf,mThDepth);
得到預處理的結果mCurrentFrame,從而系統剩余部分的處理流程都是基於mCurrentFrame,和單目相機無關。mCurrentFrame是Frame類的對象,這里的預處理在Frame類的構造函數中進行。Frame類對單目相機輸入的構造函數重載形式為:
// Frame.h // Constructor for Monocular cameras. Frame(const cv::Mat &imGray, const double &timeStamp, ORBextractor* extractor,ORBVocabulary* voc, cv::Mat &K, cv::Mat &distCoef, const float &bf, const float &thDepth);
在Frame.cc文件中查看其該重載函數定義,
// Frame.cc line:181~191 // Scale Level Info mnScaleLevels = mpORBextractorLeft->GetLevels(); mfScaleFactor = mpORBextractorLeft->GetScaleFactor(); mfLogScaleFactor = log(mfScaleFactor); mvScaleFactors = mpORBextractorLeft->GetScaleFactors(); mvInvScaleFactors = mpORBextractorLeft->GetInverseScaleFactors(); mvLevelSigma2 = mpORBextractorLeft->GetScaleSigmaSquares(); mvInvLevelSigma2 = mpORBextractorLeft->GetInverseScaleSigmaSquares(); // ORB extraction ExtractORB(0,imGray);
其先提取ORB特征參數,然后調用Frame類成員函數ExtractORB()來提取ORB特征,ORB特征參數存儲在配置文件中,在mpTracker的初始化中加載讀入,並傳入Frame的構造函數中。
ExtractORB()函數定義為:
// Frame.cc line:247~253
// Extract ORB on the image. 0 for left image and 1 for right image.
void Frame::ExtractORB(int flag, const cv::Mat &im)
{
if(flag==0)
(*mpORBextractorLeft)(im,cv::Mat(),mvKeys,mDescriptors);
else
(*mpORBextractorRight)(im,cv::Mat(),mvKeysRight,mDescriptorsRight);
}
其調用了ORBextractor類的重載運算符來提取ORB特征:
// ORBextractor.h line:56~61
// Compute the ORB features and descriptors on an image.
// ORB are dispersed on the image using an octree.
// Mask is ignored in the current implementation.
void operator()( cv::InputArray image, cv::InputArray mask,
std::vector<cv::KeyPoint>& keypoints,
cv::OutputArray descriptors);
ORB-SLAM提取ORB特征時采用了8層金字塔,尺寸因子為1.2。對於像素為512*384到752*480的圖片,提取1000個FAST角點,對於更高的分辨率,提取2000個FAST角點就可以了。
至此,得到當前幀ORB特征點mvKeys和描述子mDescriptors,均是Frame類對象mCurrentFrame的成員變量。提取出特征點后,需要對其去失真UndistortKeyPoints();。同時需要將圖片分割為64*48大小的柵格,並將關鍵點按照位置分配到相應柵格中,從而降低匹配時的復雜度,實現函數為AssignFeaturesToGrid(); 。
參考資料
[1] 高翔,張濤.“視覺SLAM十四講”
[2] OBR-SLAM2 github 主頁
