pos機(jī)一直顯示網(wǎng)絡(luò)正在初始化,SLAM3 單目地圖初始化

 新聞資訊2  |   2023-05-25 13:20  |  投稿人:pos機(jī)之家

網(wǎng)上有很多關(guān)于pos機(jī)一直顯示網(wǎng)絡(luò)正在初始化,SLAM3 單目地圖初始化的知識,也有很多人為大家解答關(guān)于pos機(jī)一直顯示網(wǎng)絡(luò)正在初始化的問題,今天pos機(jī)之家(www.shineka.com)為大家整理了關(guān)于這方面的知識,讓我們一起來看下吧!

本文目錄一覽:

1、pos機(jī)一直顯示網(wǎng)絡(luò)正在初始化

pos機(jī)一直顯示網(wǎng)絡(luò)正在初始化

來源:公眾號|計(jì)算機(jī)視覺工坊(系投稿)

作者:喬不思

「3D視覺工坊」技術(shù)交流群已經(jīng)成立,目前大約有12000人,方向主要涉及3D視覺、CV&深度學(xué)習(xí)、SLAM、三維重建、點(diǎn)云后處理、自動駕駛、CV入門、三維測量、VR/AR、3D人臉識別、醫(yī)療影像、缺陷檢測、行人重識別、目標(biāo)跟蹤、視覺產(chǎn)品落地、視覺競賽、車牌識別、硬件選型、學(xué)術(shù)交流、求職交流、ORB-SLAM系列源碼交流、深度估計(jì)等。工坊致力于干貨輸出,不做搬運(yùn)工,為計(jì)算機(jī)視覺領(lǐng)域貢獻(xiàn)自己的力量!歡迎大家一起交流成長~

一、前言

請閱讀本文之前最好把ORB-SLAM3的單目初始化過程再過一遍(ORB-SLAM3 細(xì)讀單目初始化過程(上)、超詳細(xì)解讀ORB-SLAM3單目初始化(下篇)),以提高學(xué)習(xí)效率。單目初始化過程中最重要的是兩個(gè)函數(shù)實(shí)現(xiàn),分別是構(gòu)建幀(Frame)和初始化(Track)。接下來,就是完成初始化過程的最后一步:地圖的初始化,是由CreateInitialMapMonocular函數(shù)完成的,本文基于該函數(shù)的流程出發(fā),目的是為了結(jié)合代碼流程,把單目初始化的上下兩篇的知識點(diǎn)和ORB-SLAM3整個(gè)系統(tǒng)的知識點(diǎn)串聯(lián)起來,系統(tǒng)化零碎的知識,告訴你平時(shí)學(xué)到的各個(gè)小知識應(yīng)用在SLAM系統(tǒng)中的什么位置,達(dá)到快速高效學(xué)習(xí)的效果。

二、CreateInitialMapMonocular 函數(shù)的總體流程1. 將初始關(guān)鍵幀,當(dāng)前關(guān)鍵幀的描述子轉(zhuǎn)為BoW;2. 將關(guān)鍵幀插入到地圖;3. 用三角測量初始化得到的3D點(diǎn)來生成地圖點(diǎn),更新關(guān)鍵幀間的連接關(guān)系;4. 全局BA優(yōu)化,同時(shí)優(yōu)化所有位姿和三維點(diǎn);5. 取場景的中值深度,用于尺度歸一化;6. 將兩幀之間的變換歸一化到平均深度1的尺度下;7. 把3D點(diǎn)的尺度也歸一化到1;8. 將關(guān)鍵幀插入局部地圖,更新歸一化后的位姿、局部地圖點(diǎn)。三、必備知識1. 為什么單目需要專門策略生成初始化地圖

根據(jù)論文《ORB-SLAM: a Versatile and Accurate Monocular SLAM System》,即ORB-SLAM1的論文(中文翻譯版[ORB-SLAM: a Versatile and Accurate Monocular SLAM System](https://blog.csdn.net/weixin_42905141/article/details/102857958))可知:

1) 單目SLAM系統(tǒng)需要設(shè)計(jì)專門的策略來生成初始化地圖,這也是為什么代碼中單獨(dú)設(shè)計(jì)一個(gè)CreateInitialMapMonocular()函數(shù)來實(shí)現(xiàn)單目初始化,也是我們這篇文章要討論的。為什么要單獨(dú)設(shè)計(jì)呢?就是因?yàn)閱文繘]有深度信息。2) 怎么解決單目沒有深度信息問題?有2種,論文用的是第二種,用一個(gè)具有高不確定度的逆深度參數(shù)來初始化點(diǎn)的深度信息,該參數(shù)會在后期逐漸收斂到真值。3) 說了ORB-SLAM為什么要同時(shí)計(jì)算基礎(chǔ)矩陣F和單應(yīng)矩陣H的原因:這兩種攝像頭位姿重構(gòu)方法在低視差下都沒有很好的約束,所以提出了一個(gè)新的基于模型選擇的自動初始化方法,對平面場景算法選擇單應(yīng)性矩陣,而對于非平面場景,算法選擇基礎(chǔ)矩陣。4)說了ORB-SLAM初始化容易失敗的原因:(條件比較苛刻)在平面的情況下,為了保險(xiǎn)起見,如果最終存在雙重歧義,則算法避免進(jìn)行初始化,因?yàn)榭赡軙驗(yàn)殄e(cuò)誤選擇而導(dǎo)致算法崩潰。因此,我們會延遲初始化過程,直到所選的模型在明顯的視差下產(chǎn)生唯一的解。2. 共視圖 Covisibility Graph

共視圖非常的關(guān)鍵,需要先理解共視圖,才能更好的理解后續(xù)程序中如何設(shè)置頂點(diǎn)和邊。

2.1 共視圖定義

共視圖是無向加權(quán)圖,每個(gè)節(jié)點(diǎn)是關(guān)鍵幀,如果兩個(gè)關(guān)鍵幀之間滿足一定的共視關(guān)系(至少15個(gè)共同觀測地圖點(diǎn))他們就連成一條邊,邊的權(quán)重就是共視地圖點(diǎn)數(shù)目。

2.2 共視圖作用2.2.1 跟蹤局部地圖,擴(kuò)大搜索范圍

? Tracking::UpdateLocalKeyFrames()

2.2.2 局部建圖里關(guān)鍵幀之間新建地圖點(diǎn)

? LocalMapping::CreateNewMapPoints()

? LocalMapping::SearchInNeighbors()

2.2.3 閉環(huán)檢測、重定位檢測

? LoopClosing::DetectLoop()、LoopClosing::CorrectLoop()

? KeyFrameDatabase::DetectLoopCandidates

? KeyFrameDatabase::DetectRelocalizationCandidates

2.2.4 優(yōu)化

? Optimizer::OptimizeEssentialGraph

3. 地圖點(diǎn) MapPoint 和關(guān)鍵幀 KeyFrame

地圖點(diǎn)云保存以下信息:

1)它在世界坐標(biāo)系中的3D坐標(biāo)

2) 視圖方向,即所有視圖方向的平均單位向量(該方向是指連接該點(diǎn)云和其對應(yīng)觀測關(guān)鍵幀光心的射線方向)

3)ORB特征描述子,與其他所有能觀測到該點(diǎn)云的關(guān)鍵幀中ORB描述子相比,該描述子的漢明距離最小

4)根據(jù)ORB特征尺度不變性約束,可觀測的點(diǎn)云的最大距離和最小距離

4. 圖優(yōu)化 Graph SLAM

可先看看這些資料[《計(jì)算機(jī)視覺大型攻略 —— SLAM(2) Graph-based SLAM(基于圖優(yōu)化的算法)》](https://blog.csdn.net/plateros/article/details/103498039),還有《概率機(jī)器人學(xué)》的第11章,深入理解圖優(yōu)化的概念。

我們在文章開頭說過,單目初始化結(jié)果得到了三角測量初始化得到的3D地圖點(diǎn)Pw,計(jì)算得到了初始兩幀圖像之間的相對位姿(相當(dāng)于得到了SE(3)),通過相機(jī)坐標(biāo)系Pc和世界坐標(biāo)系Pw之間的公式,(參考[《像素坐標(biāo)系、圖像坐標(biāo)系、相機(jī)坐標(biāo)系和世界坐標(biāo)系的關(guān)系(簡單易懂版)》](https://blog.csdn.net/shanpenghui/article/details/110481140))

得到相機(jī)坐標(biāo)系的坐標(biāo)Pc,但是這樣還是不能和像素坐標(biāo)比較。我們接著通過相機(jī)坐標(biāo)系Pc和像素坐標(biāo)系P(u,v)之間的公式

5. g2o使用方法

關(guān)于g2o庫的使用方法,可以參考[《G2O圖優(yōu)化基礎(chǔ)和SLAM的Bundle Adjustment(光束平差)》](http://zhaoxuhui.top/blog/2018/04/10/g2o&bundle_adjustment.html#2g2o庫簡介與編譯安裝)和[《理解圖優(yōu)化,一步步帶你看懂g2o代碼》](https://www.cnblogs.com/CV-life/p/10286037.html)。一般來說,g2o的使用流程如下:

5.1創(chuàng)建一個(gè)線性求解器LinearSolver5.2創(chuàng)建BlockSolver,并用上面定義的線性求解器LinearSolver初始化5.3創(chuàng)建總求解器solver,并從GN, LM, DogLeg 中選一個(gè),再用上述塊求解器BlockSolver初始化5.4創(chuàng)建終極大boss 稀疏優(yōu)化器(SparseOptimizer),并用已定義的總求解器solver作為求解方法5.5定義圖的頂點(diǎn)和邊,并添加到稀疏優(yōu)化器(SparseOptimizer)中5.6設(shè)置優(yōu)化參數(shù),開始執(zhí)行優(yōu)化四、代碼1. 將初始關(guān)鍵幀,當(dāng)前關(guān)鍵幀的描述子轉(zhuǎn)為BoW

pKFini->ComputeBoW();pKFcur->ComputeBoW();

不展開詞袋BoW,只需要知道一點(diǎn),就是我們在回環(huán)檢測的時(shí)候,需要用到詞袋向量mBowVec和特征點(diǎn)向量mFeatVec,所以這里要計(jì)算。

2. 向地圖添加關(guān)鍵幀

mpAtlas->AddKeyFrame(pKFini);mpAtlas->AddKeyFrame(pKFcur);3. 生成地圖點(diǎn),更新圖(節(jié)點(diǎn)和邊)3.1 遍歷

for(size_t i=0; i<mvIniMatches.size();i++)

因?yàn)橐萌菧y量初始化得到的3D點(diǎn),所以外圍是一個(gè)大的循環(huán),遍歷三角測量初始化得到的3D點(diǎn)mvIniP3D。

3.2 檢查

if(mvIniMatches[i]<0)continue;

沒有匹配的點(diǎn),則跳過。

3.3 構(gòu)造點(diǎn)

cv::Mat worldPos(mvIniP3D[i]);

用三角測量初始化得到的3D點(diǎn)mvIniP3D[i]作為空間點(diǎn)的世界坐標(biāo) worldPos。

MapPoint* pMP = new MapPoint(worldPos,pKFcur,mpAtlas->GetCurrentMap());

然后用空間點(diǎn)的世界坐標(biāo) worldPos構(gòu)造地圖點(diǎn) pMP。

3.4 修改點(diǎn)屬性3.4.1 添加可以觀測到該地圖點(diǎn)pMP的關(guān)鍵幀

pMP->AddObservation(pKFini,i);pMP->AddObservation(pKFcur,mvIniMatches[i]);3.4.2 計(jì)算該地圖點(diǎn)pMP的描述子

pMP->ComputeDistinctiveDescriptors();

因?yàn)镺RBSLAM是特征點(diǎn)方法,描述子非常重要,但是一個(gè)地圖點(diǎn)有非常多能觀測到該點(diǎn)的關(guān)鍵幀,每個(gè)關(guān)鍵幀都有相對該地圖點(diǎn)的值(距離和角度)不一樣的描述子,在這么多的描述子中,如何選取一個(gè)最能代表該點(diǎn)的描述子呢?這里作者用了距離中值法,意思就是說,最能代表該地圖點(diǎn)的描述子,應(yīng)該是與其他描述子具有最小的距離中值。

舉個(gè)栗子,現(xiàn)有描述子A、B、C、D、E、F、G,它們之間的距離分別是1、1、2、3、4、5,求最小距離中值的描述子:

把它們的距離做成2維vector行列的形式,如下:

對每個(gè)關(guān)鍵幀得到的描述子與其他描述子的距離進(jìn)行排序。然后,中位數(shù)是median = vDists[0.5*(N-1)]=0.5×(7-1)=3,得到:

可以看到,描述子B具有最小距離中值,所以選擇描述子B作為該地圖點(diǎn)的描述子。

上述例子比較容易理解,但實(shí)際問題是,描述子是一個(gè)值,如何描述一個(gè)值和另一個(gè)值的距離呢?我們可以把兩個(gè)值看成是兩個(gè)二進(jìn)制串,而描述兩個(gè)二進(jìn)制串之間的距離可以用漢明距離,指的是其不同位數(shù)的個(gè)數(shù)。這樣,我們就可以求出兩個(gè)描述子之間的距離了。

3.4.3 更新該地圖點(diǎn)pMP的平均觀測方向和深度范圍

pMP->UpdateNormalAndDepth();

知道方法之后,我們看程序里面MapPoint::UpdateNormalAndDepth()如何實(shí)現(xiàn):

3.4.3.1 獲取地圖點(diǎn)信息

observations=mObservations; // 獲得觀測到該地圖點(diǎn)的所有關(guān)鍵幀pRefKF=mpRefKF; // 觀測到該點(diǎn)的參考關(guān)鍵幀(第一次創(chuàng)建時(shí)的關(guān)鍵幀)Pos = mWorldPos.clone(); // 地圖點(diǎn)在世界坐標(biāo)系中的位置

我們要獲得觀測到該地圖點(diǎn)的所有關(guān)鍵幀,用來找到每個(gè)關(guān)鍵幀的光心Owi。還要獲得觀測到該點(diǎn)的參考關(guān)鍵幀(即第一次創(chuàng)建時(shí)的關(guān)鍵幀),因?yàn)檫@里只是更新觀測方向,距離還是用參考關(guān)鍵幀到該地圖點(diǎn)的距離,體現(xiàn)在后面dist = cv::norm(Pos - pRefKF->GetCameraCenter())。還要獲得地圖點(diǎn)在世界坐標(biāo)系中的位置,用來計(jì)算法向量。

3.4.3.2 計(jì)算該地圖點(diǎn)的法向量

cv::Mat normal = cv::Mat::zeros(3,1,CV_32F);int n=0;for(map<KeyFrame*,size_t>::iterator mit=observations.begin(), mend=observations.end(); mit!=mend; mit++){KeyFrame* pKF = mit->first;tuple<int,int> indexes = mit -> second;int leftIndex = get<0>(indexes), rightIndex = get<1>(indexes);if(leftIndex != -1){cv::Mat Owi = pKF->GetCameraCenter();cv::Mat normali = mWorldPos - Owi;normal = normal + normali/cv::norm(normali);n++;}if(rightIndex != -1){cv::Mat Owi = pKF->GetRightCameraCenter();cv::Mat normali = mWorldPos - Owi;normal = normal + normali/cv::norm(normali);n++;}}3.4.3.3 計(jì)算該地圖點(diǎn)到圖像的距離

cv::Mat PC = Pos - pRefKF->GetCameraCenter();const float dist = cv::norm(PC);

計(jì)算參考關(guān)鍵幀相機(jī)指向地圖點(diǎn)的向量,利用該向量求該地圖點(diǎn)的距離。

3.4.3.4 更新該地圖點(diǎn)的距離上下限

    // 觀測到該地圖點(diǎn)的當(dāng)前幀的特征點(diǎn)在金字塔的第幾層    tuple<int ,int> indexes = observations[pRefKF];    int leftIndex = get<0>(indexes), rightIndex = get<1>(indexes);    int level;    if(pRefKF -> NLeft == -1){        level = pRefKF->mvKeysUn[leftIndex].octave;    }    else if(leftIndex != -1){        level = pRefKF -> mvKeys[leftIndex].octave;    }    else{        level = pRefKF -> mvKeysRight[rightIndex - pRefKF -> NLeft].octave;    }    //const int level = pRefKF->mvKeysUn[observations[pRefKF]].octave;            const float levelScaleFactor =  pRefKF->mvScaleFactors[level];          // 當(dāng)前金字塔層對應(yīng)的縮放倍數(shù)    const int nLevels = pRefKF->mnScaleLevels;                              // 金字塔層數(shù)    {        unique_lock<mutex> lock3(mMutexPos);        // 使用方法見PredictScale函數(shù)前的注釋        mfMaxDistance = dist*levelScaleFactor;                              // 觀測到該點(diǎn)的距離上限        mfMinDistance = mfMaxDistance/pRefKF->mvScaleFactors[nLevels-1];    // 觀測到該點(diǎn)的距離下限        mNormalVector = normal/n;                                           // 獲得地圖點(diǎn)平均的觀測方向    }

回顧之前的知識:

3.5 添加地圖點(diǎn)到地圖

mpAtlas->AddMapPoint(pMP);3.6 更新圖

非常重要的知識點(diǎn),好好琢磨,該過程由函數(shù)UpdateConnections完成,深入其中看看有什么奧妙。

3.6.1 統(tǒng)計(jì)共視幀

// 遍歷每一個(gè)地圖點(diǎn)for(vector<MapPoint*>::iterator vit=vpMP.begin(), vend=vpMP.end(); vit!=vend; vit++)...     // 統(tǒng)計(jì)與當(dāng)前關(guān)鍵幀存在共視關(guān)系的其他幀     map<KeyFrame*,size_t> observations = pMP->GetObservations();     for(map<KeyFrame*,size_t>::iterator mit=observations.begin(), mend=observations.end(); mit!=mend; mit++)     ...     // 體現(xiàn)了作者的編程功底,很強(qiáng)     KFcounter[mit->first]++;

這里代碼主要是想完成遍歷每一個(gè)地圖點(diǎn),統(tǒng)計(jì)與當(dāng)前關(guān)鍵幀存在共視關(guān)系的其他幀,統(tǒng)計(jì)結(jié)果放在KFcounter。看代碼有點(diǎn)費(fèi)勁,舉個(gè)栗子:已知有一關(guān)鍵幀F(xiàn)1,上面有四個(gè)地圖點(diǎn)ABCD,其中,能觀測到點(diǎn)A的關(guān)鍵幀是有3個(gè),分別是幀F(xiàn)1、F2、F3。能觀測到點(diǎn)B的關(guān)鍵幀是有4個(gè),分別是幀F(xiàn)1、F2、F3、F4。能觀測到點(diǎn)C的關(guān)鍵幀是有5個(gè),分別是幀F(xiàn)1、F2、F3、F4、F5。能觀測到點(diǎn)D的關(guān)鍵幀是有6個(gè),分別是幀F(xiàn)1、F2、F3、F4、F5、F6。對應(yīng)關(guān)系如下:

總而言之,代碼想統(tǒng)計(jì)的就是與當(dāng)前關(guān)鍵幀存在共視關(guān)系的其他幀,共視關(guān)系是通過能看到同個(gè)特征點(diǎn)來描述的,所以,當(dāng)前幀F(xiàn)1與幀F(xiàn)2的共視關(guān)系為4,當(dāng)前幀F(xiàn)1與幀F(xiàn)3的共視關(guān)系為4,當(dāng)前幀F(xiàn)1與幀F(xiàn)4的共視關(guān)系為3,當(dāng)前幀F(xiàn)1與幀F(xiàn)5的共視關(guān)系為2,當(dāng)前幀F(xiàn)1與幀F(xiàn)6的共視關(guān)系為1。

3.6.2 更新共視關(guān)系大于一定閾值的邊,并找到共視程度最高的關(guān)鍵幀

    for(map<KeyFrame*,int>::iterator mit=KFcounter.begin(), mend=KFcounter.end(); mit!=mend; mit++)    {        ...        // 找到共視程度最高的關(guān)鍵幀        if(mit->second>nmax)        {            nmax=mit->second;            pKFmax=mit->first;        }        if(mit->second>=th)        {            // 更新共視關(guān)系大于一定閾值的邊            vPairs.push_back(make_pair(mit->second,mit->first));            // 更新其它關(guān)鍵幀與當(dāng)前幀的連接權(quán)重            (mit->first)->AddConnection(this,mit->second);        }    }

假設(shè)共視關(guān)系閾值為1,在上面這個(gè)例子中,只要和當(dāng)前幀有共視關(guān)系的幀都需要更新邊,由于在這之前,關(guān)鍵幀只和地圖點(diǎn)之間有連接關(guān)系,和其他幀沒有連接關(guān)系,要構(gòu)建共視圖(以幀為節(jié)點(diǎn),以共視關(guān)系為邊)就要一個(gè)個(gè)更新節(jié)點(diǎn)之間的邊的值。

(mit->first)->AddConnection(this,mit->second)的含義是更新其他幀F(xiàn)i和當(dāng)前幀F(xiàn)1的邊(因?yàn)楫?dāng)前幀F(xiàn)1也被當(dāng)做其他幀F(xiàn)i的有共視關(guān)系的一個(gè))。在遍歷查找共視關(guān)系最大幀的時(shí)候同步做這個(gè)事情,可以加速計(jì)算和高效利用代碼。mit->first在這里,代表和當(dāng)前幀有共視關(guān)系的F2...F6(因?yàn)楸闅v的是KFcounter,存儲著與當(dāng)前幀F(xiàn)1有共視關(guān)系的幀F(xiàn)2...F6)。舉個(gè)栗子,當(dāng)處理當(dāng)前幀F(xiàn)1和共視幀F(xiàn)2時(shí),更新與幀F(xiàn)2有共視關(guān)系的幀F(xiàn)1,以此類推,當(dāng)處理當(dāng)前幀F(xiàn)1和共視幀F(xiàn)3時(shí),更新與幀F(xiàn)3有共視關(guān)系的幀F(xiàn)1....。

3.6.3 如果沒有連接到關(guān)鍵幀(沒有超過閾值的權(quán)重),則連接權(quán)重最大的關(guān)鍵幀

    if(vPairs.empty())    {        vPairs.push_back(make_pair(nmax,pKFmax));        pKFmax->AddConnection(this,nmax);    }

如果每個(gè)關(guān)鍵幀與它共視的關(guān)鍵幀的個(gè)數(shù)都少于給定的閾值,那就只更新與其它關(guān)鍵幀共視程度最高的關(guān)鍵幀的 mConnectedKeyFrameWeights,以免之前這個(gè)閾值可能過高造成當(dāng)前幀沒有共視幀,容易造成跟蹤失???(自己猜的)

3.6.4 對共視程度比較高的關(guān)鍵幀對更新連接關(guān)系及權(quán)重(從大到?。?/strong>

    sort(vPairs.begin(),vPairs.end());    // 將排序后的結(jié)果分別組織成為兩種數(shù)據(jù)類型    list<KeyFrame*> lKFs;    list<int> lWs;    for(size_t i=0; i<vPairs.size();i++)    {        // push_front 后變成了從大到小順序        lKFs.push_front(vPairs[i].second);        lWs.push_front(vPairs[i].first);    }3.6.5 更新當(dāng)前幀的信息

   ...   mConnectedKeyFrameWeights = KFcounter;   mvpOrderedConnectedKeyFrames = vector<KeyFrame*>(lKFs.begin(),lKFs.end());   mvOrderedWeights = vector<int>(lWs.begin(), lWs.end());3.6.6 更新生成樹的連接

    ...   if(mbFirstConnection && mnId!=mpMap->GetInitKFid())   {        // 初始化該關(guān)鍵幀的父關(guān)鍵幀為共視程度最高的那個(gè)關(guān)鍵幀        mpParent = mvpOrderedConnectedKeyFrames.front();        // 建立雙向連接關(guān)系,將當(dāng)前關(guān)鍵幀作為其子關(guān)鍵幀        mpParent->AddChild(this);        mbFirstConnection = false;   }4. 全局BA

全局BA主要是由函數(shù)GlobalBundleAdjustemnt完成的,其調(diào)用了函數(shù)BundleAdjustment,建議開始閱讀之前復(fù)習(xí)一下文章前面的《二、4. 圖優(yōu)化 Graph SLAM》和《二、5. g2o使用方法》,下文直接從函數(shù)BundleAdjustment展開敘述。

// 調(diào)用    Optimizer::GlobalBundleAdjustemnt(mpAtlas->GetCurrentMap(),20);// 定義void Optimizer::GlobalBundleAdjustemnt(Map* pMap, int nIterations, bool* pbStopFlag, const unsigned long nLoopKF, const bool bRobust)//調(diào)用    vector<KeyFrame*> vpKFs = pMap->GetAllKeyFrames();    vector<MapPoint*> vpMP = pMap->GetAllMapPoints();    BundleAdjustment(vpKFs,vpMP,nIterations,pbStopFlag, nLoopKF, bRobust);// 定義void Optimizer::BundleAdjustment(const vector<KeyFrame *> &vpKFs, const vector<MapPoint *> &vpMP, int nIterations, bool* pbStopFlag, const unsigned long nLoopKF, const bool bRobust)4.1 方程求解器 LinearSolver

    g2o::BlockSolver_6_3::LinearSolverType * linearSolver;    linearSolver = new g2o::LinearSolverEigen<g2o::BlockSolver_6_3::PoseMatrixType>();4.2 矩陣求解器 BlockSolver

g2o::BlockSolver_6_3 * solver_ptr = new g2o::BlockSolver_6_3(linearSolver);

typedef BlockSolver< BlockSolverTraits<6, 3> > BlockSolver_6_3;4.3 算法求解器 AlgorithmSolver

g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg(solver_ptr);

用BlockSolver創(chuàng)建方法求解器solver,選擇非線性最小二乘解法(高斯牛頓GN、LM、狗腿DogLeg等),AlgorithmSolver是我自己想出來的名字,方便記憶。

4.4 稀疏優(yōu)化器 SparseOptimizer

    g2o::SparseOptimizer optimizer;    optimizer.setAlgorithm(solver);// 用前面定義好的求解器作為求解方法    optimizer.setVerbose(false);// 設(shè)置優(yōu)化過程輸出信息用的

用solver創(chuàng)建稀疏優(yōu)化器SparseOptimizer。

4.5 定義圖的頂點(diǎn)和邊,添加到稀疏優(yōu)化器SparseOptimizer

在開始看具體步驟前,注意兩點(diǎn),一是ORB-SLAM3中圖的定義,二是其誤差模型,理解之后才可能明白為什么初始化過程中要操作這些變量。

4.5.1 圖的定義4.5.1.1 圖的節(jié)點(diǎn)和邊

再計(jì)算相機(jī)坐標(biāo)系坐標(biāo)Pc轉(zhuǎn)換到像素坐標(biāo)系下的坐標(biāo)P(u,v),利用如下公式,EdgeSE3ProjectXYZ::cam_project函數(shù)實(shí)現(xiàn)(types_six_dof_expmap.cpp):

結(jié)合代碼,可以看看下圖的示意(點(diǎn)擊查看高清大圖):

4.5.1.2 設(shè)置節(jié)點(diǎn)和邊的步驟

和把大象放冰箱的步驟一樣的簡單,設(shè)置頂點(diǎn)和邊的步驟總共分三步:

1. 設(shè)置類型是關(guān)鍵幀位姿的節(jié)點(diǎn)信息:位姿(SE3)、編號(setId(pKF->mnId))、最大編號(maxKFid);

2. 設(shè)置類型是地圖點(diǎn)坐標(biāo)的節(jié)點(diǎn)信息:位姿(3dPos)、編號(setId(pMp->mnId+maxKFid+1))、計(jì)算的變量(setMarginalized);【為什么要設(shè)置setMarginalized,感興趣的同學(xué)可以自己參考一下這篇文章[《g2o:非線性優(yōu)化與圖論的結(jié)合》](https://zhuanlan.zhihu.com/p/37843131),這里就不過多贅述了】

3. 設(shè)置邊的信息:連接的節(jié)點(diǎn)(setVertex)、信息矩陣(setInformation)、計(jì)算觀測值的相關(guān)參數(shù)(setMeasurement/fx/fy/cx/cy)、核函數(shù)(setRobustKernel)。【引入魯棒核函數(shù)是人為降低過大的誤差項(xiàng),可以更加穩(wěn)健地優(yōu)化,具體請參考《視覺十四講》第10講】

4.5.1.3 ORB-SLAM3新增部分

ORB-SLAM3中新增了單獨(dú)記錄邊、地圖點(diǎn)和關(guān)鍵幀的容器,比如單目中的vpEdgesMono、vpEdgeKFMono和vpMapPointEdgeMono,分別記錄的是誤差值、關(guān)鍵幀和地圖點(diǎn),目的是在獲取優(yōu)化后的關(guān)鍵幀位姿時(shí),使用該誤差值vpEdgesMono[i],對地圖點(diǎn)vpMapPointEdgeMono[i]進(jìn)行自由度為2的卡方檢驗(yàn)e->chi2()>5.991,以此排除外點(diǎn),選出質(zhì)量好的地圖點(diǎn),見源碼[Optimizer.cc#L337](https://github.com/UZ-SLAMLab/ORB_SLAM3/blob/ef9784101fbd28506b52f233315541ef8ba7af57/src/Optimizer.cc#L337)。為了不打斷圖優(yōu)化思路,不過多展開ORB-SLAM2和3的區(qū)別,感興趣的同學(xué)可自行研究源碼。

4.5.2 誤差模型

SLAM中要計(jì)算的誤差如下示意:

其中,

是觀測誤差,對應(yīng)到代碼中就是,用觀測值【即校正后的特征點(diǎn)坐標(biāo)mvKeysUn,是Frame類的UndistortKeyPoints函數(shù)獲取的】,減去其估計(jì)值【即地圖點(diǎn)mvIniP3D,該點(diǎn)是ReconstructF或者ReconstructH中,利用三角測量得到空間點(diǎn)坐標(biāo),之后把該地圖點(diǎn)mvIniP3D投影到圖像上,得到估計(jì)的特征點(diǎn)坐標(biāo)P(u,v)】。Q是觀測噪聲,對應(yīng)到代碼中就是協(xié)方差矩陣sigma(而且還和特征點(diǎn)所在金字塔層數(shù)有關(guān),層數(shù)越高,噪聲越大)。

4.5.3 步驟一,添加關(guān)鍵幀位姿頂點(diǎn)

    // 對于當(dāng)前地圖中的所有關(guān)鍵幀    for(size_t i=0; i<vpKFs.size(); i++)    {        KeyFrame* pKF = vpKFs[i];        // 去除無效的        if(pKF->isBad())            continue;        // 對于每一個(gè)能用的關(guān)鍵幀構(gòu)造SE3頂點(diǎn),其實(shí)就是當(dāng)前關(guān)鍵幀的位姿        g2o::VertexSE3Expmap * vSE3 = new g2o::VertexSE3Expmap();        vSE3->setEstimate(Converter::toSE3Quat(pKF->GetPose()));        vSE3->setId(pKF->mnId);        // 只有第0幀關(guān)鍵幀不優(yōu)化(參考基準(zhǔn))        vSE3->setFixed(pKF->mnId==0);        // 向優(yōu)化器中添加頂點(diǎn),并且更新maxKFid        optimizer.addVertex(vSE3);        if(pKF->mnId>maxKFid)            maxKFid=pKF->mnId;    }

注意三點(diǎn):

- 第0幀關(guān)鍵幀作為參考基準(zhǔn),不優(yōu)化

- 只需設(shè)置SE(3)和Id即可

- 需要更新maxKFid,以便下方添加觀測值(相機(jī)3D位姿)時(shí)使用

4.5.4 步驟二,添加地圖點(diǎn)位姿頂點(diǎn)

    // 卡方分布 95% 以上可信度的時(shí)候的閾值    const float thHuber2D = sqrt(5.99);     // 自由度為2    const float thHuber3D = sqrt(7.815);    // 自由度為3    // Set MapPoint vertices    // Step 2.2:向優(yōu)化器添加MapPoints頂點(diǎn)    // 遍歷地圖中的所有地圖點(diǎn)    for(size_t i=0; i<vpMP.size(); i++)    {        MapPoint* pMP = vpMP[i];        // 跳過無效地圖點(diǎn)        if(pMP->isBad())            continue;        // 創(chuàng)建頂        g2o::VertexSBAPointXYZ* vPoint = new g2o::VertexSBAPointXYZ();        // 注意由于地圖點(diǎn)的位置是使用cv::Mat數(shù)據(jù)類型表示的,這里需要轉(zhuǎn)換成為Eigen::Vector3d類型        vPoint->setEstimate(Converter::toVector3d(pMP->GetWorldPos()));        // 前面記錄maxKFid 是在這里使用的        const int id = pMP->mnId+maxKFid+1;        vPoint->setId(id);        // g2o在做BA的優(yōu)化時(shí)必須將其所有地圖點(diǎn)全部schur掉,否則會出錯(cuò)。        // 原因是使用了g2o::LinearSolver<BalBlockSolver::PoseMatrixType>這個(gè)類型來指定linearsolver,        // 其中模板參數(shù)當(dāng)中的位姿矩陣類型在程序中為相機(jī)姿態(tài)參數(shù)的維度,于是BA當(dāng)中schur消元后解得線性方程組必須是只含有相機(jī)姿態(tài)變量。        // Ceres庫則沒有這樣的限制        vPoint->setMarginalized(true);        optimizer.addVertex(vPoint);4.5.5 步驟三,添加邊

        // 邊的關(guān)系,其實(shí)就是點(diǎn)和關(guān)鍵幀之間觀測的關(guān)系        const map<KeyFrame*,size_t> observations = pMP->GetObservations();        // 邊計(jì)數(shù)        int nEdges = 0;        //SET EDGES        // Step 3:向優(yōu)化器添加投影邊(是在遍歷地圖點(diǎn)、添加地圖點(diǎn)的頂點(diǎn)的時(shí)候順便添加的)        // 遍歷觀察到當(dāng)前地圖點(diǎn)的所有關(guān)鍵幀        for(map<KeyFrame*,size_t>::const_iterator mit=observations.begin(); mit!=observations.end(); mit++)        {            KeyFrame* pKF = mit->first;            // 濾出不合法的關(guān)鍵幀            if(pKF->isBad() || pKF->mnId>maxKFid)                continue;            nEdges++;            const cv::KeyPoint &kpUn = pKF->mvKeysUn[mit->second];            if(pKF->mvuRight[mit->second]<0)            {                // 如果是單目相機(jī)按照下面操作                // 構(gòu)造觀測                Eigen::Matrix<double,2,1> obs;                obs << kpUn.pt.x, kpUn.pt.y;                // 創(chuàng)建邊                g2o::EdgeSE3ProjectXYZ* e = new g2o::EdgeSE3ProjectXYZ();                // 填充數(shù)據(jù),構(gòu)造約束關(guān)系                e->setVertex(0, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(id)));                e->setVertex(1, dynamic_cast<g2o::OptimizableGraph::Vertex*>(optimizer.vertex(pKF->mnId)));                e->setMeasurement(obs);                // 信息矩陣,也是協(xié)方差,表明了這個(gè)約束的觀測在各個(gè)維度(x,y)上的可信程度,在我們這里對于具體的一個(gè)點(diǎn),兩個(gè)坐標(biāo)的可信程度都是相同的,                // 其可信程度受到特征點(diǎn)在圖像金字塔中的圖層有關(guān),圖層越高,可信度越差                // 為了避免出現(xiàn)信息矩陣中元素為負(fù)數(shù)的情況,這里使用的是sigma^(-2)                const float &invSigma2 = pKF->mvInvLevelSigma2[kpUn.octave];                e->setInformation(Eigen::Matrix2d::Identity()*invSigma2);                // 如果要使用魯棒核函數(shù)                if(bRobust)                {                    g2o::RobustKernelHuber* rk = new g2o::RobustKernelHuber;                    e->setRobustKernel(rk);                    // 這里的重投影誤差,自由度為2,所以這里設(shè)置為卡方分布中自由度為2的閾值,如果重投影的誤差大約大于1個(gè)像素的時(shí)候,就認(rèn)為不太靠譜的點(diǎn)了,                    // 核函數(shù)是為了避免其誤差的平方項(xiàng)出現(xiàn)數(shù)值上過大的增長                    rk->setDelta(thHuber2D);                }                // 設(shè)置相機(jī)內(nèi)參                // ORB-SLAM2的做法                //e->fx = pKF->fx;                //e->fy = pKF->fy;                //e->cx = pKF->cx;                //e->cy = pKF->cy;                // ORB-SLAM3的改變    e->pCamera = pKF->mpCamera;                optimizer.addEdge(e);            }            else            {                // 雙目或RGBD相機(jī)按照下面操作                ......這里忽略,只講單目            }          } // 向優(yōu)化器添加投影邊,也就是遍歷所有觀測到當(dāng)前地圖點(diǎn)的關(guān)鍵幀        // 如果因?yàn)橐恍┨厥庠?實(shí)際上并沒有任何關(guān)鍵幀觀測到當(dāng)前的這個(gè)地圖點(diǎn),那么就刪除掉這個(gè)頂點(diǎn),并且這個(gè)地圖點(diǎn)也就不參與優(yōu)化        if(nEdges==0)        {            optimizer.removeVertex(vPoint);            vbNotIncludedMP[i]=true;        }        else        {            vbNotIncludedMP[i]=false;        }    }    4.5.6 優(yōu)化

    optimizer.initializeOptimization();    optimizer.optimize(nIterations);

添加邊設(shè)置優(yōu)化參數(shù),開始執(zhí)行優(yōu)化。

5. 計(jì)算深度中值

    float medianDepth = pKFini->ComputeSceneMedianDepth(2);    float invMedianDepth = 1.0f/medianDepth;

這里開始,5到7是比較關(guān)鍵的轉(zhuǎn)換,要理解這部分背后的含義,我們需要回顧一下相機(jī)模型,復(fù)習(xí)一下各個(gè)坐標(biāo)系之間的轉(zhuǎn)換關(guān)系,再看代碼就簡單多了。

5.1 相機(jī)模型與坐標(biāo)系轉(zhuǎn)換

很多人看了n遍相機(jī)模型,小孔成像原理爛熟于心,但那只是爛熟,并沒有真正應(yīng)用到實(shí)際,想真正掌握,認(rèn)真看下去。先復(fù)習(xí)一下相機(jī)投影的過程,也可參考該文[《像素坐標(biāo)系、圖像坐標(biāo)系、相機(jī)坐標(biāo)系和世界坐標(biāo)系的關(guān)系(簡單易懂版)》](https://blog.csdn.net/shanpenghui/article/details/110481140),如圖(點(diǎn)擊查看高清大圖):

再來弄清楚各個(gè)坐標(biāo)系之間的轉(zhuǎn)換關(guān)系,認(rèn)真研究下圖,懂了之后能解決很多心里的疑問(點(diǎn)擊查看高清大圖):

總之,圖像上的像素坐標(biāo)和世界坐標(biāo)的關(guān)系是:

其中,zc是相機(jī)坐標(biāo)系下的坐標(biāo);dx和dy分別表示每個(gè)像素在橫軸x和縱軸y的物理尺寸,單位為毫米/像素;u0,v0表示的是圖像的中心像素坐標(biāo)和圖像圓點(diǎn)像素坐標(biāo)之間相差的橫向和縱向像素?cái)?shù);f是相機(jī)的焦距,R,T是旋轉(zhuǎn)矩陣和平移矩陣,xw,yw,zw是世界坐標(biāo)系下的坐標(biāo)。

5.2 歸一化平面

講歸一化平面的資料比較少,可參考性不高。大家也不要把這個(gè)東西看的有多玄乎,其實(shí)就是一個(gè)數(shù)學(xué)技巧,主要是為了方便計(jì)算。從上面的公式可以看到,左邊還有個(gè)zc的因數(shù),除掉這個(gè)因數(shù)的過程其實(shí)就可以叫歸一化。代碼中接下來要講的幾步其實(shí)都可以歸結(jié)為以下這個(gè)公式:

6. 歸一化兩幀變換到平均深度為1

    cv::Mat Tc2w = pKFcur->GetPose();    // x/z y/z 將z歸一化到1     Tc2w.col(3).rowRange(0,3) = Tc2w.col(3).rowRange(0,3)*invMedianDepth;    pKFcur->SetPose(Tc2w);7. 3D點(diǎn)的尺度歸一化

    vector<MapPointPtr> vpAllMapPoints = pKFini->GetMapPointMatches();    for (size_t iMP = 0; iMP < vpAllMapPoints.size(); iMP++)    {        if (vpAllMapPoints[iMP])        {            MapPointPtr pMP = vpAllMapPoints[iMP];            if(!pMP->isBad())                pMP->SetWorldPos(pMP->GetWorldPos() * invMedianDepth);        }    }8. 將關(guān)鍵幀插入局部地圖

    mpLocalMapper->InsertKeyFrame(pKFini);    mpLocalMapper->InsertKeyFrame(pKFcur);    mCurrentFrame.SetPose(pKFcur->GetPose());    mnLastKeyFrameId = pKFcur->mnId;    mnLastKeyFrameFrameId=mCurrentFrame.mnId;    mpLastKeyFrame = pKFcur;    mvpLocalKeyFrames.push_back(pKFcur);    mvpLocalKeyFrames.push_back(pKFini);    mvpLocalMapPoints = mpMap->GetAllMapPoints();    mpReferenceKF = pKFcur;    mCurrentFrame.mpReferenceKF = pKFcur;    mLastFrame = Frame(mCurrentFrame);    mpMap->SetReferenceMapPoints(mvpLocalMapPoints);    {        unique_lock<mutex> lock(mMutexState);        mState = eTrackingState::OK;    }    mpMap->calculateAvgZ();    // 初始化成功,至此,初始化過程完成五、總結(jié)

總之,初始化地圖部分,重要的支撐在于兩個(gè)點(diǎn):

1. 理解圖優(yōu)化的概念,包括ORB-SLAM3是如何定義圖的,頂點(diǎn)和邊到底是什么,他們有什么關(guān)系,產(chǎn)生這種關(guān)系背后的公式是什么,搞清楚這些,圖優(yōu)化就算入門了吧,也可以看得懂地圖初始化部分了;2. 相機(jī)模型,以及各個(gè)坐標(biāo)系之間的關(guān)系,大多數(shù)人還是停留在大概理解的層面,需要結(jié)合代碼實(shí)際來加深對它的理解,因?yàn)檎麄€(gè)視覺SLAM就是多視圖幾何理論的天下,不懂這些就扎近茫茫代碼中,很容易迷失。

至此,初始化過程完結(jié)了。我們通過初始化過程認(rèn)識了ORB-SLAM3系統(tǒng),但只是管中窺豹,看不到全面,想要更加深入的挖掘,還是要多多拆分源碼,一個(gè)個(gè)模塊掌握,然后才能轉(zhuǎn)化成自己的東西。以上都是各人見解,如有紕漏,請各位不吝賜教,O(∩_∩)O謝謝。

六、參考

1. [ORB-SLAM: a Versatile and Accurate Monocular SLAM System](https://blog.csdn.net/weixin_42905141/article/details/102857958)

2. [ORB-SLAM3 細(xì)讀單目初始化過程(上)](https://blog.csdn.net/shanpenghui/article/details/109809723#t10)

3. [理解圖優(yōu)化,一步步帶你看懂g2o代碼](https://www.cnblogs.com/CV-life/p/10286037.html)

4. [ORB-SLAM2 代碼解讀(三):優(yōu)化 1(概述)](https://wym.netlify.app/2019-07-03-orb-slam2-optimization1/)

5. [視覺slam十四講 6.非線性優(yōu)化](https://blog.csdn.net/weixin_42905141/article/details/92993097#2_59)

6. 《視覺十四講》 高翔

7. Mur-Artal R , Tardos J D . ORB-SLAM2: an Open-Source SLAM System for Monocular, Stereo and RGB-D Cameras[J]. IEEE Transactions on Robotics, 2017, 33(5):1255-1262.

8. Campos C , Elvira R , Juan J. Gómez Rodríguez, et al. ORB-SLAM3: An Accurate Open-Source Library for Visual, Visual-Inertial and Multi-Map SLAM[J]. 2020.

9. 《概率機(jī)器人》 [美] Sebastian Thrun / [德] Wolfram Burgard / [美] Dieter Fox 機(jī)械工業(yè)出版社

備注:作者也是我們「3D視覺從入門到精通」特邀嘉賓:一個(gè)超干貨的3D視覺學(xué)習(xí)社區(qū)

本文僅做學(xué)術(shù)分享,如有侵權(quán),請聯(lián)系刪文。

以上就是關(guān)于pos機(jī)一直顯示網(wǎng)絡(luò)正在初始化,SLAM3 單目地圖初始化的知識,后面我們會繼續(xù)為大家整理關(guān)于pos機(jī)一直顯示網(wǎng)絡(luò)正在初始化的知識,希望能夠幫助到大家!

轉(zhuǎn)發(fā)請帶上網(wǎng)址:http://www.shineka.com/newsone/56237.html

你可能會喜歡:

版權(quán)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn),該文觀點(diǎn)僅代表作者本人。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請發(fā)送郵件至 babsan@163.com 舉報(bào),一經(jīng)查實(shí),本站將立刻刪除。