(1) octree是一種用於管理稀疏3D數據的樹形數據結構,每個內部節點都正好有八個子節點,介紹如何用octree在點雲數據中進行空間划分及近鄰搜索,實現“體素內近鄰搜索(Neighbors within VOxel Search)”,"K近鄰搜索(K Nearest Neighbor Search)","半徑內近鄰搜索"(Neighbors within Radius Search)
K近鄰搜索(K Nearest Neighbor Search)
所謂K近鄰算法,即是給定一個訓練數據集,對新的輸入實例,在訓練數據集中找到與該實例最鄰近的K個實例(也就是上面所說的K個鄰居), 這K個實例的多數屬於某個類,就把該輸入實例分類到這個類中。
我們常說,物以類聚,人以群分,判別一個人是一個什么樣品質特征的人,常常可以從他/她身邊的朋友入手,所謂觀其友,而識其人。我們不是要判別上圖中那個綠色的圓是屬於哪一類數據么,好說,從它的鄰居下手。但一次性看多少個鄰居呢?從上圖中,你還能看到:
-
如果K=3,綠色圓點的最近的3個鄰居是2個紅色小三角形和1個藍色小正方形,少數從屬於多數,基於統計的方法,判定綠色的這個待分類點屬於紅色的三角形一類。
-
如果K=5,綠色圓點的最近的5個鄰居是2個紅色三角形和3個藍色的正方形,還是少數從屬於多數,基於統計的方法,判定綠色的這個待分類點屬於藍色的正方形一類。
-
K 值的選擇會對算法的結果產生重大影響。K值較小意味着只有與輸入實例較近的訓練實例才會對預測結果起作用,但容易發生過擬合;如果 K 值較大,優點是可以減少學習的估計誤差,但缺點是學習的近似誤差增大,這時與輸入實例較遠的訓練實例也會對預測起作用,使預測發生錯誤。在實際應用中,K 值一般選擇一個較小的數值,通常采用交叉驗證的方法來選擇最優的 K 值。隨着訓練實例數目趨向於無窮和 K=1 時,誤差率不會超過貝葉斯誤差率的2倍,如果K也趨向於無窮,則誤差率趨向於貝葉斯誤差率。
-
該算法中的分類決策規則往往是多數表決,即由輸入實例的 K 個最臨近的訓練實例中的多數類決定輸入實例的類別
-
距離度量一般采用 Lp 距離,當p=2時,即為歐氏距離,在度量之前,應該將每個屬性的值規范化,這樣有助於防止具有較大初始值域的屬性比具有較小初始值域的屬性的權重過大。
半徑內近鄰搜索"(Neighbors within Radius Search)
代碼解析,新建工程文件ch3_3,及文件octree_search.cpp
octree_search.cpp 內容
#include <pcl/point_cloud.h> //點雲頭文件 #include <pcl/octree/octree.h> //八叉樹頭文件 #include <iostream> #include <vector> #include <ctime> int main (int argc, char** argv) { srand ((unsigned int) time (NULL)); //用系統時間初始化隨機種子與 srand (time (NULL))的區別 pcl::PointCloud<pcl::PointXYZ>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZ>); // 創建點雲數據 cloud->width = 1000; cloud->height = 1; //無序 cloud->points.resize (cloud->width * cloud->height); for (size_t i = 0; i < cloud->points.size (); ++i) //隨機循環產生點雲的坐標值 { cloud->points[i].x = 1024.0f * rand () / (RAND_MAX + 1.0f); cloud->points[i].y = 1024.0f * rand () / (RAND_MAX + 1.0f); cloud->points[i].z = 1024.0f * rand () / (RAND_MAX + 1.0f); } /**************************************************************************** 創建一個octree實例,用設置分辨率進行初始化,該octree用它的頁節點存放點索引向量, 分辨率參數描述最低一級octree的最小體素的尺寸,因此octree的深度是分辨率和點雲空間維度 的函數,如果知道點雲的邊界框,應該用defineBoundingbox方法把它分配給octree然后通過 點雲指針把所有點增加到ctree中。 *****************************************************************************/ float resolution = 128.0f; pcl::octree::OctreePointCloudSearch<pcl::PointXYZ> octree (resolution); //初始化Octree octree.setInputCloud (cloud); //設置輸入點雲 這兩句是最關鍵的建立PointCloud和octree之間的聯系 octree.addPointsFromInputCloud (); //構建octree pcl::PointXYZ searchPoint; //設置searchPoint searchPoint.x = 1024.0f * rand () / (RAND_MAX + 1.0f); searchPoint.y = 1024.0f * rand () / (RAND_MAX + 1.0f); searchPoint.z = 1024.0f * rand () / (RAND_MAX + 1.0f); /************************************************************************************* 一旦PointCloud和octree聯系一起,就能進行搜索操作,這里使用的是“體素近鄰搜索”,把查詢點所在體素中 其他點的索引作為查詢結果返回,結果以點索引向量的形式保存,因此搜索點和搜索結果之間的距離取決於octree的分辨率參數 *****************************************************************************************/ std::vector<int> pointIdxVec; //存儲體素近鄰搜索結果向量 if (octree.voxelSearch (searchPoint, pointIdxVec)) //執行搜索,返回結果到pointIdxVec { std::cout << "Neighbors within voxel search at (" << searchPoint.x << " " << searchPoint.y << " " << searchPoint.z << ")" << std::endl; for (size_t i = 0; i < pointIdxVec.size (); ++i) //打印結果點坐標 std::cout << " " << cloud->points[pointIdxVec[i]].x << " " << cloud->points[pointIdxVec[i]].y << " " << cloud->points[pointIdxVec[i]].z << std::endl; } /********************************************************************************** K 被設置為10 ,K近鄰搜索 方法把搜索結果寫到兩個分開的向量,第一個pointIdxNKNSearch包含搜索結果 (結果點的索引的向量) 第二個向量pointNKNSquaredDistance存儲搜索點與近鄰之間的距離的平方。 *************************************************************************************/ //K 近鄰搜索 int K = 10; std::vector<int> pointIdxNKNSearch; //結果點的索引的向量 std::vector<float> pointNKNSquaredDistance; //搜索點與近鄰之間的距離的平方 std::cout << "K nearest neighbor search at (" << searchPoint.x << " " << searchPoint.y << " " << searchPoint.z << ") with K=" << K << std::endl; if (octree.nearestKSearch (searchPoint, K, pointIdxNKNSearch, pointNKNSquaredDistance) > 0) { for (size_t i = 0; i < pointIdxNKNSearch.size (); ++i) std::cout << " " << cloud->points[ pointIdxNKNSearch[i] ].x << " " << cloud->points[ pointIdxNKNSearch[i] ].y << " " << cloud->points[ pointIdxNKNSearch[i] ].z << " (squared distance: " << pointNKNSquaredDistance[i] << ")" << std::endl; } // 半徑內近鄰搜索 std::vector<int> pointIdxRadiusSearch; std::vector<float> pointRadiusSquaredDistance; float radius = 256.0f * rand () / (RAND_MAX + 1.0f); std::cout << "Neighbors within radius search at (" << searchPoint.x << " " << searchPoint.y << " " << searchPoint.z << ") with radius=" << radius << std::endl; if (octree.radiusSearch (searchPoint, radius, pointIdxRadiusSearch, pointRadiusSquaredDistance) > 0) { for (size_t i = 0; i < pointIdxRadiusSearch.size (); ++i) std::cout << " " << cloud->points[ pointIdxRadiusSearch[i] ].x << " " << cloud->points[ pointIdxRadiusSearch[i] ].y << " " << cloud->points[ pointIdxRadiusSearch[i] ].z << " (squared distance: " << pointRadiusSquaredDistance[i] << ")" << std::endl; } }
建立八叉樹,根據不同的搜索形式搜索體素周圍的點雲。
結果如下:
Octree類關鍵點的說明
PCL octree組件提供了幾個octree類型,它們各自的葉節點特征基本上是不同的,
OctreePointCloudVector(等於OctreePointCloud):該octree能夠保存每一個節點上的點索引列。
OctreePointCloudSinglePoint: 該octree類僅僅保存每一個節點上的單個點的索引,僅僅保存最后分配給葉節點的點索引
OctreePointCloudOccupancy: 該octree類不存儲它的葉節點上的任何信息,它能用於空間填充情況檢查
OctreePointCloudDensity:存儲每一個葉節點體素中點的數目,它可以進行空間點集密集程度的查詢
(2) 無序點雲數據集的空間變化檢測
octree是一種管理稀疏3D數據的樹狀結構,利用octree實現多個無序點雲之間的空間變化檢測,這些點雲可能在尺寸。分辨率 密度,和點順序等方面有所差異,通過遞歸的比較octree的樹結構,可以鑒定出由octree產生的體素組成的區別所代表的空間變化,還要學習關於octree的“雙緩沖”技術,以便實時的探測多個點雲之間的空間組成的差異。
代碼分析
octree_change_detection.cpp :
#include <pcl/point_cloud.h> #include <pcl/octree/octree.h> #include <iostream> #include <vector> #include <ctime> int main (int argc, char** argv) { srand ((unsigned int) time (NULL)); //用系統時間初始化隨機種子 // 八叉樹的分辨率,即體素的大小 float resolution = 32.0f; // 初始化空間變化檢測對象 pcl::octree::OctreePointCloudChangeDetector<pcl::PointXYZ> octree (resolution); pcl::PointCloud<pcl::PointXYZ>::Ptr cloudA (new pcl::PointCloud<pcl::PointXYZ> ); //創建點雲實例cloudA生成的點雲數據用於建立八叉樹octree對象 // 為cloudA點雲填充點數據 cloudA->width = 128; //設置點雲cloudA的點數 cloudA->height = 1; //無序點 cloudA->points.resize (cloudA->width * cloudA->height); //總數 for (size_t i = 0; i < cloudA->points.size (); ++i) //循環填充 { cloudA->points[i].x = 64.0f * rand () / (RAND_MAX + 1.0f); cloudA->points[i].y = 64.0f * rand () / (RAND_MAX + 1.0f); cloudA->points[i].z = 64.0f * rand () / (RAND_MAX + 1.0f); } // 添加點雲到八叉樹中,構建八叉樹 octree.setInputCloud (cloudA); //設置輸入點雲 octree.addPointsFromInputCloud (); //從輸入點雲構建八叉樹 /*********************************************************************************** 點雲cloudA是參考點雲用其建立的八叉樹對象描述它的空間分布,octreePointCloudChangeDetector 類繼承自Octree2BufBae類,Octree2BufBae類允許同時在內存中保存和管理兩個octree,另外它應用了內存池 該機制能夠重新利用已經分配了的節點對象,因此減少了在生成點雲八叉樹對象時昂貴的內存分配和釋放操作 通過訪問 octree.switchBuffers ()重置八叉樹 octree對象的緩沖區,但把之前的octree數據仍然保留在內存中 ************************************************************************************/ // 交換八叉樹的緩沖,但是CloudA對應的八叉樹結構仍然在內存中 octree.switchBuffers (); //cloudB點雲用於建立新的八叉樹結構,與前一個cloudA對應的八叉樹共享octree對象,同時在內存中駐留 pcl::PointCloud<pcl::PointXYZ>::Ptr cloudB (new pcl::PointCloud<pcl::PointXYZ> ); //實例化點雲對象cloudB // 為cloudB創建點雲 cloudB->width = 128; cloudB->height = 1; cloudB->points.resize (cloudB->width * cloudB->height); for (size_t i = 0; i < cloudB->points.size (); ++i) { cloudB->points[i].x = 64.0f * rand () / (RAND_MAX + 1.0f); cloudB->points[i].y = 64.0f * rand () / (RAND_MAX + 1.0f); cloudB->points[i].z = 64.0f * rand () / (RAND_MAX + 1.0f); } // 添加cloudB到八叉樹中 octree.setInputCloud (cloudB); octree.addPointsFromInputCloud (); /************************************************************************************************************** 為了檢索獲取存在於couodB的點集R,此R並沒有cloudA中的元素,可以調用getPointIndicesFromNewVoxels方法,通過探測兩個八叉樹之間 體素的不同,它返回cloudB 中新加點的索引的向量,通過索引向量可以獲取R點集 很明顯這樣就探測了cloudB相對於cloudA變化的點集,但是只能探測 到在cloudA上增加的點集,二不能探測減少的 ****************************************************************************************************************/ std::vector<int> newPointIdxVector; //存儲新添加的索引的向量 // 獲取前一cloudA對應八叉樹在cloudB對應在八叉樹中沒有的點集 octree.getPointIndicesFromNewVoxels (newPointIdxVector); // 打印點集 std::cout << "Output from getPointIndicesFromNewVoxels:" << std::endl; for (size_t i = 0; i < newPointIdxVector.size (); ++i) std::cout << i << "# Index:" << newPointIdxVector[i] << " Point:" << cloudB->points[newPointIdxVector[i]].x << " " << cloudB->points[newPointIdxVector[i]].y << " " << cloudB->points[newPointIdxVector[i]].z << std::endl; }
本實驗是為了檢索獲取存在於couodB的點集R,此R並沒有cloudA中的元素,可以調用getPointIndicesFromNewVoxels方法,通過探測兩個八叉樹之間
體素的不同,它返回cloudB 中新加點的索引的向量,通過索引向量可以獲取R點集 很明顯這樣就探測了cloudB相對於cloudA變化的點集,但是只能探測
到在cloudA上增加的點集,二不能探測減少的
編譯運行的結果如下(因為生成的隨機點雲,所以運行的結果每次都會不一樣的):
微信公眾號號可掃描二維碼一起共同學習交流
未完待續*******************************************8888