KD-Tree 學習筆記

MoyouSayuki發表於2024-07-28

學習資料:

1.B站 - 一隻叫小花的貓
2.語雀 - 雙愚:kdtree
3.B站影片:學習kdtree的前置知識:KNN演算法

KD樹簡介與背景

  k-d樹,是一種分割k維資料空間的資料結構。主要應用於多維空間關鍵資料的搜尋。關於kd樹的背景,它主要是一種解決特徵點匹配問題的演算法,kd樹就是一種高維空間索引結構和近似查詢的演算法。
  關於特徵匹配運算元,可以分為兩類,第一種就是線性掃描法,但是這種方法將資料集中的點與查詢點逐一進行距離比較,也就是窮舉,缺點很明顯,搜尋效率較低。其次就是建立資料索引,而kdtree正是這樣一種方法,其基本思想就是對搜尋空間進行層次劃分,劃分空間沒有重疊。

KD樹的結構

下面這段話選自語雀 - 雙愚:kdtree
image
看似較為複雜,其實一點都不復雜,二叉排序樹學過吧,其實kdtree有些地方與二叉排序樹有點相似,即左小右大,但是嚴格上來講,又不是完全的相似,來看下面的圖:kdtree的構造過程:
image
有這樣的一些點集,在座標系中畫出這些點,首先我們按照垂直於x軸進行劃分,如圖(1),找到每個點的橫座標,2、3、4、7、8、9,取中位數為(4+7)/ 2 = 5.5,那麼既然與4和7的距離都是1.5,所以我們可以取7作為一個劃分的標準,分成左右兩塊,這時就把點(7,2)加入到樹中,接著我們就按照y軸進行劃分,取左右兩塊分別劃分,與上面的邏輯是相同的,劃分完畢再把點加入到樹中,要注意左小右大,以此類推即可劃分成功。注意:當一科kdtree的子樹結點只有一個孩子時,不存在左右孩子這種說法。

KD樹查詢最鄰近點演算法

如圖(選自上面b站影片):imageimage

在這裡我們想找(3, 4.5)的鄰近點,分為以下幾個步驟:

  1. 首先看樹根,這裡記為第1層,樹根是x(1)(這裡我們把x(1)看為x軸,x(2)看為y軸),因為要找橫軸為3的點,故找比7小的,這時到達(5,4)。
  2. (5,4)在y的行中,所以這時我們看4,由於4.5>4, 這時進入到(4, 7)。
  3. 到達葉子結點(4, 7),這時我們把它作為當前最臨近點,計算到(3, 4.5)的距離為2.69。
  4. 開始回溯,到(5, 4),距離為2.06,更新最鄰近點。這時我們發現,(3, 4.5)與(5, 4)連線為半徑做圓,與y=4超平面相交,那麼這時還需要進入到另一個子空間進行查詢,也就是得回溯到(2, 3)這裡,計算後,距離為1.8。
  5. 再次做圓,此圓與x=7並不相交,故不用進入到根結點右子樹進行查詢。結束。

KD樹上的KNN演算法

image

KD樹相關函式

PCL中類pcl::KdTree<PointT>是kd-tree資料結構的實現。並且提供基於FLANN進行快速搜尋的一些相關子類與包裝類。具體可以參考相應的API。下面給出2個類的具體用法。

  1. pcl::search::KdTree < PointT >
    pcl::search::KdTree<PointT>pcl::search::Search< PointT >的子類,是pcl::KdTree<PointT>的包裝類。包含(1) k 近鄰搜尋;(2) 鄰域半徑搜尋。
    示例程式碼:
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/search/kdtree.h> // 包含kdtree標頭檔案
typedef pcl::PointXYZ PointT;
int main()
{
	pcl::PointCloud<PointT>::Ptr cloud(new pcl::PointCloud<PointT>);
	pcl::io::loadPCDFile("read.pcd", *cloud);
	// 定義KDTree物件
	pcl::search::KdTree<PointT>::Ptr kdtree(new pcl::search::KdTree<PointT>);
	kdtree->setInputCloud(cloud); // 設定要搜尋的點雲,建立KDTree
	std::vector<int> indices; // 儲存查詢近鄰點索引
	std::vector<float> distances; // 儲存近鄰點對應距離的平方
	PointT point = cloud->points[0]; // 初始化一個查詢點
	
	// 查詢距point最近的k個點
	int k = 10;
	int size = kdtree->nearestKSearch(point, k, indices, distances);
	std::cout << "search point : " << size << std::endl;
	// 查詢point半徑為radius鄰域球內的點
	double radius = 2.0;
	size = kdtree->radiusSearch(point, radius, indices, distances);
	std::cout << "search point : " << size << std::endl;
	system("pause");
	return 0;
}
  1. pcl::KdTreeFLANN < PointT >
    pcl::KdTreeFLANN<PointT>pcl::KdTree<PointT>的子類,可以實現同樣的功能。
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
// 包含相關標頭檔案
#include <pcl/kdtree/kdtree_flann.h>
typedef pcl::PointXYZ PointT;
int main()
{
	pcl::PointCloud<PointT>::Ptr cloud(new pcl::PointCloud<PointT>);
	pcl::io::loadPCDFile("read.pcd", *cloud);
	pcl::KdTreeFLANN<pcl::PointXYZ> kdtree; //建立KDtree
	kdtree.setInputCloud(cloud); // 設定要搜尋的點雲,建立KDTree
	std::vector<int> indices; // 儲存查詢近鄰點索引
	std::vector<float> distances; // 儲存近鄰點對應距離的平方
	PointT point = cloud->points[0]; // 初始化一個查詢點
	
	// 查詢距point最近的k個點
	int k = 10;
	int size = kdtree.nearestKSearch(point, k, indices, distances);
	std::cout << "search point : " << size << std::endl;
	// 查詢point半徑為radius鄰域球內的點
	double radius = 2.0;
	size = kdtree.radiusSearch(point, radius, indices, distances);
	std::cout << "search point : " << size << std::endl;
	system("pause");
	return 0;
}