ORB-SLAM2中四叉樹管理特征點

當從圖像金字塔中的每一層圖像上提取特征點之后,都要先用四叉樹技術對這些特征點進行管理

//該類中定義了四叉樹創建的函數以及樹中結點的屬性
//bool bNoMore: 根據該結點中被分配的特征點的數目來決定是否繼續對其進行分割
//DivisionNode():實現如何對一個結點進行分割
//vKeys:用來存儲被分配到該結點區域內的所有特征點
//UL, UR, BL, BR:四個點定義了一個結點的區域
//lit:list的迭代器,遍歷所有生成的節點
class ExtractorNode
{
public:///用初始化列表來初始化本類內的成員變量ExtractorNode():bNoMore(false){}void DivideNode(ExtractorNode &n1, ExtractorNode &n2, ExtractorNode &n3, ExtractorNode &n4);std::vector<cv::KeyPoint> vKeys;cv::Point2i UL, UR, BL, BR;std::list<ExtractorNode>::iterator lit;bool bNoMore;
};

下面的函數實現利用四叉樹來管理從金字塔中的每一層圖像上提取的特征點

//vToDistributeKeys變量中存儲的是從金字塔中某一層圖像上提取的特征點
//minX, maxX, minY, maxY:是該層圖像去除了邊界的區域
//N: mnFeaturesPerLevel[i]表示該層圖像上應該提取的特征點的個數
//level: 該圖像處在金字塔上的層數
vector<cv::KeyPoint> ORBextractor::DistributeOctTree(const vector<cv::KeyPoint>& vToDistributeKeys, const int &minX,const int &maxX, const int &minY, const int &maxY, const int &N, const int &level)
{//常用的相機kinect v1的分辨率是:640*480 kinect v2的分辨率是:1920*1080//為了盡量使得每一個結點的區域形狀接近正方形所以圖像的長寬比決定了四叉樹根節點的數目//如果使用kinect v1那么只有一個根結點,如果使用kinect v2那么就會有兩個根結點const int nIni = round(static_cast<float>(maxX-minX)/(maxY-minY));//hX變量可以理解為一個根節點所占的寬度  const float hX = static_cast<float>(maxX-minX)/nIni;//lNodes中存儲生成的樹結點list<ExtractorNode> lNodes;//vpIniNodes變量中存儲的是結點的地址vector<ExtractorNode*> vpIniNodes;//vpIniNodes的大小先設置成根結點的個數vpIniNodes.resize(nIni);for(int i=0; i<nIni; i++){ExtractorNode ni;//四叉樹是每次根據特定條件將一個結點分成四等分,四個區域左上(UL),右上(UR), //左下(BL),右下(BR)//左上角位置坐標ni.UL = cv::Point2i(hX*static_cast<float>(i),0);//右上角位置坐標ni.UR = cv::Point2i(hX*static_cast<float>(i+1),0);///左下角的位置坐標ni.BL = cv::Point2i(ni.UL.x,maxY-minY);///右下角的位置坐標ni.BR = cv::Point2i(ni.UR.x,maxY-minY);//vKeys的大小為在上面的這個根節點范圍內總共提取的特征點的個數ni.vKeys.reserve(vToDistributeKeys.size());//將創建的根節點插入到list lNodes中lNodes.push_back(ni);將lNodes變量中的最后存儲的那個結點的地址存儲到vector變量vpIniNodes中//暫時還不知道這個變量做何用//看都了吧vpIniNodes總是把最后插入到lNodes中的結點的地址拿走,然后要為//該結點的vKeys成員變量內部添加屬于該結點的特征點。vpIniNodes[i] = &lNodes.back();}//Associate points to childs//要一直記得vToDistributeKeys變量中存儲的是該層圖像中提取的特征點//遍歷在該層圖像上提取的所有特征點for(size_t i=0;i<vToDistributeKeys.size();i++){const cv::KeyPoint &kp = vToDistributeKeys[i];//將所有提取的特征點根據坐標位置將其分配到對應的根節點中去//如果使用kinect b=v1那么所有的kp.pt.x都小于hX,所以所有的特征點都被分配到//vpIniNodes的第0個元素中存儲的結點指針所指向的空間中去了。//到這里明白了這個四叉樹的玩法了//定義一個list變量,用來存儲生成的樹節點本身//定義一個vector變量,用來存儲結點的指針,這個指針可以指向該結點區域被分配的特征點的內存//list是一個雙向鏈表容器,可高效地進行插入刪除元素//vector是將元素置于一個動態數組中,vector可以隨機存取元素,在頭尾插入數據快,但是從中            //間插入數據很慢//正是利用了list和vector的特點,使得我們即可以快速高效的插入刪除結點,又可以隨機的存取//被分配到某一個結點區域的的特征點//如何將這個list和vector聯系起來共同維護這個四叉樹呢?vpIniNodes[kp.pt.x/hX]->vKeys.push_back(kp);}list<ExtractorNode>::iterator lit = lNodes.begin();//遍歷已經生成的所有節點while(lit!=lNodes.end()){//如果判斷在一個結點里面只有一個特征點if(lit->vKeys.size()==1){//將該結點的bNoMore屬性設置為true,表示不再對這個結點進行分割lit->bNoMore=true;lit++;}//如果判斷這個結點中沒有被分配到任何的特征點那么就將這個結點刪除else if(lit->vKeys.empty())lit = lNodes.erase(lit);elselit++;}//lNodes中的結點和 vpIniNodes中的結點指針是同步的,只有在 vpIniNodes中存儲的結點中存儲了 //特征點,才能根據特征點的數目來決定如何處理這個結點//那如果在lNodes中刪除一個沒有特征點的結點,那么在 vpIniNodes中對應的這個地址也會被銷毀嗎?bool bFinish = false;int iteration = 0;vector<pair<int,ExtractorNode*> > vSizeAndPointerToNode;vSizeAndPointerToNode.reserve(lNodes.size()*4);while(!bFinish){iteration++;//lNodes中已經存儲的結點的數目int prevSize = lNodes.size();lit = lNodes.begin();int nToExpand = 0;vSizeAndPointerToNode.clear();while(lit!=lNodes.end()){///如果結點內被分配的特征點的數目只有1個則不繼續分割這個結點if(lit->bNoMore){// If node only contains one point do not subdivide and continuelit++;continue;}else{// 如果結點中被分配到的特征點數大于1則要繼續分割ExtractorNode n1,n2,n3,n4;//在下面在介紹這個函數//概括來說就是將上面這個結點分成了四個結點,并且已經完成了特征點的分配,以及特征//個數的檢測設定好每個節點的bNoMore的值lit->DivideNode(n1,n2,n3,n4);// Add childs if they contain pointsif(n1.vKeys.size()>0){//如果新分割出來的第一個結點中被分配的特征點的個數大于0那么就將這個結點//插入到list的頭部lNodes.push_front(n1);//如果這個心結點中被分配的特征點的個數大于1,那么接下來要被分割的結點的數目//就得加1了                    if(n1.vKeys.size()>1){nToExpand++;//變量vSizeAndPointerToNode中存儲的是每一個結點的地址以及該結點中被分配到的特征點的個數。                    vSizeAndPointerToNode.push_back(make_pair(n1.vKeys.size(),&lNodes.front()));lNodes.front().lit = lNodes.begin();}}//對新分配出的第二個結點進行同上面相同的測試和操作if(n2.vKeys.size()>0){    //在list的頭部插入元素lNodes.push_front(n2);if(n2.vKeys.size()>1){nToExpand++;vSizeAndPointerToNode.push_back(make_pair(n2.vKeys.size(),&lNodes.front()));//每插入一個結點就要更新list的開始結點的位置lNodes.front().lit = lNodes.begin();}}if(n3.vKeys.size()>0){lNodes.push_front(n3);if(n3.vKeys.size()>1){nToExpand++;vSizeAndPointerToNode.push_back(make_pair(n3.vKeys.size(),&lNodes.front()));lNodes.front().lit = lNodes.begin();}}if(n4.vKeys.size()>0){lNodes.push_front(n4);if(n4.vKeys.size()>1){nToExpand++;vSizeAndPointerToNode.push_back(make_pair(n4.vKeys.size(),&lNodes.front()));lNodes.front().lit = lNodes.begin();}}lit=lNodes.erase(lit);continue;}}       // Finish if there are more nodes than required features// or all nodes contain just one point//當創建的結點的數目比要求的特征點還要多或者,每個結點中都只有一個特征點的時候就停止分割if((int)lNodes.size()>=N || (int)lNodes.size()==prevSize){bFinish = true;}//如果現在生成的結點全部進行分割后生成的結點滿足大于需求的特征點的數目,但是不繼續分割又//不能滿足大于N的要求時//這里為什么是乘以三,這里也正好印證了上面所說的當一個結點被分割成四個新的結點時,//這個結點時要被刪除的,其實總的結點時增加了三個else if(((int)lNodes.size()+nToExpand*3)>N){while(!bFinish){prevSize = lNodes.size();//這里將已經創建好的結點放到一個新的容器中vector<pair<int,ExtractorNode*> > vPrevSizeAndPointerToNode = vSizeAndPointerToNode;vSizeAndPointerToNode.clear();//根據結點中被分配都的特征點的數目對結點進行排序//這里為何要排序,我們想要的結果是想讓盡可能多的特征點均勻的分布在圖像上//如果前面的特征分布相對均勻的結點中的特征點數目已經達到了指標那么就可以將//后面那些分布密集的特征點去掉了。sort(vPrevSizeAndPointerToNode.begin(),vPrevSizeAndPointerToNode.end());for(int j=vPrevSizeAndPointerToNode.size()-1;j>=0;j--){ExtractorNode n1,n2,n3,n4;vPrevSizeAndPointerToNode[j].second->DivideNode(n1,n2,n3,n4);// Add childs if they contain pointsif(n1.vKeys.size()>0){lNodes.push_front(n1);if(n1.vKeys.size()>1){vSizeAndPointerToNode.push_back(make_pair(n1.vKeys.size(),&lNodes.front()));lNodes.front().lit = lNodes.begin();}}if(n2.vKeys.size()>0){lNodes.push_front(n2);if(n2.vKeys.size()>1){vSizeAndPointerToNode.push_back(make_pair(n2.vKeys.size(),&lNodes.front()));lNodes.front().lit = lNodes.begin();}}if(n3.vKeys.size()>0){lNodes.push_front(n3);if(n3.vKeys.size()>1){vSizeAndPointerToNode.push_back(make_pair(n3.vKeys.size(),&lNodes.front()));lNodes.front().lit = lNodes.begin();}}if(n4.vKeys.size()>0){lNodes.push_front(n4);if(n4.vKeys.size()>1){vSizeAndPointerToNode.push_back(make_pair(n4.vKeys.size(),&lNodes.front()));lNodes.front().lit = lNodes.begin();}}lNodes.erase(vPrevSizeAndPointerToNode[j].second->lit);//如果多有的結點還沒有被分割完就已經達到了大于N的要求那么就直接跳出循環if((int)lNodes.size()>=N)break;}if((int)lNodes.size()>=N || (int)lNodes.size()==prevSize)bFinish = true;}}}// Retain the best point in each nodevector<cv::KeyPoint> vResultKeys;vResultKeys.reserve(nfeatures);//遍歷創建的所有結點for(list<ExtractorNode>::iterator lit=lNodes.begin(); lit!=lNodes.end(); lit++){//獲取每個結點下的特征點vector<cv::KeyPoint> &vNodeKeys = lit->vKeys;cv::KeyPoint* pKP = &vNodeKeys[0];float maxResponse = pKP->response;//在每個結點中找到那個最強壯的特征點進行保存for(size_t k=1;k<vNodeKeys.size();k++){if(vNodeKeys[k].response>maxResponse){pKP = &vNodeKeys[k];maxResponse = vNodeKeys[k].response;}}//只將每個結點下最強壯的的特征點保存vResultKeys.push_back(*pKP);}return vResultKeys;
}

經過上面的操作,我們已經將圖像金字塔中的某一層圖像上提取的特征點優化完畢了。

//再來看看結點時如何被分割成四個新的結點的
//這個成員函數的參數是四個引用,所以函數返回值是void,C++中的引用就好比與C語言中的指針
void ExtractorNode::DivideNode(ExtractorNode &n1, ExtractorNode &n2, ExtractorNode &n3, ExtractorNode &n4)
{//static_cast就相當于C語言中的強制類型轉換//在分割結點之前要先計算出每一個新結點的四個角點坐標//halfX和halfY分別是已創建結點的x方向的和y方向的中間位置const int halfX = ceil(static_cast<float>(UR.x-UL.x)/2);const int halfY = ceil(static_cast<float>(BR.y-UL.y)/2);//設定四個子結點的邊界n1.UL = UL;n1.UR = cv::Point2i(UL.x+halfX,UL.y);n1.BL = cv::Point2i(UL.x,UL.y+halfY);n1.BR = cv::Point2i(UL.x+halfX,UL.y+halfY);//將每個新結點的用來存儲特征點的向量的capacity設置為母結點中所有的特征點的個數n1.vKeys.reserve(vKeys.size());n2.UL = n1.UR;n2.UR = UR;n2.BL = n1.BR;n2.BR = cv::Point2i(UR.x,UL.y+halfY);n2.vKeys.reserve(vKeys.size());n3.UL = n1.BL;n3.UR = n1.BR;n3.BL = BL;n3.BR = cv::Point2i(n1.BR.x,BL.y);n3.vKeys.reserve(vKeys.size());n4.UL = n3.UR;n4.UR = n2.BR;n4.BL = n3.BR;n4.BR = BR;n4.vKeys.reserve(vKeys.size());//Associate points to childs//根據特征點的坐標來將特征點分配到不同的新結點區域for(size_t i=0;i<vKeys.size();i++){const cv::KeyPoint &kp = vKeys[i];if(kp.pt.x<n1.UR.x){if(kp.pt.y<n1.BR.y)n1.vKeys.push_back(kp);elsen3.vKeys.push_back(kp);}else if(kp.pt.y<n1.BR.y)n2.vKeys.push_back(kp);elsen4.vKeys.push_back(kp);}//最后根據每個結點中分得的特征點的數目來設定bNoMore變量的真假if(n1.vKeys.size()==1)n1.bNoMore = true;if(n2.vKeys.size()==1)n2.bNoMore = true;if(n3.vKeys.size()==1)n3.bNoMore = true;if(n4.vKeys.size()==1)n4.bNoMore = true;}

接下來捋一捋四叉樹的數據模型

假設一個四叉樹的形狀是這樣的,看看他在內存中是如何存儲和管理結點的

在ORB-SLAM中是定義了一個list用來存儲四叉樹中的結點 std::vector<ExtractNode> lNodes;

如果一個結點滿足被分割的條件那么將他新分割出來的四個結點依次從頭部插入到list,每插入一個新的結點就讓list的迭代器指向list中的第一個元素,四個新結點插入完畢之后將母結點刪除

然后是從list頭部開始依次遍歷所有結點,判斷哪個節點要被繼續分割,如果需要繼續分割則按照上面的方法完成新結點的存儲和母結點的刪除,因為每插入一個結點都會讓迭代器指向新插入的結點,所以每次都是從list的頭部開始遍歷,所以剛被分割出來的結點如果滿足分割條件會被緊接著繼續分割,而最早生成的結點要越晚被分割。

接下來談一談為什么要將四叉樹應用到ORB-SLAM中,聽別人說是為了管理圖像中提取的特征點,那這個四叉樹到底在特征點的管理中起到了什么作用呢?

記得很清楚的一點是在上面的程序中,有一個環節是將創建好的結點按照結點中包含的特征點的個數從小到大來對結點進行排序,然后是優先對那些包含特征點相對較少的結點進行分割,因為結點中包含的特征點少,分割的次數也就少了,并且倘若還沒有遍歷完所有的結點得到的特征點的個數就已經達到了每層圖像提取特征點的要求,那么就不必對那些包含特征點多的結點進行分割了,一個是分割起來費勁,再者,這個結點中包含比同等層次的結點要多的特征點,說明這里面的特征點有“集會滋事”的嫌疑, 畢竟我們希望最終得到的特征點盡可能的均勻的分布在圖像中。

接下來,就要對從上面每個結點中挑選出response最大的作為這個結點的代表保留下來,我想的這樣一來,萬一留下的特征點的數目沒有達標怎么辦呢? 欲知后事如何,請聽下次分曉。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/252511.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/252511.shtml
英文地址,請注明出處:http://en.pswp.cn/news/252511.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python多線程3:queue

queue模塊實現了多生產者。多消費者隊列。在多線程環境下&#xff0c;該隊列能實現多個線程間安全的信息交換。 queue模塊介紹 模塊實現了3種類型的隊列&#xff0c;差別在于隊列中條目檢索的順序不同。在FIFO隊列中。依照先進先出的順序檢索條目。在LIFO隊列中&#xff0c;最后…

微信小程序教程02:App(Object)和Page(Object) 構造器介紹

在/app.js中&#xff0c;有方法App&#xff0c;它的作用是注冊整個小程序的應用&#xff0c;其中可以傳入一些配置&#xff0c;或者存儲全局狀態。 App(Object) 構造器生命周期 屬性類型描述onLaunchFunction在小程序初始化時觸發&#xff0c;全局僅觸發一次onShowFunction小程…

阿里云.log

申請證書審核失敗的原因及處理方法;( 新添加站點 免費版 SSL 網頁內不能有 HTTPS的連接&#xff1b;更多點擊連接) 轉載于:https://www.cnblogs.com/q1104460935/p/8287377.html

SharePoint Search之(七)Search result- 結果源

在使用搜索引擎的時候。非常多情況下&#xff0c;用戶希望限定一下搜索范圍&#xff0c;以便更加easy找到想要的結果。在SharePoint 2013的search里&#xff0c;也支持類似的功能&#xff0c;SharePoint 默認提供了幾種范圍&#xff1a; 在SharePoint&#xff0c;這個叫Search …

曠視砸20億進軍AIoT,發布國內首個機器人協作大腦河圖

1 月 16 日&#xff0c;人工智能獨角獸曠視科技發布了機器人戰略&#xff0c;以及自 2018 年 4 月收購艾瑞思機器人&#xff0c;進軍機器人領域的最新進展——智能協同大腦河圖。在會上&#xff0c;曠視還大筆一揮&#xff0c;決定投入 20 億元&#xff0c;用于打造物流倉儲上下…

ORB-SLAM2-金字塔求解-特征點的提取-描述子的計算

//這個成員函數重載了函數括號運算符&#xff0c;讓他具有函數的特點 //但是還不知道在其他程序塊是如何應用這塊代碼的。 //InputArray和OutputArray是opencv中的兩個函數接口 void ORBextractor::operator()( InputArray _image, InputArray _mask, vector<KeyPoint>&a…

am335x uboot, kernel 編譯

一、設置環境變量// 寫在家目錄下面的 .bashrc 里面export KERNEL_PATH~/aplex/kernel3.2.0 // kernel 路徑export UBOOT_PATH~/aplex/uboot2011.09 // u-boot 路勁export ROOTFS_PATH~/aplex/filesystemexport TOOLFS_PATH~/aplex/toolsexport ARCHarm …

php+ajax簡單實現跨域(http+https)請求調用

當一個網站 a站 需要調用另一個網站 b站 列表文章時 比如&#xff1a;www.a123.com 調用 www.b456.com 文章 在 a站 建立php文件獲取 b站 資源文章到本地后&#xff0c;再傳遞a站前端 在網站 b456 下的文件為 <ul class"ls_wz"> <li><a href"#&q…

ORB-SLAM2中MapPoints的描述子的計算

//我們在從金字塔的圖像中獲取特征點時為每一個特征點計算了描述子 //現在看看如何計算一個空間的地圖點的描述子 void MapPoint::ComputeDistinctiveDescriptors() {// Retrieve all observed descriptorsvector<cv::Mat> vDescriptors;//獲取到某一個地圖點可以被哪些關…

HDU:4185-Oil Skimming

Oil Skimming Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Problem Description Thanks to a certain “green” resources company, there is a new profitable industry of oil skimming. There are large slicks of crude oil floa…

域控制器情況分析

域控制器情況分析 1、Windows Server 的 Foundation、Standard、Enterprise 以及 Datacenter 版本號既可作為源server&#xff0c;也可作為目標server。僅支持將 Foundation Server 版本號作為受限方案中的目標server。在使用 Foundation Server 作為目標server之前&#xff0c…

Linux基礎命令---su

su臨時切換身份到另外一個用戶&#xff0c;使用su切換用戶之后&#xff0c;不會改變當前的工作目錄&#xff0c;但是會改變一些環境變量。此命令的適用范圍&#xff1a;RedHat、RHEL、Ubuntu、CentOS、SUSE、openSUSE、Fedora。1、語法su [選項] [參數]2、選項列表--help顯示…

在Ubuntu 16.04 上安裝和卸載matlab 2018b(Install and uninstall matlab 2018b on ubuntu)

1.安裝2018b可以參考下面兩篇文章 https://www.ph0en1x.space/2018/04/23/ubuntu_matlab/ https://blog.csdn.net/qq_32892383/article/details/79670871 2.卸載2018b 我的默認安裝在 /usr/local/MATLAB $ sudo rm -r /usr/local/MATLAB $ cd ~ $ ll (這個時候可以看到隱…

04.openssl編程——哈希表

4.1 哈希表在一般的數據結構如線性表和樹中&#xff0c;記錄在結構中的相對位置與記錄的關鍵字之間不存在確定的關系&#xff0c;在結構中查找記錄時需要進行一系列的關鍵字比較。這一類查找方法建立在比較的基礎上&#xff0c;查找的效率與比較次數密切相關。理想的情況是能…

shell數組中“和@的妙用

#!/bin/bashlist(4k"8k a bit""16k abc""32k gold"64k)for i in "${list[]}"do echo $idone 分別對比一下不帶” 和換成*&#xff0c;之間的區別。轉載于:https://www.cnblogs.com/zjd2626/p/7041341.html

「JupyterLab」 Jupyter Notebook 新生代IDE模式頁面

參考&#xff1a;Overview 安裝&#xff1a; $ pip install jupyterlab 啟動&#xff08;不是jupyter notebook&#xff09;&#xff1a; $ jupyter lab Jupyterlab中最好用的就是顯示csv數據。CSV數據顯示效果&#xff1a; 安裝插件 jupyterlab是和jupyter notebook隔離的&…

undefined reference to 'pthread_create'

剛在Ubuntu16.04 上用Clion寫一個"單生產者-單消費者"的線程的程序&#xff0c;源程序可以參考下面的網址 https://www.cnblogs.com/haippy/p/3252092.html 在編譯的時候編譯器給的提示是&#xff1a; undefined reference to pthread_create 解決辦法就是在CMake…

windows下安裝vundle

windows下安裝vundle ## 前言 windows下安裝vundle和linux下稍微有些不一樣&#xff0c;雖然官網給出了 安裝說明&#xff0c;但是有些問題的。E117: Unknown function: vundle#begin ## 安裝步驟 參考官方文檔即可vundle ## 問題處理 修改_vimrc配置文件內容&#xff0c;這是正…

深度學習框架不能“包治百病”,開發者如何選出最適合自己的?

隨著深度學習關注度和勢頭上升&#xff0c;深度學習被越來越多的企業和組織的生產實踐結合起來。這時&#xff0c;無論是對于深度學習相關專業的初學者&#xff0c;還是已經在企業和組織中從事工業場景應用和研發的開發者來說&#xff0c;選擇一個適合自己&#xff0c;適合業務…