RobHess的SIFT代碼解析之kd樹

平臺:win10 x64 +VS 2015專業版 +opencv-2.4.11 + gtk_-bundle_2.24.10_win32html

主要參考:1.代碼:RobHess的SIFT源碼:SIFT+KD樹+BBF算法+RANSAC算法node

2.書:王永明 王貴錦 《圖像局部不變性特徵與描述》linux

 

 

RobHess的SIFT源碼中的幾個文件說明?程序員

RobHessSIFT源碼分析:算法

(1) minpq.h和minpq.c文件
這兩個文件中實現了最小優先級隊列(Minimizing Priority Queue),也就是小頂堆,在k-d樹的創建和搜索過程當中要用到。

(2) kdtree.h和kdtree.c文件
這兩個文件中實現了k-d樹的創建以及用BBF(Best Bin First)算法搜索匹配點的函數。
若是你須要對兩個圖片中的特徵點進行匹配,就要用到這兩個文件。
數據庫

 

RobHess的SIFT源碼中使用的結構體及存儲說明?數組

(1)minpq.h——結構體min_pq和pq_node
    minpq.c——無
緩存

struct pq_node //最小化優先級隊列中的元素安全

{app

  void* data;

  int key;

};

struct min_pq //最小化優先級隊列

{

  struct pq_node* pq_array;    /*數組包含優先級隊列 */

  int nallocd;                 /* 分配的元素數 */

  int n;                       /*pq中的元素數量*/

};

 

(2)kdtree.h——結構體kd_node
    kdtree.c文件——結構體bbf_data

struct kd_node //K-D樹中的結點結構

{

  int ki;                      /*分割位置(樞軸)的維數索引(哪一維是分割位置),取值爲1-128*/

  double kv;                   /*樞軸的值(全部特徵向量在樞軸索引維數上的份量的中值)*/

  int leaf;                    /*是否葉子結點的標誌 1 if node is a leaf, 0 otherwise */

  struct feature* features;    /*此結點對應的特徵點集合(數組)*/

  int n;                       /*特徵點的個數*/

  struct kd_node* kd_left;     /*左子樹*/

  struct kd_node* kd_right;    /*右子樹*/

};

 

struct bbf_data

{

  double d;

  void* old_data;

};

 

請在看這個以前先看完如下內容前四個:

 

SIFT四步驟和特徵匹配及篩選:

步驟一:創建尺度空間,即創建高斯差分(DoG)金字塔dog_pyr

步驟二:在尺度空間中檢測極值點,並進行精肯定位和篩選建立默認大小的內存存儲器

步驟三:特徵點方向賦值,完成此步驟後,每一個特徵點有三個信息:位置、尺度、方向

步驟四:計算特徵描述子

SIFT後特徵匹配:KD樹+BBF算法

SIFT後特徵匹配後錯誤點篩選:RANSAC算法

 

 

SIFT後特徵匹配:KD樹+BBF算法

問題及解答:

 

(1)問題描述:SIFT檢測的特徵點後,爲何用KD樹算法進行特徵匹配?

答: 找到問題的本質:

以前網上有blog內曾經介紹過SIFT特徵匹配算法,特徵點匹配和數據庫查、圖像檢索本質上是同一個問題,均可以歸結爲一個經過距離函數在高維矢量之間進行類似性檢索的問題,如何快速而準確地找到查詢點的近鄰,很多人提出了不少高維空間索引結構和近似查詢的算法。
通常說來,索引結構中類似性查詢有兩種基本的方式:
一種是範圍查詢,範圍查詢時給定查詢點和查詢距離閾值,從數據集中查找全部與查詢點距離小於閾值的數據
另外一種是K近鄰查詢,就是給定查詢點及正整數K,從數據集中找到距離查詢點最近的K個數據,當K=1時,它就是最近鄰查詢。
一樣,針對特徵點匹配也有兩種方法:
最容易的辦法就是線性掃描,也就是咱們常說的窮舉搜索,依次計算樣本集E中每一個樣本到輸入實例點的距離,而後抽取出計算出來的最小距離的點即爲最近鄰點。此種辦法簡單直白,但當樣本集或訓練集很大時,它的缺點就立馬暴露出來了,舉個例子,在物體識別的問題中,可能有數千個甚至數萬個SIFT特徵點,而去一一計算這成千上萬的特徵點與輸入實例點的距離,明顯是不足取的。
另一種,就是構建數據索引,由於實際數據通常都會呈現簇狀的聚類形態,所以咱們想到創建數據索引,而後再進行快速匹配。索引樹是一種樹結構索引方法,其基本思想是對搜索空間進行層次劃分。根據劃分的空間是否有混疊能夠分爲Clipping和Overlapping兩種。前者劃分空間沒有重疊,其表明就是k-d樹;後者劃分空間相互有交疊,其表明爲R樹。

 

什麼是k-d樹?

首先必須搞清楚的是,k-d樹是一種空間劃分樹,說白了,就是把整個空間劃分爲特定的幾個部分,而後在特定空間的部份內進行相關搜索操做。想像一個三維(多維有點爲難你的想象力了)空間,kd樹按照必定的劃分規則把這個三維空間劃分了多個空間,以下圖所示:

 

 

(2)問題描述:RobHess的源碼如何實現RANSAC,大概思路是這樣的?

答:2.1)代碼及說明:——match.c的main函數裏

//特徵匹配

  fprintf( stderr, "Building kd tree...\n" ); //創建kd樹

  kd_root = kdtree_build( feat2, n2 ); //根據圖2的特徵點集feat2創建k-d樹,返回k-d樹根給kd_root

  //遍歷特徵點集feat1,針對feat1中每一個特徵點feat,選取符合距離比值條件的匹配點,放到feat的fwd_match域中

  for( i = 0; i < n1; i++ )    //逐點匹配

    {

      feat = feat1 + i; //第i個特徵點的指針

           //在kd_root中搜索目標點feat的2個最近鄰點,存放在nbrs中,返回實際找到的近鄰點個數

      k = kdtree_bbf_knn( kd_root, feat, 2, &nbrs, KDTREE_BBF_MAX_NN_CHKS );          //找2個最近點

      if( k == 2 ) //只有進行2次以上匹配過程,纔算是正常匹配過程

         {

           d0 = descr_dist_sq( feat, nbrs[0] ); //feat與最近鄰點的距離的平方

           d1 = descr_dist_sq( feat, nbrs[1] ); //feat與次近鄰點的距離的平方

           //若d0和d1的比值小於閾值NN_SQ_DIST_RATIO_THR,則接受此匹配,不然剔除

           if( d0 < d1 * NN_SQ_DIST_RATIO_THR )      //最近點與次最近點距離之比要小才當作正確匹配,而後畫一條線

             {

                     //pt1,pt2爲連線的兩個端點,將目標點feat和最近鄰點做爲匹配點對

                     pt1 = cvPoint( cvRound( feat->x ), cvRound( feat->y ) );

               pt2 = cvPoint( cvRound( nbrs[0]->x ), cvRound( nbrs[0]->y ) );

               pt2.y += img1->height; //因爲兩幅圖是上下排列的,pt2的縱座標加上圖1的高度,做爲連線的終點

               cvLine( stacked, pt1, pt2, CV_RGB(255,0,255), 1, 8, 0 );  //畫出連線

               m++; //統計匹配點對的個數

               feat1[i].fwd_match = nbrs[0]; //使點feat的fwd_match域指向其對應的匹配點

             }

         }

      free( nbrs ); //釋放近鄰數組

    }

 

  fprintf( stderr, "Found %d total matches\n", m ); //總共找到多少組匹配

  display_big_img( stacked, "Matches" );

  cvWaitKey( 0 );

 

(2.2)kdtree_build代碼及說明:

答:

/*根據給定的特徵點集合創建k-d樹
參數:
features:特徵點數組,注意:此函數將會改變features數組中元素的排列順序
n:特徵點個數
返回值:創建好的k-d樹的樹根指針
*/
struct kd_node* kdtree_build( struct feature* features, int n )
{
    struct kd_node* kd_root;
 
    //輸入參數檢查
    if( ! features  ||  n <= 0 )
    {
        fprintf( stderr, "Warning: kdtree_build(): no features, %s, line %d\n",
                __FILE__, __LINE__ );
        return NULL;
    }
 
    //調用函數,用給定的特徵點集初始化k-d樹節點,返回值做爲樹根
    kd_root = kd_node_init( features, n );
    //調用函數,擴展根節點kd_root及其左右孩子
    expand_kd_node_subtree( kd_root );
 
    return kd_root;
}

(2.2.1)kd_node_init代碼及說明:

答:

/*用給定的特徵點集初始化k-d樹節點
參數:
features:特徵點集
n:特徵點個數
返回值:k-d樹節點指針
*/
static struct kd_node* kd_node_init( struct feature* features, int n )
{
    struct kd_node* kd_node;
 
    kd_node = malloc( sizeof( struct kd_node ) );//分配內存
    memset( kd_node, 0, sizeof( struct kd_node ) ); //用0填充
    kd_node->ki = -1;//樞軸索引
    kd_node->features = features;//節點對應的特徵點集
    kd_node->n = n;//特徵點的個數
 
    return kd_node;
}

問題:memset函數說明?

答:void *memset(void *s, int ch, size_t n);
函數解釋:將s中當前位置後面的n個字節 (typedef unsigned int size_t )用 ch 替換並返回 s 。
memset:做用是在一段內存塊中填充某個給定的值,它是對較大的結構體或數組進行清零操做的一種最快方法  。
memset()函數原型是extern void *memset(void *buffer, int c, int count) buffer:爲指針或是數組,c:是賦給buffer的值,count:是buffer的長度.

參看:百度百科(memset):https://baike.baidu.com/item/memset/4747579?fr=aladdin

 

(2.2.2)expand_kd_node_subtree代碼及說明:

答:

/*

遞歸的擴展指定的k-d樹節點及其左右孩子

kd_node爲k-d樹中未展開節點

*/
static void expand_kd_node_subtree( struct kd_node* kd_node )
{
    //基本狀況:葉子節點
    /* base case: leaf node */
    if( kd_node->n == 1  ||  kd_node->n == 0 )
    {
        kd_node->leaf = 1;//葉節點標誌位設爲1
        return;
    }
 
    //調用函數,肯定節點的樞軸索引和樞軸值
    assign_part_key( kd_node ); //ki:方差最大的那個維度;kv:各特徵點在那個維度上的中間值
    //在指定k-d樹節點上劃分特徵點集(即根據指定節點的ki和kv值來劃分特徵點集)
    partition_features( kd_node );//特徵點ki位置左樹比右樹模值小,kv做爲分界模值


 
    //繼續擴展左右孩子
    if( kd_node->kd_left )
        expand_kd_node_subtree( kd_node->kd_left ); //遞歸,展開左子樹
    if( kd_node->kd_right )
        expand_kd_node_subtree( kd_node->kd_right ); //遞歸,展開右子樹
}

(2.2.2.1)assign_part_key代碼及說明:

答:

/*肯定輸入節點的樞軸索引和樞軸值
參數:kd_node:輸入的k-d樹節點
函數執行完後將給kd_node的ki和kv成員複製
*/
static void assign_part_key( struct kd_node* kd_node )
{
    struct feature* features;
    //樞軸的值kv,均值mean,方差var,方差最大值var_max
    double kv, x, mean, var, var_max = 0;
    double* tmp;
    int d, n, i, j, ki = 0; //樞軸索引ki
 
    features = kd_node->features;
    n = kd_node->n;//結點個數
    d = features[0].d;//特徵向量的維數
 
    //樞軸的索引值就是方差最大的那一維的維數,即n個128維的特徵向量中,若第k維的方差最大,則k就是樞軸(分割位置)
    /* partition key index is that along which descriptors have most variance */
    for( j = 0; j < d; j++ )
    {
        mean = var = 0;
        //求第j維的均值
        for( i = 0; i < n; i++ )
            mean += features[i].descr[j];
        mean /= n;
        //求第j維的方差
        for( i = 0; i < n; i++ )
        {
            x = features[i].descr[j] - mean;
            var += x * x;
        }
        var /= n;
        //找到最大方差的維數
        if( var > var_max ) //書P154
        {
            ki = j;//最大方差的維數就是樞軸
            var_max = var;
        }
    }
 
    //樞軸的值就是全部特徵向量的ki維的中值(按ki維排序後中間的那個值)
    tmp = calloc( n, sizeof( double ) ); //分配空間
    for( i = 0; i < n; i++ )
        tmp[i] = features[i].descr[ki]; //n個特徵點在ki維上的描述子,賦給tmp數組
    //調用函數,找tmp數組的中值
    kv = median_select( tmp, n ); //kv:各特徵點在那個維度上的中間值
    free( tmp );
 
    kd_node->ki = ki;//樞軸的維數索引
    kd_node->kv = kv;//樞軸的值
}

(2.2.2.1.1)median_select代碼及說明:

答:

/*找到輸入數組的中值
參數:
array:輸入數組,元素順序將會被改動
n:元素個數
返回值:中值
*/
static double median_select( double* array, int n )
{
    //調用函數,找array數組中的第(n-1)/2小的數,即中值
    return rank_select( array, n, (n - 1) / 2 );
}

(2.2.2.1.1.1)rank_select代碼及說明:

答:

/*找到輸入數組中第r小的數
參數:
array:輸入數組,元素順序將會被改動
n:元素個數
r:找第r小元素,要選擇的元素從0開始的等級
返回值:第r小的元素值
*/ //該函數從新排列數組中的元素
static double rank_select( double* array, int n, int r )
{
    double* tmp, med;
    int gr_5, gr_tot, rem_elts, i, j;
 
    /* base case */
    if( n == 1 ) //1個特徵點,返回array[0]
        return array[0];
 
    //將數組分紅5個一組,共gr_tot組
    /* divide array into groups of 5 and sort them */
    gr_5 = n / 5; //組的個數-1,n/5向下取整
    gr_tot = cvCeil( n / 5.0 ); //組的個數,n/5向上取整(cvCeil向上取整
    rem_elts = n % 5;//最後一組中的元素個數
    tmp = array;
    //對每組進行插入排序
    for( i = 0; i < gr_5; i++ )
    {
        insertion_sort( tmp, 5 );
        tmp += 5; //數組下標加5
    }
    //最後一組,不足5個
    insertion_sort( tmp, rem_elts );
 
    //以遞歸的方式查找5組中位數的中位數,找中值的中值
    /* recursively find the median of the medians of the groups of 5 */
    tmp = calloc( gr_tot, sizeof( double ) );
    //將每一個5元組中的中值(即下標爲2,2+5,...的元素)複製到temp數組
    for( i = 0, j = 2; i < gr_5; i++, j += 5 )
        tmp[i] = array[j];
    //最後一組的中值
    if( rem_elts )
        tmp[i++] = array[n - 1 - rem_elts/2];
    //找temp中的中值med,即中值的中值
    med = rank_select( tmp, i, ( i - 1 ) / 2 );
    free( tmp );
 
    //利用中值的中值劃分數組,看劃分結果是不是第r小的數,若不是則遞歸調用rank_select從新選擇
    /* partition around median of medians and recursively select if necessary */
    j = partition_array( array, n, med );//劃分數組,返回med在新數組中的索引
    if( r == j )//結果是第r小的數
        return med;
    else if( r < j )//第r小的數在前半部分
        return rank_select( array, j, r );
    else//第r小的數在後半部分
    {
        array += j+1;
        return rank_select( array, ( n - j - 1 ), ( r - j - 1 ) );
    }
}

(2.2.2.1.1.1.1)insertion_sort代碼及說明:

答:

/*用插入法對輸入數組進行升序排序
參數:
array:輸入數組
n:元素個數
*/
static void insertion_sort( double* array, int n )
{
    double k;
    int i, j;
 
    for( i = 1; i < n; i++ )
    {
        k = array[i];
        j = i-1;
        while( j >= 0  &&  array[j] > k )
        {
            array[j+1] = array[j];
            j -= 1;
        }
        array[j+1] = k;
    }
}

(2.2.2.1.1.1.2)partition_array代碼及說明:

答:

/*根據給定的樞軸值分割數組,使數組前部分小於pivot,後部分大於pivot
參數:
array:輸入數組
n:數組的元素個數
pivot:樞軸值
返回值:分割後樞軸的下標
*/
static int partition_array( double* array, int n, double pivot )
{
    double tmp;
    int p, i, j;
 
    i = -1;
    for( j = 0; j < n; j++ )
        if( array[j] <= pivot )
        {
            tmp = array[++i];
            array[i] = array[j];
            array[j] = tmp;
            if( array[i] == pivot )
                p = i;//p保存樞軸的下標
        }
    //將樞軸和最後一個小於樞軸的數對換
    array[p] = array[i];
    array[i] = pivot;
 
    return i; //在分區後返回數據透視的索引
}

(2.2.2.2)partition_features代碼及說明:

答:

 /*在指定的k-d樹節點上劃分特徵點集
使得特徵點集的前半部分是第ki維小於樞軸的點,後半部分是第ki維大於樞軸的點
*/
static void partition_features( struct kd_node* kd_node )
{
    struct feature* features, tmp;
    double kv;
    int n, ki, p, i, j = -1;
 
    features = kd_node->features;//特徵點數組
    n = kd_node->n;//特徵點個數
    //printf("%d\n",n);
    ki = kd_node->ki;//樞軸的維數索引(哪一維是樞軸)ki:方差最大的那個維度
    kv = kd_node->kv;//樞軸的值 kv:各特徵點在那個維度上的中間值
    for( i = 0; i < n; i++ )
    {
        //若第i個特徵點的特徵向量的第ki維的值小於kv
        if( features[i].descr[ki] <= kv ) //左子樹特徵點的數據
        {
            tmp = features[++j];
            features[j] = features[i];
            features[i] = tmp;
            if( features[j].descr[ki] == kv )
                p = j;//p保存樞軸所在的位置
        }
    }
    //將樞軸features[p]和最後一個小於樞軸的點features[j]對換
    tmp = features[p];
    features[p] = features[j];
    features[j] = tmp;
    //此後,樞軸的位置下標爲j
 
    //若全部特徵點落在同一側,則此節點成爲葉節點
    /* if all records fall on same side of partition, make node a leaf */
    if( j == n - 1 )
    {
        kd_node->leaf = 1;
        return;
    }
 
    //初始化左孩子的根節點,左孩子共j+1個特徵點
    kd_node->kd_left = kd_node_init( features, j + 1 );
    //初始化右孩子的根節點,右孩子共n-j-1個特徵點
    kd_node->kd_right = kd_node_init( features + ( j + 1 ), ( n - j - 1 ) );
}


(2.3)kdtree_bbf_knn代碼及說明:

答:

/*用BBF算法在k-d樹中查找指定特徵點的k個最近鄰特徵點
參數:
kd_root:圖像特徵的k-d樹的樹根
feat:目標特徵點
k:近鄰個數
nbrs:k個近鄰特徵點的指針數組,按到目標特徵點的距離升序排列
      此數組的內存將在本函數中被分配,使用完後必須在調用出釋放:free(*nbrs)
max_nn_chks:搜索的最大次數,超過此值再也不搜索
返回值:存儲在nbrs中的近鄰個數,返回-1表示失敗
*/
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是當前搜索節點
    struct 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優先級隊列中
 
    //min_pq隊列沒有回溯完且未達到搜索最大次數
    while( min_pq->n > 0  &&  t < max_nn_chks )
    {
        //從min_pq中提取(並移除)優先級最高的節點,賦值給當前節點expl
        expl = (struct kd_node*)minpq_extract_min( min_pq );
        if( ! expl )
        {   //出錯處理
            fprintf( stderr, "Warning: PQ unexpectedly empty, %s line %d\n",__FILE__, __LINE__ );
            goto fail;
        }
        //從當前搜索節點expl一直搜索到葉子節點,搜索過程當中將未搜索的節點根據優先級放入隊列,返回值爲葉子節點
        expl = explore_to_leaf( expl, feat, min_pq );
        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];//第i個特徵點的指針
            bbf_data = malloc( sizeof( struct bbf_data ) );//新建bbf結構
            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;//保存第i個特徵點的feature_data域之前的值
            bbf_data->d = descr_dist_sq(feat, tree_feat);//當前搜索點和目標點之間的歐氏距離
            tree_feat->feature_data = bbf_data;//將bbf結構賦給此特徵點的feature_data域
            //判斷並插入符合條件的特徵點到最近鄰數組_nbrs中,插入成功返回1
            //當最近鄰數組中元素個數已達到k時,繼續插入元素個數不會增長,但會更新元素的值
            n += insert_into_nbr_array( tree_feat, _nbrs, n, k );
        }
        t++;//搜索次數
    }
 
    minpq_release( &min_pq );//釋放優先隊列
 
    //對於最近鄰數組中的特徵點,恢復其feature_data域的值
    for( i = 0; i < n; i++ )
    {
        bbf_data = _nbrs[i]->feature_data;
        _nbrs[i]->feature_data = bbf_data->old_data;//將以前的數據賦值給feature_data域
        free( bbf_data );
    }
    *nbrs = _nbrs;
    return n;
 
    //失敗處理
fail:
    minpq_release( &min_pq );
    //對於最近鄰數組中的特徵點,恢復其feature_data域的值
    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;
}

(2.3.1)minpq_init代碼及說明:

答:

/*Creates a new minimizing priority queue.建立新的最小化優先級隊列。*/
struct min_pq* minpq_init()
{
  struct min_pq* min_pq;

  min_pq = malloc( sizeof( struct min_pq ) );
  min_pq->pq_array = calloc( MINPQ_INIT_NALLOCD, sizeof( struct pq_node ) );
  min_pq->nallocd = MINPQ_INIT_NALLOCD; //分配的元素數,512
  min_pq->n = 0; //pq中元素數量

  return min_pq;
}

問題:calloc函數說明?calloc與malloc有什麼區別?

答:函數原型:void *calloc(size_t n, size_t size);
功 能: 在內存的動態存儲區中分配n個長度爲size的連續空間,函數返回一個指向分配起始地址的指針;若是分配不成功,返回NULL。

malloc/calloc/realloc/alloca內存分配函數的區別?

在說明它們具體含義以前,先簡單從字面上加以認識,前3個函數有個共同的特色,就是都帶有字符」alloc」,就是」allocate」,」分配」的意思,也就是給對象分配足夠的內存,」 calloc()」是」分配內存給多個對象」,」 malloc()」是」分配內存給一個對象」,」realloc()」是」從新分配內存」之意。」free()」就比較簡單了,」釋放」的意思,就是把以前所分配的內存空間給釋放出來。

void *calloc(size_t nobj, size_t size);

分配足夠的內存給nobj個大小爲size的對象組成的數組, 並返回指向所分配區域的第一個字節的指針;
若內存不夠,則返回NULL. 該空間的初始化大小爲0字節.
char *p = (char *) calloc(100,sizeof(char));

void *malloc(size_t size);
分配足夠的內存給大小爲size的對象, 並返回指向所分配區域的第一個字節的指針;
若內存不夠,則返回NULL. 不對分配的空間進行初始化.
char *p = (char *)malloc(sizeof(char));
void *realloc(void *p, size_t size);
將p所指向的對象的大小改成size個字節.
若是新分配的內存比原內存大, 那麼原內存的內容保持不變, 增長的空間不進行初始化.
若是新分配的內存比原內存小, 那麼新內存保持原內存的內容, 增長的空間不進行初始化.
返回指向新分配空間的指針; 若內存不夠,則返回NULL, 原p指向的內存區不變.
char *p = (char *)malloc(sizeof(char));
p= (char *)realloc(p, 256);
void free(void *p);
釋放p所指向的內存空間; 當p爲NULL時, 不起做用.
p必先調用calloc, malloc或realloc.
值得注意的有如下5點:

  1)經過malloc函數獲得的堆內存必須使用memset函數來初始化
malloc函數分配獲得的內存空間是未初始化的。所以,通常在使用該內存空間時,要調用另外一個函數memset來將其初始化爲全0,memset函數的聲明以下:void * memset (void * p,int c,int n) ;
該函數能夠將指定的內存空間按字節單位置爲指定的字符c,其中,p爲要清零的內存空間的首地址,c爲要設定的值,n爲被操做的內存空間的字節長度。若是要用memset清0,變量c實參要爲0。

malloc函數和memset函數的操做語句通常以下:
int * p=NULL;
p=(int*)malloc(sizeof(int));
if(p==NULL)
printf(「Can’t get memory!\n」);
memset(p,0,siezeof(int));
  2)使用malloc函數分配的堆空間在程序結束以前必須釋放
從堆上得到的內存空間在程序結束之後,系統不會將其自動釋放,須要程序員來本身管理。一個程序結束時,必須保證全部從堆上得到的內存空間已被安全釋放,不然,會致使內存泄露。
咱們可使用free()函數來釋放內存空間,可是,free函數只是釋放指針指向的內容,而該指針仍然指向原來指向的地方,此時,指針爲野指針,若是此時操做該指針會致使不可預期的錯誤。安全作法是:在使用free函數釋放指針指向的空間以後,將指針的值置爲NULL。

  3)calloc函數的分配的內存也須要自行釋放
calloc函數的功能與malloc函數的功能類似,都是從堆分配內存,它與malloc函數的一個顯著不一樣時是,calloc函數獲得的內存空間是通過初始化的,其內容全爲0。calloc函數適合爲數組申請空間,能夠將size設置爲數組元素的空間長度,將n設置爲數組的容量。
  4)若是要使用realloc函數分配的內存,必須使用memset函數對其內存初始化
realloc函數的功能比malloc函數和calloc函數的功能更爲豐富,能夠實現內存分配和內存釋放的功能。realloc 能夠對給定的指針所指的空間進行擴大或者縮小,不管是擴張或是縮小,原有內存的中內容將保持不變。固然,對於縮小,則被縮小的那一部分的內容會丟失。realloc 並不保證調整後的內存空間和原來的內存空間保持同一內存地址。相反,realloc 返回的指針極可能指向一個新的地址。
因此,在代碼中,咱們必須將realloc返回的值,從新賦值給 p :
p = (int *) realloc(p, sizeof(int) *15);
甚至,你能夠傳一個空指針(0)給 realloc ,則此時realloc 做用徹底至關於malloc。
int* p = (int *)realloc (0,sizeof(int) * 10); //分配一個全新的內存空間,
這一行,做用徹底等同於:
int* p = (int *)malloc(sizeof(int) * 10);
  5)關於alloca()函數
還有一個函數也值得一提,這就是alloca()。其調用序列與malloc相同,可是它是在當前函數的棧幀上分配存儲空間,而不是在堆中。其優勢是:當 函數返回時,自動釋放它所使用的棧幀,因此沒必要再爲釋放空間而費心。其缺點是:某些系統在函數已被調用後不能增長棧幀長度,因而也就不能支持alloca 函數。儘管如此,不少軟件包仍是使用alloca函數,也有不少系統支持它。

總結:應用時候須要記得,只有calloc能夠指定個數和大小,並且可以對分配內存進行初始化,其他函數均不會對內存進行初始化工做,須要自行調用memset()函數.

參看:1)百度百科(calloc)——https://baike.baidu.com/item/calloc/10931444?fr=aladdin

2)malloc/calloc/realloc/alloca內存分配函數——https://www.cnblogs.com/3me-linux/p/3962152.html

 

(2.3.2)minpq_insert代碼及說明:

答:

//將元素插入到最小化優先級隊列,成功時返回0或失敗返回1

//min_pq最小化優先級隊列,  data要插入的元素,  key與數據關聯的鍵

int minpq_insert( struct min_pq* min_pq, void* data, int key )
{
  int n = min_pq->n;
  //必要時進行雙數組分配

  if( min_pq->nallocd == n ) //越界檢查
    {
      min_pq->nallocd = array_double( (void**)&min_pq->pq_array,
                      min_pq->nallocd,
                      sizeof( struct pq_node ) ); //array_double在utils.c中,函數功能:數組大小加倍,錯誤檢查
      if( ! min_pq->nallocd )
    {
      fprintf( stderr, "Warning: unable to allocate memory, %s, line %d\n",
           __FILE__, __LINE__ );
      return 1; //失敗返回1
    }
    }
  //初始時,把根壓入到優先級隊列,後來把未搜索的節點存入優先級隊列
  min_pq->pq_array[n].data = data;
  min_pq->pq_array[n].key = INT_MAX;
  decrease_pq_node_key( min_pq->pq_array, min_pq->n, key ); //min_pq->pq_array爲初始根節點, min_pq->n初始爲0, key初始爲0
  min_pq->n++;

  return 0; //成功返回0
}

(2.3.2.1)decrease_pq_node_key代碼及說明:

答:

/*減小最小化pq元素的鍵,必要時從新排列pq

pq_array最小化優先級隊列數組

i要減小其鍵的元素的索引

key鍵的新值; 若是大於當前關鍵,不採起任何行動*/
static void decrease_pq_node_key( struct pq_node* pq_array, int i, int key )
{
  struct pq_node tmp;
 //越界檢查
  if( key > pq_array[i].key )
    return;

  pq_array[i].key = key; //初始pq_array[0].key=0
  while( i > 0  &&  pq_array[i].key < pq_array[parent(i)].key )
    {
      tmp = pq_array[parent(i)];
      pq_array[parent(i)] = pq_array[i];
      pq_array[i] = tmp;
      i = parent(i);
    }
}

(2.3.2.1.1)parent代碼及說明:

答:

/*返回元素i的父級的數組索引*/
static inline int parent( int i )
{
  return ( i - 1 ) / 2;
}

(2.3.3)minpq_extract_min代碼及說明:

答:

//刪除並返回具備最小鍵的最小化優先級隊列的元素,返回最小鍵或若是爲空,返回NULL

//min_pq最小化優先級隊列

void* minpq_extract_min( struct min_pq* min_pq )
{
  void* data;

  if( min_pq->n < 1 )
    {
      fprintf( stderr, "Warning: PQ empty, %s line %d\n", __FILE__, __LINE__ );
      return NULL;
    }
  data = min_pq->pq_array[0].data; //數組中包含的優先級級隊列的第一個
  min_pq->n--;
  min_pq->pq_array[0] = min_pq->pq_array[min_pq->n]; //指向下一個
  restore_minpq_order( min_pq->pq_array, 0, min_pq->n );

  return data;
}

(2.3.3.1)restore_minpq_order代碼及說明:

答:

/*遞歸地將正確的優先級隊列順序恢復爲最小化pq數組

pq_array最小化優先級隊列數組
i索引開始從新排序

n pq_array中的元素數量 */
static void restore_minpq_order( struct pq_node* pq_array, int i, int n )
{
  struct pq_node tmp;
  int l, r, min = i;

  l = left( i );
  r = right( i );
  if( l < n )
    if( pq_array[l].key < pq_array[i].key )
      min = l;
  if( r < n )
    if( pq_array[r].key < pq_array[min].key )
      min = r;

  if( min != i )
    {
      tmp = pq_array[min];
      pq_array[min] = pq_array[i];
      pq_array[i] = tmp;
      restore_minpq_order( pq_array, min, n ); //遞歸
    }
}

(2.3.3.1)left和right代碼及說明:

答:

/*返回元素i右子的數組索引*/
static inline int right( int i )
{
  return 2 * i + 2;
}
//返回元素i的左子節點的數組索引
static inline int left( int i )
{
  return 2 * i + 1;
}

(2.3.4)explore_to_leaf代碼及說明:

答:

/*從給定結點搜索k-d樹直到葉節點,搜索過程當中將未搜索的節點根據優先級放入隊列
優先級隊列和搜索路徑是同時生成的,這也是BBF算法的精髓所在:在二叉搜索的時
候將搜索路徑另外一側的分支加入到優先級隊列中,供回溯時查找。而優先級隊列的排
序就是根據目標特徵與分割超平面的距離ABS(kv - feat->descr[ki])
參數:
kd_node:要搜索的子樹的樹根
feat:目標特徵點
min_pq:優先級隊列
返回值:葉子節點的指針
*/
static struct kd_node* explore_to_leaf( struct kd_node* kd_node, struct feature* feat,
                                        struct min_pq* min_pq )
{
    //unexpl中存放着優先級隊列的候選節點(還未搜索的節點),expl爲當前搜索節點
    struct kd_node* unexpl, * expl = kd_node;
    double kv;//分割樞軸的值
    int ki;//分割樞軸的維數索引
 
    //一直搜索到葉子節點,搜索過程當中將未搜索的節點根據優先級放入隊列
    while( expl  &&  ! expl->leaf )
    {
        ki = expl->ki; //ki:方差最大的那個維度
        kv = expl->kv; //kv:各特徵點在那個維度上的中間值
 
        //樞軸的維數索引大於特徵點的維數,出錯
        if( ki >= feat->d )
        {
            fprintf( stderr, "Warning: comparing imcompatible descriptors, %s" " line %d\n", __FILE__, __LINE__ );
            return NULL;
        }
        //目標特徵點ki維的數據小於等於kv,進入左子樹搜索
        if( feat->descr[ki] <= kv )
        {
            unexpl = expl->kd_right;//候選搜索節點是expl的右子樹的根
            expl = expl->kd_left;//當前搜索節點是expl的左子樹的根
        }
        else//目標特徵點ki維的數據大於kv,進入右子樹搜索
        {
            unexpl = expl->kd_left;//候選搜索節點是expl的左子樹
            expl = expl->kd_right;//當前搜索節點是expl的右子樹
        }
 
        //將候選節點unexpl根據目標特徵點ki維與其父節點的距離插入到優先隊列中,距離越小,優先級越大
        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;//返回葉子節點的指針
}

(2.3.5)descr_dist_sq代碼及說明:

答:

//計算兩個特徵描述子間的歐式距離的平方,返回值爲歐式距離的平方

//f1爲第一個特徵點,f2爲第二個特徵點

double descr_dist_sq( struct feature* f1, struct feature* f2 )
{
  double diff, dsq = 0;
  double* descr1, * descr2;
  int i, d;

  d = f1->d; //f1的特徵描述子的長度,128
  if( f2->d != d ) //若f1和f2的特徵描述子長度不一樣,返回
    return DBL_MAX; //DBL_MAX其餘文檔未找到定義
  descr1 = f1->descr; //f1的特徵描述子,一個double數組
  descr2 = f2->descr; //f2的特徵描述子,一個double數組
 //計算歐式距離的平方,即對應元素的差的平方和
  for( i = 0; i < d; i++ )
    {
      diff = descr1[i] - descr2[i];
      dsq += diff*diff;
    }
  return dsq; //返回歐式距離的平方
}

 

(2.3.6)insert_into_nbr_array代碼及說明:

答:

/*插入一個特徵點到最近鄰數組,使數組中的點按到目標點的距離升序排列
參數:
feat:要插入的特徵點,其feature_data域應是指向bbf_data結構的指針,其中的d值時feat和目標點的距離的平方
nbrs:最近鄰數組
n:已在最近鄰數組中的元素個數
k:最近鄰數組元素個數的最大值
返回值:若feat成功插入,返回1,不然返回0
*/
static int insert_into_nbr_array( struct feature* feat, struct feature** nbrs, int n, int k )
{
    struct bbf_data* fdata, * ndata;//fdata是要插入的點的bbf結構,ndata是最近鄰數組中的點的bbf結構
    double dn, df; //dn是最近鄰數組中特徵點的bbf結構中的距離值,df是要插入的特徵點的bbf結構中的距離值
    int i, ret = 0;
 
    //原最近鄰數組爲空
    if( n == 0 )
    {
        nbrs[0] = feat;
        return 1;//插入成功,返回1
    }
 
    /* check at end of array */
    fdata = (struct bbf_data*)feat->feature_data;//要插入點的bbf結構
    df = fdata->d;//要插入的特徵點的bbf結構中的距離值
    ndata = (struct bbf_data*)nbrs[n-1]->feature_data;//最近鄰數組中的點的bbf結構
    dn = ndata->d;//最近鄰數組中最後一個特徵點的bbf結構中的距離值
 
    //df>=dn,說明要插入在最近鄰數組的末尾
    if( df >= dn )
    {
        //最近鄰數組中元素個數已達到最大值,沒法插入
        if( n == k )
        {
            feat->feature_data = fdata->old_data; //原來的值還原回去

     //在SIFT極值點檢測中,是detection_data結構的指針
                 //在k-d樹搜索中,是bbf_data結構的指針,搜索結束,還原回detection_data
                 //在RANSAC算法中,是ransac_data結構的指針*/
            free( fdata );
            return 0;//插入失敗,返回0
        }
        nbrs[n] = feat;//插入到末尾
        return 1;//插入成功,返回1
    }
 
    //運行到此處說明插入位置不在數組末尾
    /* find the right place in the array */
    if( n < k )//最近鄰數組中元素個數小於最大值,可插入
    {
        nbrs[n] = nbrs[n-1];//原數組最後一個點後移
        ret = 1;//插入結果設爲1
    }
    else//最近鄰數組中元素個數大於或等於最大值,沒法插入,插入結果ret仍是0
    {//其實也不是沒法插入,而是最近鄰數組中元素個數不變,但值會更新
        nbrs[n-1]->feature_data = ndata->old_data; //原來的值還原回去
        free( ndata );
    }
    i = n-2;
    //在最近鄰數組中查找要插入的位置
    while( i >= 0 )
    {
        ndata = (struct bbf_data*)nbrs[i]->feature_data;
        dn = ndata->d;
        if( dn <= df )//找到插入點
            break;
        nbrs[i+1] = nbrs[i];//一次後移
        i--;
    }
    i++;
    nbrs[i] = feat;//插入
 
    return ret;//返回結果
}

(2.3.7)minpq_release代碼及說明:

答:

 /* 取消分配由最小化的先前隊列保持的內存  

min_pq指向最小化優先級隊列的指針*/
void minpq_release( struct min_pq** min_pq )
{
  if( ! min_pq )
    {
      fprintf( stderr, "Warning: NULL pointer error, %s line %d\n", __FILE__,
           __LINE__ );
      return;
    }
  if( *min_pq  &&  (*min_pq)->pq_array )
    {
      free( (*min_pq)->pq_array );
      free( *min_pq );
      *min_pq = NULL;
    }
}

相關文章
相關標籤/搜索