點雲處理算法核心-八叉樹


         一直想寫一篇關於八叉樹的博客,我的博客大概快一年也沒更新了,當然這之間的原因跟疫情或多或少還是存在關系的,2020注定是讓人壓抑的一年,所以這一年也慢下了腳步。八叉樹的重要性其實不用我再次強調了吧,它涉及到算法的方方面面吧,也是三維點雲數據處理的一個重要基石,從顯示到交互再到算法八叉樹無不扮演着極其重要的角色。當然並不是任何算法都會涉及到八叉樹,將三維點雲數據處理成二維有時也是一種比較常見的處理手段,畢竟二維的鄰域處理要比三維的鄰域處理簡單的多。對於八叉樹的研究博主本人其實很早就提上了日程,也付諸了相應的行動,一直想寫一篇博客分享這方面的知識,一方面它涉及到的篇幅較大,所以每次一想到要寫很久就放棄了,另一方面受疫情的影響、工作忙碌以及自己的一些事情導致學習的熱情也大不如以往,今年似乎大部分時間都在刷B站,這里表示慚愧。

         對於八叉樹,我想大家首先可能接觸到的就是pcl的八叉樹的鄰域搜索,短短幾句代碼就實現了查找鄰域的功能,我一開始覺得還挺好用的,但是用久了,不免產生了一個疑問,八叉樹的具體實現機制是什么,但是網上似乎很少有關於八叉樹的精細講解,見得最多就是那個八叉樹的樹狀圖,此時唯一可行的辦法,那就只能硬着頭皮上源碼了。

       對於八叉樹的印象,我想下面這個圖大家見得是最多的。

                                                                           

 

                typedef pcl::octree::OctreeLeafNode<OctreeContainerPointIndices> LeafNode;
                typedef pcl::octree::OctreeBranchNode<OctreeContainerEmpty>BranchNode;

               節點分為兩種類型,根據英文名,博主姑且將其翻譯為分支節點以及葉子節點,上圖有文字標注,其中綠色的分支節點為空節點。就是我們常說的八叉樹的子節點要么為8個,要么為0個,就是因為空節點的存在,空節點之所以存在就是因為其里面不存在數據點,這樣也提高了遍歷的效率,不然在滿樹的情況下,節點會多很多。

         一、第一部分分塊功能

          第一步就是八叉樹的分層,就是分幾層,當然也可以指定八叉樹葉子節點(就是盒子套盒子,那個最小的盒子)所對應包圍盒的大小。當然分層的邏輯似乎更直觀更好理解點。分層的思想其實也不難理解,大家可以理解為盒子里剛好填滿8個盒子,直到設定的深度為止。舉個例子,如果八叉樹的深度為3,先將外包的大盒子分成八個,然后對這8個盒子又分別分成8個,然后再次分成8個。執行3次為止,當然空節點直接跳過。

                          

                       深度為1                                                                                  深度為2                                                                     深度為3    

               可以從上圖看到,單從z方向這個維度來看,深度每增加一層,在維度上就會一分為二。

 

                二、第二部分鄰域搜索

 

                                                                                                   

 

 

                為了使得邏輯清晰點,博主在這里附上了一個流程圖,其實就是一個迭代的過程,對待每個分支節點重復流程1的操作。

                1.一點包圍盒鄰域的獲取

                對於鄰域搜索的問題,這里先以一個易於理解的例子切入,我們求一點包圍盒內的鄰域點。求一點包圍盒范圍內的鄰域點算法實現的思路,即從根節點開始判斷,判斷每一個根節點是否與所設置的包圍盒有交集,如果有繼續查找其子節點,同樣判斷其子節點與該包圍盒是否存在交集,直到訪問葉子節點,如果該葉子節點與所設置包圍盒有交集,便獲取該葉子節點里的索引執行判定條件3(對於每個節點的判斷無論是分支節點還是葉子節點都會執行一個判定條件,即這里的判定條件1條件2,這里的條件1與條件2都是2個包圍盒是否存在交集)。

            流程:

            a.這里我們設 預先設置的包圍盒為boundingbox1(即我們所需獲取的索引的包圍盒),當前節點的包圍盒為boundingbox2。

            b.判定boundingbox1與boundingbox2是否存在交集,即流程圖里的判定條件1條件2(這里的條件是一致的)

            c.如果到了葉子節點,且滿足b,那么則要執行判定條件3,即判定Pi是否在boundingbox1里

                          

          與包圍盒有交集的葉子節點                                                  包圍盒里的數據點                                                                          整體效果

               2.一點半徑鄰域的獲取

               類似於這類思想,我們便可以求一點半徑鄰域內的數據點,所以可以先將球處理成包圍盒子,唯一不同的是處理到葉子節點時,判定條件即為兩點之間的點間距

 

                             

              與包圍盒有交集的葉子節點                                                   包圍盒里的數據點                                                                  整體效果

            3.一點K鄰域的獲取

           對於k近鄰會相對比較復雜一些,因為並不知道那些點距離自己最近,而且這些點可能來自於周圍的數個葉子節點,所以算法處理一開始只能通過訪問到的第一個葉子節點,然后執行以下操作

std::sort(point_candidates.begin(), point_candidates.end());
if (point_candidates.size() > K)
point_candidates.resize(K);
if (point_candidates.size() == K)
smallest_squared_dist = point_candidates.back().point_distance_;

  看這段代碼就很直觀了,升序排序,取到第K個值

       對於下一個節點就會以smallest_squared_dist作為一個判斷依據,因為總不能所有節點都去訪問吧

float disThread = smallest_squared_dist + voxelSquaredDiameter / 4.0 + sqrt(smallest_squared_dist * voxelSquaredDiameter);
if(search_heap.back().point_distance < disThread)
 search_heap.back().point_distance<disThread 滿足此條件

        voxelSquaredDiameter 代表當前深度節點的包圍盒的長度smallest_squared_dist代表上次葉子節點獲取的鄰域點里第K個距離值(已經升序排序了,如果沒找到K個鄰域,那么smallest_squared_dist的值為初始化的值,一般設置較大) 

 search_heap.back().point_distance 代表當前層次的根節點包圍盒的中心點與搜索點的距離
  這個公式曾經在一篇論文看的過,具體是什么原理還真忘了,知道的小伙伴評論區留言賜教一番,感激不盡。
當然自己也大概抽象的理解了一下,大概要滿足以下這種形式:

        search_heap.back().point_distance - δ<smallest_squared_dist  =>  search_heap.back().point_distance<smallest_squared_dist + δ

        所以這個判定條件完全是可以自定義的,所以我也沒去糾結這個問題了。亦步亦趨也不太好,還是得有一定的原創性。
       以往對待一個問題比較愛刨根問底,因為這篇博文在數個月前就琢磨再寫了,可惜一拖再拖,今年整個精神狀態都不在線,所以這里請允許我偷個懶(慚愧慚愧),這篇博文的插圖有點多,所以感覺確實是有點棘手。我對於整個八叉樹的理解也遠沒到細致入微的境界,所以很多細節的地方都得去看源碼。
       在空閑的時間里,我盡量養成看源碼的習慣,爭取面面俱到做到有始有終,后續可能會陸續更新我對八叉樹的一些理解。
       下面來一張插圖,不過沒什么代表性吧,就是一張很常規的K近鄰搜索圖。

      K近鄰搜索結果

         三、第三部分一點所在葉子節點的父節點及其兄弟節點

          描述:獲取一點所在的葉子節點,然后獲取該葉子節點所在的父親節點,並且獲取該葉子節點的兄弟節點

           1.這里定義八叉樹的最大深度為 maxDepth,查詢點為Pt

           2.那么其父親節點必定在maxDepth-1層,那么判定條件-----Pt在此層根節點的包圍盒內。假設得到該父親節點且命名為parentNode.

           3.獲取parentNode的子節點,判斷子節點與Pt的空間關系,包含關系的屬於當前節點,否則屬於其兄弟節點

           當然這個原理不是pcl庫里面的,是博主自己的一個簡單應用吧,邏輯也很簡單,包括上文的半徑內搜索,也是博主的一個簡單應用,期待有更簡單更高效的搜索方式的小伙伴們在評論區里共享一番,感激不盡

           下面貼上幾個示意圖吧

                   

    紅色部分為該節點,剩下為其兄弟節點                                              白色為父節點                                                                       整體效果

            

                    到此這篇博文已經結束,屬於千呼萬喚始出來系列,差不多一年沒更新博客了,其實更新博客的速度也代表自己當前的一種狀態,不過對於知識技術的認可不應該在何種情況下去打折扣,所以寫這篇博客的目的一方面分享一下我在八叉樹方面的一些淺顯的認知,另一方面也提醒自己知識技能如尊嚴般重要,畢竟是自己的安身立命之本,因為你永遠不知道未來等待自己的會是什么,所以就得把主動權早早的掌握在自己手里。生活得靠自己而不是靠施舍,所以如我一般的草根們還是得清醒的認知這一點。對未來是否無所畏懼其實很大程度取決於當前自己邁出的每一步。

               接下來我會繼續更新一些知識,當然大部分都來自於一些開源庫,例如pcl或者Cloudcompare,又或者meshlab這些。期待下一次能有大的更新,這段時間我會抽本來不多的休息時間去多看看源碼跟大家分享相關知識。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM