k-d tree的優化查找算法BBF


  BBF(Best Bin First)是一種改進的k-d樹最近鄰查詢算法。從前兩篇標准的k-d樹查詢過程可以看出其搜索過程中的“回溯”是由“查詢路徑”來決定的,並沒有考慮查詢路徑上數據點本身的一些性質。BBF的查詢思路就是將“查詢路徑”上的節點進行排序,如按各自分割超平面(稱為Bin)與查詢點的距離排序。回溯檢查總是從優先級最高的(Best Bin)的樹節點開始。另外BBF還設置了一個運行超時限制,當優先級隊列中的所有節點都經過檢查或者超出時間限制時,算法返回當前找到的最好結果作為近似的最近鄰。采用了best-bin-first search方法就可以將k-d樹擴展到高維數據集上。

  下面我們通過大牛Rob Hess基於OpenCV的SIFT實現中的相關代碼來具體學習下BBF算法。

/*
Finds an image feature's approximate k nearest neighbors in a kd tree using
Best Bin First search.

@param kd_root root of an image feature kd tree
@param feat image feature for whose neighbors to search
@param k number of neighbors to find
@param nbrs pointer to an array in which to store pointers to neighbors
in order of increasing descriptor distance
@param max_nn_chks search is cut off after examining this many tree entries

@return Returns the number of neighbors found and stored in nbrs, or
-1 on error.
*/
//參數和返回值參看以上注釋
//基於k-d tree + bbf的k近鄰查找函數
int kdtree_bbf_knn( struct kd_node* kd_root, struct feature* feat, int k,
struct feature*** nbrs, int max_nn_chks )
{
struct kd_node* expl;      //expl是特征k-d tree中的一個節點
struct min_pq* min_pq; //min_pq是優先級隊列
struct feature* tree_feat, ** _nbrs;//tree_feat是一個SIFT特征,_nbrs中存放着查找出來的近鄰特征節點
struct bbf_data* bbf_data;   //bbf_data是一個用來存放臨時特征數據和特征間距離的緩存結構
int i, t = 0, n = 0;       //t是運行時限,n是查找出來的近鄰個數
if( ! nbrs || ! feat || ! kd_root )
{
fprintf( stderr, "Warning: NULL pointer error, %s, line %d\n",
__FILE__, __LINE__ );
return -1;
}

_nbrs = calloc( k, sizeof( struct feature* ) );  //給查找結果分配相應大小的內存
min_pq = minpq_init();                 //min_pq隊列初始化
minpq_insert( min_pq, kd_root, 0 );         //將根節點先插入到min_pq優先級隊列中
while( min_pq->n > 0 && t < max_nn_chks ) //min_pq隊列沒有回溯完且未達到時限
{
expl = (struct kd_node*)minpq_extract_min( min_pq );//從min_pq中提取優先級最高的節點(並移除)
if( ! expl )
{
fprintf( stderr, "Warning: PQ unexpectedly empty, %s line %d\n",
__FILE__, __LINE__ );
goto fail;
}

expl = explore_to_leaf( expl, feat, min_pq );    //從expl節點開始查找到葉子節點(下詳) 
if( ! expl )
{
fprintf( stderr, "Warning: PQ unexpectedly empty, %s line %d\n",
__FILE__, __LINE__ );
goto fail;
}

for( i = 0; i < expl->n; i++ )  //開始比較查找最近鄰
{
tree_feat = &expl->features[i];
bbf_data = malloc( sizeof( struct bbf_data ) );
if( ! bbf_data )
{
fprintf( stderr, "Warning: unable to allocate memory,"
" %s line %d\n", __FILE__, __LINE__ );
goto fail;
}
bbf_data->old_data = tree_feat->feature_data;
bbf_data->d = descr_dist_sq(feat, tree_feat);  //計算葉子節點特征和目標特征的距離
tree_feat->feature_data = bbf_data;
n += insert_into_nbr_array( tree_feat, _nbrs, n, k );//判斷並插入符合條件的近鄰到_nbrs中
}
t++;
}
   //釋放內存並返回結果
minpq_release( &min_pq );
for( i = 0; i < n; i++ )
{
bbf_data = _nbrs[i]->feature_data;
_nbrs[i]->feature_data = bbf_data->old_data;
free( bbf_data );
}
*nbrs = _nbrs;
return n;

fail:
minpq_release( &min_pq );
for( i = 0; i < n; i++ )
{
bbf_data = _nbrs[i]->feature_data;
_nbrs[i]->feature_data = bbf_data->old_data;
free( bbf_data );
}
free( _nbrs );
*nbrs = NULL;
return -1;
}

   整個kdtree_bbf_knn函數包括了優先級隊列的建立和k鄰近查找兩個過程。其中最關鍵的兩個數據結構就是min_pq優先級隊列和_nbrs存放k鄰近結果的隊列。min_pq優先級隊列是按照各節點的分割超平面和目標查詢特征點之間的距離升序排列的,第一個節點就是最小距離(優先級最高)的節點。另外_nbrs中也是按照與目標特征的距離升序排列,直接取結果的前k個特征就是對應的k近鄰。注意:上述代碼中的一些數據結構的定義以及一些對應的函數如:minpq_insert,minpq_extract_min, insert_into_nbr_array, descr_dist_sq等在這里就不貼了,詳細代碼可參看Rob Hess主頁(http://blogs.oregonstate.edu/hess/)中代碼參考文檔。

  下面來詳細看看函數explore_to_leaf是如何實現的。

/*
Explores a kd tree from a given node to a leaf. Branching decisions are
made at each node based on the descriptor of a given feature. Each node
examined but not explored is put into a priority queue to be explored
later, keyed based on the distance from its partition key value to the
given feature's desctiptor.

@param kd_node root of the subtree to be explored
@param feat feature upon which branching decisions are based
@param min_pq a minimizing priority queue into which tree nodes are placed
as described above

@return Returns a pointer to the leaf node at which exploration ends or
NULL on error.
*/
//參數和返回值參看以上注釋
//搜索路徑和優先級隊列的生成函數
static struct kd_node* explore_to_leaf( struct kd_node* kd_node, struct feature* feat,
struct min_pq* min_pq )
{
struct kd_node* unexpl, * expl = kd_node;  //unexpl中存放着優先級隊列的候選特征點
                             //expl為開始搜索節點
double kv;                     //kv是分割維度的數據
int ki;                     //ki是分割維度序號
while( expl && ! expl->leaf )
{
ki = expl->ki;                //獲得分割節點的ki,kv數據
kv = expl->kv;

if( ki >= feat->d )
{
fprintf( stderr, "Warning: comparing imcompatible descriptors, %s" \
" line %d\n", __FILE__, __LINE__ );
return NULL;
}
if( feat->descr[ki] <= kv )        //目標特征和分割節點分割維上的數據比較
{
unexpl = expl->kd_right;       //小於右子樹根節點成為候選節點 
expl = expl->kd_left; //並進入左子樹搜索
}
else
{
unexpl = expl->kd_left;        //大於左子樹根節點成為候選節點
expl = expl->kd_right;        //並進入右子樹搜索
}
     //將候選節點unexpl根據目標與分割超平面的距離插入到優先級隊列中
if( minpq_insert( min_pq, unexpl, ABS( kv - feat->descr[ki] ) ) )  
{
fprintf( stderr, "Warning: unable to insert into PQ, %s, line %d\n",
__FILE__, __LINE__ );
return NULL;
}
}

return expl;  //返回搜索路徑中最后的葉子節點
}

  explore_to_leaf函數的實現中可以看到,優先級隊列和搜索路徑是同時生成的,這也是BBF算法的精髓所在:在二叉搜索的時候將搜索路徑另一側的分支加入到優先級隊列中,供回溯時查找。而優先級隊列的排序就是根據目標特征與分割超平面的距離ABS( kv - feat->descr[ki] )

注意:是目標特征和分割超平面間的距離,不是候選節點和分割超平面的距離。如還是上兩篇例子中的數據,查找(2,4.5)的k近鄰,當搜索到(5,4)節點時,應將(4,7)節點加入搜索路徑而將(2,3)節點選為優先級隊列的候選節點,優先級的計算是:abs(4 - 4.5) = 0.5。

 

轉載請注明出處:http://www.cnblogs.com/eyeszjwang/articles/2437706.html

 


免責聲明!

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



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