在k-d tree樹中進行數據的k近鄰搜索是特征匹配的重要環節,其目的是檢索在k-d tree中與待查詢點距離最近的k個數據點。
最近鄰搜索是k近鄰的特例,也就是1近鄰。將1近鄰改擴展到k近鄰非常容易。下面介紹最簡單的k-d tree最近鄰搜索算法。
基本的思路很簡單:首先通過二叉樹搜索(比較待查詢節點和分裂節點的分裂維的值,小於等於就進入左子樹分支,大於就進入右子樹分支直到葉子結點),順着“搜索路徑”很快能找到最近鄰的近似點,也就是與待查詢點處於同一個子空間的葉子結點;然后再回溯搜索路徑,並判斷搜索路徑上的結點的其他子結點空間中是否可能有距離查詢點更近的數據點,如果有可能,則需要跳到其他子結點空間中去搜索(將其他子結點加入到搜索路徑)。重復這個過程直到搜索路徑為空。
算法:kdtreeFindNearest 輸入:Kd target 輸出 : nearest dist 1. 如果Kd是空的,則設dist為無窮大返回 2. 向下搜索直到葉子結點 pSearch = &Kd while(pSearch != NULL) { pSearch加入到search_path中; if(target[pSearch->split] <= pSearch->dom_elt[pSearch->split]) { pSearch = pSearch->left; } else { pSearch = pSearch->right; } } 取出search_path最后一個賦給nearest dist = Distance(nearest, target); 3. 回溯搜索路徑 while(search_path不為空) { 取出search_path最后一個結點賦給pBack if(pBack->left為空 && pBack->right為空) { if( Distance(nearest, target) > Distance(pBack->dom_elt, target) ) { nearest = pBack->dom_elt; dist = Distance(pBack->dom_elt, target); } } else { s = pBack->split; if( abs(pBack->dom_elt[s] - target[s]) < dist) { if( Distance(nearest, target) > Distance(pBack->dom_elt, target) ) { nearest = pBack->dom_elt; dist = Distance(pBack->dom_elt, target); } if(target[s] <= pBack->dom_elt[s]) pSearch = pBack->right; else pSearch = pBack->left; if(pSearch != NULL) pSearch加入到search_path中 } } }
現在舉一些例子來說明上面的最近鄰搜索算法,假設我們的k-d tree就是上面通過樣本集{(2,3), (5,4), (9,6), (4,7), (8,1), (7,2)}創建的。將上面的圖轉化為樹形圖的樣子如下:
我們來查找點(2.1,3.1),在(7,2)點測試到達(5,4),在(5,4)點測試到達(2,3),然后search_path中的結點為<(7,2), (5,4), (2,3)>,從search_path中取出(2,3)作為當前最佳結點nearest, dist為0.141;
然后回溯至(5,4),以(2.1,3.1)為圓心,以dist=0.141為半徑畫一個圓,並不和超平面y=4相交,如下圖,所以不必跳到結點(5,4)的右子空間去搜索,因為右子空間中不可能有更近樣本點了。
於是在回溯至(7,2),同理,以(2.1,3.1)為圓心,以dist=0.141為半徑畫一個圓並不和超平面x=7相交,所以也不用跳到結點(7,2)的右子空間去搜索。
至此,search_path為空,結束整個搜索,返回nearest(2,3)作為(2.1,3.1)的最近鄰點,最近距離為0.141。
再舉一個稍微復雜的例子,我們來查找點(2,4.5),在(7,2)處測試到達(5,4),在(5,4)處測試到達(4,7),然后search_path中的結點為<(7,2), (5,4), (4,7)>,從search_path中取出(4,7)作為當前最佳結點nearest, dist為3.202;
然后回溯至(5,4),以(2,4.5)為圓心,以dist=3.202為半徑畫一個圓與超平面y=4相交,如下圖,所以需要跳到(5,4)的左子空間去搜索。所以要將(2,3)加入到search_path中,現在search_path中的結點為<(7,2), (2, 3)>;另外,(5,4)與(2,4.5)的距離為3.04 < dist = 3.202,所以將(5,4)賦給nearest,並且dist=3.04。
回溯至(2,3),(2,3)是葉子節點,直接判斷(2,3)是否離(2,4.5)更近,計算得到距離為1.5,所以nearest更新為(2,3),dist更新為(1.5)
回溯至(7,2),同理,以(2,4.5)為圓心,以dist=1.5為半徑畫一個圓並不和超平面x=7相交, 所以不用跳到結點(7,2)的右子空間去搜索。
至此,search_path為空,結束整個搜索,返回nearest(2,3)作為(2,4.5)的最近鄰點,最近距離為1.5。
兩次搜索的返回的最近鄰點雖然是一樣的,但是搜索(2, 4.5)的過程要復雜一些,因為(2, 4.5)更接近超平面。研究表明,當查詢點的鄰域與分割超平面兩側的空間都產生交集時,回溯的次數大大增加。最壞的情況下搜索N個結點的k維kd-tree所花費的時間為:
關於k-d tree還有很多擴展。由於大量回溯會導致kd-tree最近鄰搜索的性能大大下降,因此研究人員也提出了改進的k-d tree近鄰搜索,其中一個比較著名的就是 Best-Bin-First,它通過設置優先級隊列和運行超時限定來獲取近似的最近鄰,有效地減少回溯的次數。
參考資料
1.An intoductory tutorial on kd-trees Andrew W.Moore
2.《圖像局部不變特性特征與描述》王永明 王貴錦 編著 國防工業出版社
3.kdtree A simple C library for working with KD-Trees