RobHess的SIFT代碼解析步驟二

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

主要參考:1.代碼:RobHess的SIFT源碼c++

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

 

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

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

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

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

步驟四:計算特徵描述子學習

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

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

 

 

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

問題及解答:

 

(1)問題描述:如何找尋關鍵點?在幾層之間對比?思路是什麼?

答:極值的檢測是在DoG空間進行的,檢測是之前點爲中心,3pixel*3pixel*3pixel的立方體爲鄰域,判斷當前點是否爲局部最大或最小。以下圖所示,橘黃色爲當前檢測點,綠色點爲其鄰域。由於要比較當前點的上下層圖像,因此極值檢測從DoG每層的第2幅圖像開始,終止於每層的倒數第2幅圖像(第1幅沒有下層,最後1幅沒有上層,沒法比較)。

 

參考書P84-P85及圖4-6

 

(2)問題描述:爲何還要對找尋的關鍵點進行插值?爲何要精肯定位?

答:以上極值點的搜索時在離散空間中進行的,檢測到的極值點並非真正意義上的極值點。以下圖所示,連續空間中極值與離散空間的區別。一般經過插值的方式,利用離散的值來插值,求取接近真正的極值的點。

對於一維函數,利用泰勒級數,將其展開爲二次函數:
f(x) ≈ f(0) + f'(0)x + f''(0)x2
對於二維函數,泰勒展開爲:

 

矩陣表示爲:

矩陣表示爲:

矢量表示爲:

當矢量爲n維時,有:

 

書中P86頁有錯誤,我已經更正,並附在:https://www.cnblogs.com/Alliswell-WP/p/SIFT.html

 

求取f(x)的極值,只需求取∂f/∂x = 0。對於極值,x,y,σ三個變量,即爲三維空間。利用三維子像元插值,設其函數爲D(x, y, σ),令x = (x, y, σ)T,那麼在第一節中找到的極值點進行泰勒展開爲(式1)以下:

(式1)

其中D爲極值點的值,∂DT/∂x爲在極值點各自變量的倒數,∂2D/∂x2爲其在展開點相應的矩陣。對上式求導,另∂D(x)/∂x = 0,結果以下式,對應的(爲了表示方便,ˆx代替)向量即爲真正極值點偏離插值點的量。求解得(式2)以下:

(式2)

最終極值點的位置即爲插值點xx,且屢次迭代能夠提升精度(通常爲5次迭代)。

參考書P85-P87及圖4-7和圖4-8

 

(3)問題描述:既然知道了檢測到的極值點並非真正意義上的極值點,那怎麼怎麼插值呢?

答:圖像離散的像素點組成的,因此要差分代替偏導,計算一階偏導,二階偏導構建Hessian矩陣。

 

(4)問題描述:對關鍵點定位後,須要剔除一些很差的KeyPoint,那什麼是很差的KeyPoint的呢?如何剔除?

答:

1.DoG響應較低的點,即極值較小的點。
2.響應較強的點也不是穩定的特徵點。DoG對圖像中的邊緣有較強的響應值,因此落在圖像邊緣的點也不是穩定的特徵點。一方面圖像邊緣上的點是很難定位的,具備定位的歧義性;另外一方面這樣的點很容易受到噪聲的干擾變得不穩定。
對於第一種,只需計算矯正後的點的響應值D(ˆx),響應值小於必定閾值,即認爲該點效應較小,將其剔除。將(式2)帶入(式1),求解得:

在Lowe文章中,將|D(ˆx)|<0.03(圖像灰度歸一化爲[0,1])的特徵點剔除。

對於第二種,利用Hessian矩陣來剔除。一個平坦的DoG響應峯值在橫跨邊緣的地方有較大的主曲率,而在垂直邊緣的地方有較小的主曲率。主曲率能夠經過2×2的Hessian矩陣H求出:


D值能夠經過求臨近點差分獲得。H的特徵值與D的主曲率成正比,具體可參見Harris角點檢測算法。爲了不求具體的值,咱們能夠經過H將特徵值的比例表示出來。令爲最大特徵值,爲最小特徵值,那麼:
 
Tr(H)表示矩陣H的跡,Det(H)表示H的行列式。令表示最大特徵值與最小特徵值的比值,則有:
  
上式與兩個特徵值的比例有關。隨着主曲率比值的增長,也會增長。咱們只須要去掉比率大於必定值的特徵點。Lowe論文中去掉r=10的點。

參考書P87-P88

 

(5)問題描述:RobHess的SIFT源碼如何實現步驟二,大概思路是這樣的?

答:5.1)代碼及說明:

/*步驟二:在尺度空間中檢測極值點,並進行精肯定位和篩選建立默認大小的內存存儲器*/
storage = cvCreateMemStorage( 0 ); //調用opencv的cvCreateMemStorage函數,函數功能:用來建立一個內存存儲器,來統一管理各類動態對象的內存。函數返回一個新建立的內存存儲器指針。
//在尺度空間中檢測極值點,經過插值精肯定位,去除低對比度的點,去除邊緣點,
//返回檢測到的特徵點序列
features = scale_space_extrema( dog_pyr, octvs, intvls, contr_thr, curv_thr, storage );
 //計算特徵點序列features中每一個特徵點的尺度
calc_feature_scales( features, sigma, intvls );
if( img_dbl )  //若設置了將圖像放大爲原圖的2倍
  adjust_for_img_dbl( features );//將特徵點序列中每一個特徵點的座標減半
//(當設置了將圖像放大爲原圖的2倍時,特徵點檢測完以後調用)

 

(5.2)scale_space_extrema代碼及說明:

/*在尺度空間中檢測極值點,經過插值(三維二次函數)精肯定位,去除低對比度的點,去除邊緣點,返回檢測到的特徵點序列

參數:

dog_pyr:高斯差分金字塔

octvs:高斯差分金字塔的組數

intvls:每組的層數

contr_thr:對比度閾值,針對歸一化後的圖像,用來去除不穩定特徵

cur_thr:主曲率比值的閾值,用來去除邊緣特徵

storage:存儲器

返回值:返回檢測到的特徵點的序列*/

static CvSeq* scale_space_extrema( IplImage*** dog_pyr, int octvs, int intvls,

                                  double contr_thr, int curv_thr, CvMemStorage* storage ) //octvs=O(log(min(length,width))/log(2)-2);intvls=3;curv_thr=0.04;curv_thr=10

{

    CvSeq* features;//特徵點序列

    double prelim_contr_thr = 0.5 * contr_thr / intvls;//像素的對比度閾值,此處與LOWE論文不一樣,,|D(x)|<0.03,書P87

    struct feature* feat;

    struct detection_data* ddata;

    int o, i, r, c;

 

    //在存儲器storage上建立存儲極值點的序列,其中存儲feature結構類型的數據

    features = cvCreateSeq( 0, sizeof(CvSeq), sizeof(struct feature), storage );

    /*遍歷高斯差分金字塔,檢測極值點*/

    //SIFT_IMG_BORDER指明邊界寬度,只檢測邊界線之內的極值點

    for( o = 0; o < octvs; o++ )//第o組

        for( i = 1; i <= intvls; i++ )//遍i層

            for(r = SIFT_IMG_BORDER; r < dog_pyr[o][0]->height-SIFT_IMG_BORDER; r++)//第r行

                for(c = SIFT_IMG_BORDER; c < dog_pyr[o][0]->width-SIFT_IMG_BORDER; c++)//第c列

                    //進行初步的對比度檢查,只有當歸一化後的像素值大於對比度

                    //閾值prelim_contr_thr時才繼續檢測此像素點是否多是極值

                    //調用函數pixval32f獲取圖像dog_pyr[o][i]的第r行第c列的點的座標值,

                    //而後調用ABS宏求其絕對值

                    if( ABS( pixval32f( dog_pyr[o][i], r, c ) ) > prelim_contr_thr )

                        //經過在尺度空間中將一個像素點的值與其周圍3*3*3鄰域內的點比較來

                        //決定此點是否極值點(極大值或極小都行)

                        if( is_extremum( dog_pyr, o, i, r, c ) )//如果極值點

                        {

                            //因爲極值點的檢測是在離散空間中進行的,因此檢測到的極值點並

                            //不必定是真正意義上的極值點

                            //由於真正的極值點可能位於兩個像素之間,而在離散空間中只能精

                            //確到座標點精度上

                            //經過亞像素級插值進行極值點精肯定位(修正極值點座標),並去除

                            //低對比度的極值點,將修正後的特徵點組成feature結構返回

                            feat = interp_extremum(dog_pyr, o, i, r, c, intvls, contr_thr);

                            //返回值非空,代表此點已被成功修正

                            if( feat )

                            {

                                //調用宏feat_detection_data來提取參數feat中的feature_data成員

                                //並轉換爲detection_data類型的指針

                                ddata = feat_detection_data( feat );

                                //去除邊緣響應,即經過計算主曲率比值判斷某點是否邊緣點,

                                //返回值爲0表示不是邊緣點,可作特徵點

                                if( ! is_too_edge_like( dog_pyr[ddata->octv][ddata->intvl],

                                    ddata->r, ddata->c, curv_thr ) )

                                {

                                    cvSeqPush( features, feat );//向特徵點序列features末尾插

                                    //入新檢測到的特徵點feat

                                }

                                else

                                    free( ddata );

                                free( feat );

                            }

                        }

                        return features;//返回特徵點序列

}

 

5.2.1)CvSeq的理解:

說明:動態結構序列CvSeq是全部OpenCv動態數據結構的基礎。有兩種類型的序列:稠密序列,稀疏序列:
其中一種是——稠密序列都派生自CvSeq,他們用來表明可擴展的一維數組 — 向量、棧、隊列和雙端隊列。數據間不存在空隙(連續存儲)。若是元素元素從序列中間被刪除或插入新的元素到序列,那麼此元素後邊的相關元素所有被移動。

total表示稠密序列的元素個數,或者稀疏序列被分配的節點數。elem_size表示序列中每一個元素佔用的字節數。block_max是最近一個內存的最大邊界指針。ptr表示當寫指針。delta_elems表示序列間隔尺寸。storage指向序列存儲的內存塊的指針。free_blocks表示空的塊列表。first指向第一個序列塊。

參看博客:1)OpenCV——CvSeq動態結構序列:https://www.cnblogs.com/farewell-farewell/p/5999908.html

2)CvSeq的理解:https://blog.csdn.net/weijianmeng/article/details/7173560

 

5.2.2)cvCreateSeq的理解:

說明:CvSeq *cvCreateSeq(int seq_flags, int header_size, int elem_size, CvMemStorage *storage);
header_size:序列頭的大小,一般爲sizeof(CvSeq)。
elem_size:存儲元素的大小。
storage:內存存儲器,添加元素的時候,就會從內存存儲器申請空間。

參看博客:opencv建立序列cvCreateSeq與插入元素cvSeqPush的運用:https://www.cnblogs.com/farewell-farewell/p/5999908.html

 

5.2.3)for(r = SIFT_IMG_BORDER; r < dog_pyr[o][0]->height-SIFT_IMG_BORDER; r++)//第r行
for(c = SIFT_IMG_BORDER; c < dog_pyr[o][0]->width-SIFT_IMG_BORDER; c++)//第c列

中爲何須要設置SIFT_IMG_BORDER指明邊界寬度:

答:只檢測邊界線之內的極值點,由於邊界容易受到噪聲的干擾而不穩定。

 

5.2.4)pixval32f(dog_pyr[o][i])函數功能?ABS(pixval32f(dog_pyr[o][i]))爲何加絕對值?爲何須要判斷高斯差分金字塔(DOG)中某個像素小於0的狀況?

答:utils.c中定義了這個內部函數:

static inline float pixval32f( IplImage* img, int r, int c )

{

  return ( (float*)(img->imageData + img->widthStep*r) )[c];

}

具體怎麼訪問圖像元素?

參看博客:1) opencv——訪問圖像元素(imagedata widthstep):https://blog.csdn.net/xiaofeilong321/article/details/13287697

2)opencv學習筆記(八)IplImage* 訪問圖像像素的值:https://www.cnblogs.com/codingmengmeng/p/6559724.html

 

5.2.5)is_extremum代碼及說明:

/*經過在尺度空間中將一個像素點的值與其周圍3*3*3鄰域內的點比較來決定此點是否極值點

(極大值或極小都行)

參數:

dog_pyr:高斯差分金字塔

octv:像素點所在的組

intvl:像素點所在的層

r:像素點所在的行

c:像素點所在的列

返回值:若指定的像素點是極值點(極大值或極小值),返回1;不然返回0*/

static int is_extremum( IplImage*** dog_pyr, int octv, int intvl, int r, int c )

{

    //調用函數pixval32f獲取圖像dog_pyr[octv][intvl]的第r行第c列的點的座標值

    float val = pixval32f( dog_pyr[octv][intvl], r, c );

    int i, j, k;

    //檢查是否最大值

    if( val > 0 )

    {

        for( i = -1; i <= 1; i++ )//層

            for( j = -1; j <= 1; j++ )//行

                for( k = -1; k <= 1; k++ )//列

                    if( val < pixval32f( dog_pyr[octv][intvl+i], r + j, c + k ) )

                        return 0; //不是極值點,退出此函數

    }

    //檢查是否最小值

    else

    {

        for( i = -1; i <= 1; i++ )//層

            for( j = -1; j <= 1; j++ )//行

                for( k = -1; k <= 1; k++ )//列

                    if( val > pixval32f( dog_pyr[octv][intvl+i], r + j, c + k ) )

                        return 0;

    }

    return 1; //是極值點,返回1

}

 

5.2.6)interp_extremum代碼及說明:

剔除不穩定點,精肯定位關鍵點位置

 

/*經過亞像素級插值進行極值點精肯定位(修正極值點座標),並去除低對比度的極值點,

將修正後的特徵點組成feature結構返回

參數:

dog_pyr:高斯差分金字塔

octv:像素點所在的組

intvl:像素點所在的層

r:像素點所在的行

c:像素點所在的列

intvls:每組的層數

contr_thr:對比度閾值,針對歸一化後的圖像,用來去除不穩定特徵

返回值:返回經插值修正後的特徵點(feature類型);若經有限次插值依然沒法精確到理想狀況

或者該點對比度太低,返回NULL*/

static struct feature* interp_extremum( IplImage*** dog_pyr, int octv, int intvl,

       int r, int c, int intvls, double contr_thr ) //intvls=3; contr_thr=0.04

{

    struct feature* feat;//修正後的特徵點

    struct detection_data* ddata;//與特徵檢測有關的結構,存在feature結構的feature_data成員中

    double xi, xr, xc, contr;//xi,xr,xc分別爲亞像素的intvl(層),row(y),col(x)方向上的

    //增量(偏移量)

    int i = 0;//插值次數

 

    //SIFT_MAX_INTERP_STEPS指定了關鍵點的最大插值次數,即最多修正多少次,默認是5

    while( i < SIFT_MAX_INTERP_STEPS )

    {

        //進行一次極值點差值,計算σ(層方向,intvl方向),y,x方向上的子像素偏移量(增量)

        interp_step( dog_pyr, octv, intvl, r, c, &xi, &xr, &xc );

        //若在任意方向上的偏移量大於0.5時,意味着差值中心已經偏移到它的臨近點上,

        //因此必須改變當前關鍵點的位置座標

        if( ABS( xi ) < 0.5  &&  ABS( xr ) < 0.5  &&  ABS( xc ) < 0.5 )//若三方向上偏移量

            //都小於0.5,表示已經夠精確,則不用繼續插值

            break;

 

        //修正關鍵點的座標,x,y,σ三方向上的原座標加上偏移量取整(四捨五入)

        c += cvRound( xc );//x座標修正

        r += cvRound( xr );//y座標修正

        intvl += cvRound( xi );//σ方向,即層方向

 

        //若座標修正後超出範圍,則結束插值,返回NULL

        if( intvl < 1  ||           //層座標插以後越界

            intvl > intvls  ||

            c < SIFT_IMG_BORDER  ||   //行列座標插以後到邊界線內

            r < SIFT_IMG_BORDER  ||

            c >= dog_pyr[octv][0]->width - SIFT_IMG_BORDER  ||

            r >= dog_pyr[octv][0]->height - SIFT_IMG_BORDER )

        {

            return NULL;

        }

        i++;

    }

    //若通過SIFT_MAX_INTERP_STEPS次插值後尚未修正到理想的精確位置,則返回NULL,

    //即捨棄此極值點

    if( i >= SIFT_MAX_INTERP_STEPS )

        return NULL;

    //計算被插值點的對比度:D + 0.5 * dD^T * X

    contr = interp_contr( dog_pyr, octv, intvl, r, c, xi, xr, xc );

    //此處與書上的不太同樣,RobHess此處的閾值使用的是0.04/S

    if( ABS( contr ) < contr_thr / intvls )//若該點對比度太小,捨棄,返回NULL

        return NULL;

    //爲一個特徵點feature結構分配空間並初始化,返回特徵點指針

    feat = new_feature();

    //調用宏feat_detection_data來提取參數feat中的feature_data成員並轉換爲

    //detection_data類型的指針

    ddata = feat_detection_data( feat );

    //將修正後的座標賦值給特徵點feat

    //原圖中特徵點的x座標,由於第octv組中的圖的尺寸比原圖小2^octv倍,

    //因此座標值要乘以2^octv,最後針對的是原圖的匹配

    feat->img_pt.x = feat->x = ( c + xc ) * pow( 2.0, octv );

    //原圖中特徵點的y座標,由於第octv組中的圖的尺寸比原圖小2^octv倍,

    //因此座標值要乘以2^octv

    feat->img_pt.y = feat->y = ( r + xr ) * pow( 2.0, octv );

    ddata->r = r;//特徵點所在的行

    ddata->c = c;//特徵點所在的列

    ddata->octv = octv;//高斯差分金字塔中,特徵點所在的組

    ddata->intvl = intvl;//高斯差分金字塔中,特徵點所在的組中的層

    ddata->subintvl = xi;//特徵點在層方向(σ方向,intvl方向)上的亞像素偏移量

 

    return feat;//返回特徵點指針

}

 

(5.2.6.1)cvRound()說明:

答:函數cvRound,cvFloor,cvCeil 都是用一種舍入的方法將輸入浮點數轉換成整數:
cvRound():返回跟參數最接近的整數值,即四捨五入;
cvFloor():返回不大於參數的最大整數值,即向下取整;
cvCeil():返回不小於參數的最小整數值,即向上取整;

參看:【雜談opencv】OpenCV中的cvRound()、cvFloor()、 cvCeil()函數講解——https://blog.csdn.net/sinat_36264666/article/details/78849125

 

(5.2.6.2)interp_step代碼及說明

答:

/*進行一次極值點差值,計算x,y,σ方向(層方向)上的子像素偏移量(增量)

參數:

dog_pyr:高斯差分金字塔

octv:像素點所在的組

intvl:像素點所在的層

r:像素點所在的行

c:像素點所在的列

xi:輸出參數,層方向上的子像素增量(偏移)

xr:輸出參數,y方向上的子像素增量(偏移)

xc:輸出參數,x方向上的子像素增量(偏移)*/

static void interp_step( IplImage*** dog_pyr, int octv, int intvl, int r, int c,

                        double* xi, double* xr, double* xc )

{

    CvMat* dD, * H, * H_inv, X;

    double x[3] = { 0 };

    //在DoG金字塔中計算某點的x方向、y方向以及尺度方向上的偏導數,結果存放在列向量dD中

    dD = deriv_3D( dog_pyr, octv, intvl, r, c );

    //在DoG金字塔中計算某點的3*3海森矩陣

    H = hessian_3D( dog_pyr, octv, intvl, r, c );

    H_inv = cvCreateMat( 3, 3, CV_64FC1 );//海森矩陣的逆陣

    cvInvert( H, H_inv, CV_SVD );

    cvInitMatHeader( &X, 3, 1, CV_64FC1, x, CV_AUTOSTEP );

    //X = - H^(-1) * dD,H的三個元素分別是x,y,σ方向上的偏移量(具體見SIFT算法說明)

    cvGEMM( H_inv, dD, -1, NULL, 0, &X, 0 );

    cvReleaseMat( &dD );

    cvReleaseMat( &H );

    cvReleaseMat( &H_inv );

    *xi = x[2];//σ方向(層方向)偏移量

    *xr = x[1];//y方向上偏移量

    *xc = x[0];//x方向上偏移量

}

 

(5.2.6.2.1)deriv_3D代碼及說明:

答:

/*在DoG金字塔中計算某點的x方向、y方向以及尺度方向上的偏導數

參數:

dog_pyr:高斯差分金字塔

octv:像素點所在的組

intvl:像素點所在的層

r:像素點所在的行

c:像素點所在的列

返回值:返回3個偏導數組成的列向量{ dI/dx, dI/dy, dI/ds }^T*/

static CvMat* deriv_3D( IplImage*** dog_pyr, int octv, int intvl, int r, int c )

{

    CvMat* dI;

    double dx, dy, ds;

    //求差分來代替偏導,這裏是用的隔行求差取中值的梯度計算方法

    //求x方向上的差分來近似代替偏導數

    dx = ( pixval32f( dog_pyr[octv][intvl], r, c+1 ) -

        pixval32f( dog_pyr[octv][intvl], r, c-1 ) ) / 2.0;

    //求y方向上的差分來近似代替偏導數

    dy = ( pixval32f( dog_pyr[octv][intvl], r+1, c ) -

        pixval32f( dog_pyr[octv][intvl], r-1, c ) ) / 2.0;

    //求層間的差分來近似代替尺度方向上的偏導數

    ds = ( pixval32f( dog_pyr[octv][intvl+1], r, c ) 

        pixval32f( dog_pyr[octv][intvl-1], r, c ) ) / 2.0;

    //組成列向量

    dI = cvCreateMat( 3, 1, CV_64FC1 );

    cvmSet( dI, 0, 0, dx );

    cvmSet( dI, 1, 0, dy );

    cvmSet( dI, 2, 0, ds );

    return dI;

}

(5.2.6.2.1.1)問題issue:差分代替偏導?一階二階都使用嗎?怎麼操做?

差分代替偏導!一階狀況:

一階差分代替一階偏導,即梯度,[-1,0,1]

行方向[-1,0,1]

      -1

列方向[  0  ]

       1

二階狀況:

參看:圖像處理中的一階偏導數和二階偏導數——https://www.docin.com/p-749102193.html

 

(5.2.6.2.1.2)cvmSet說明

答:opencv中cvmSet爲逐點賦值

    cvmSet( dI, 0, 0, dx );       dx

    cvmSet( dI, 1, 0, dy );     dI = [   dy     ]

    cvmSet( dI, 2, 0, ds );     ds

 

(5.2.6.2.2)hessian_3D代碼及說明:

答://差分代替偏導,二階狀況

/*在DoG金字塔中計算某點的3*3海森矩陣

/ Ixx  Ixy  Ixs \

| Ixy  Iyy  Iys |

\ Ixs  Iys  Iss /

參數:

dog_pyr:高斯差分金字塔

octv:像素點所在的組

intvl:像素點所在的層

r:像素點所在的行

c:像素點所在的列

返回值:返回3*3的海森矩陣

*/

static CvMat* hessian_3D( IplImage*** dog_pyr, int octv, int intvl, int r, int c )

{

    CvMat* H;

    double v, dxx, dyy, dss, dxy, dxs, dys;

 

    v = pixval32f( dog_pyr[octv][intvl], r, c );//該點的像素值

 

    //用差分近似代替倒數(具體公式見各類梯度的求法)

    //dxx = f(i+1,j) - 2f(i,j) + f(i-1,j)

    //dyy = f(i,j+1) - 2f(i,j) + f(i,j-1)

    dxx = ( pixval32f( dog_pyr[octv][intvl], r, c+1 ) +

        pixval32f( dog_pyr[octv][intvl], r, c-1 ) - 2 * v );

    dyy = ( pixval32f( dog_pyr[octv][intvl], r+1, c ) +

        pixval32f( dog_pyr[octv][intvl], r-1, c ) - 2 * v );

    dss = ( pixval32f( dog_pyr[octv][intvl+1], r, c ) +

        pixval32f( dog_pyr[octv][intvl-1], r, c ) - 2 * v );

    dxy = ( pixval32f( dog_pyr[octv][intvl], r+1, c+1 ) -

        pixval32f( dog_pyr[octv][intvl], r+1, c-1 ) -

        pixval32f( dog_pyr[octv][intvl], r-1, c+1 ) +

        pixval32f( dog_pyr[octv][intvl], r-1, c-1 ) ) / 4.0;

    dxs = ( pixval32f( dog_pyr[octv][intvl+1], r, c+1 ) -

        pixval32f( dog_pyr[octv][intvl+1], r, c-1 ) -

        pixval32f( dog_pyr[octv][intvl-1], r, c+1 ) +

        pixval32f( dog_pyr[octv][intvl-1], r, c-1 ) ) / 4.0;

    dys = ( pixval32f( dog_pyr[octv][intvl+1], r+1, c ) -

        pixval32f( dog_pyr[octv][intvl+1], r-1, c ) -

        pixval32f( dog_pyr[octv][intvl-1], r+1, c ) +

        pixval32f( dog_pyr[octv][intvl-1], r-1, c ) ) / 4.0;

    //組成海森矩陣

    H = cvCreateMat( 3, 3, CV_64FC1 );

    cvmSet( H, 0, 0, dxx );

    cvmSet( H, 0, 1, dxy );

    cvmSet( H, 0, 2, dxs );

    cvmSet( H, 1, 0, dxy );

    cvmSet( H, 1, 1, dyy );

    cvmSet( H, 1, 2, dys );

    cvmSet( H, 2, 0, dxs );

    cvmSet( H, 2, 1, dys );

    cvmSet( H, 2, 2, dss );

 

    return H;

}

 

(5.2.6.2.3)cvInvert函數說明:

答:

double cvInvert(//矩陣取逆
    const CvArr* src,//目標矩陣
    CvArr* dst,//結果矩陣
    int method = CV_LU//逆運算方法
);

其中method有
方法的參數值     含義
CV_LU        高斯消去法
CV_SVD       奇異值分解
CV_SVD_SYM     對稱矩陣的SVD

參看:《學習opencv》筆記——矩陣和圖像操做——cvInRange,cvInRangeS,cvInvert and cvMahalonobis——https://blog.csdn.net/zhurui_idea/article/details/28630589

 

 

(5.2.6.2.4)問題issue:cvInitMatHeader的一些問題:

答:

1.   cvInitMatHeader的用法很古怪。第一個參數必須是CvMat格式的,官方的文檔是CvMat *mat,可是這裏要注意,mat必須是初始化過的,單獨定義一個指針如CvMat *data1;把data1帶入cvInitMatHeader函數後,編譯器會報錯,顯示沒有初始化!下面的代碼纔是正確的

2.下面的代碼我在使用的時候想生成兩個mat,而後被另外一個函數f調用,發現f調用時失敗的,用下面代碼生成的數據被釋放掉了。因此這種用法只能在一個函數體內使用,跨函數是不行的。

 CvMat data1; CvMat responses1; //將數組中數據轉化成opencv支持的mat格式 cvInitMatHeader(&data1, total, var_count, CV_32FC1, pData); cvInitMatHeader(&responses1, total, 1, CV_32SC1, pRespon); 

參看:1)【OpenCV學習】矩陣cvInitMatHeader和cvCreateMat:https://blog.csdn.net/cc1949/article/details/22476251

2)cvInitMatHeader的一些問題:https://www.xuebuyuan.com/1050480.html

3)【OpenCV學習】矩陣cvInitMatHeader和cvCreateMat:舉例說明看——https://blog.csdn.net/cc1949/article/details/22476251

 

(5.2.6.2.5)cvGEMM函數說明:

答:void cvGEMM( const CvArr* src1, const CvArr* src2, double alpha, const CvArr* src3, double beta, CvArr* dst, int tABC=0 );
這是通用矩陣乘法,其中各個參數表示:
src1:第一輸入數組
src2:第二輸入數組
alpha:係數
src3「第三輸入數組(偏移量),若是沒有偏移量,能夠爲空(NULL)
beta:表示偏移量的係數
dst:輸出數組
tABC:轉置操做標誌,能夠是0。當爲0時,沒有轉置。

參看博客:cvGEMM、cvMatMul和cvMatMulAdd的定義——https://blog.csdn.net/liulianfanjianshi/article/details/11737921

 

(5.2.6.3)interp_contr代碼及說明

答:

/*計算被插值點的對比度:D + 0.5 * dD^T * X

參數:

dog_pyr:高斯差分金字塔

octv:像素點所在的組

intvl:像素點所在的層

r:像素點所在的行

c:像素點所在的列

xi:層方向上的子像素增量

xr:y方向上的子像素增量

xc:x方向上的子像素增量

返回值:插值點的對比度*/

static double interp_contr( IplImage*** dog_pyr, int octv, int intvl, int r,

                           int c, double xi, double xr, double xc )

{

    CvMat* dD, X, T;

    double t[1], x[3] = { xc, xr, xi };

 

    //偏移量組成的列向量X,其中是x,y,σ三方向上的偏移量

    cvInitMatHeader( &X, 3, 1, CV_64FC1, x, CV_AUTOSTEP );

    //矩陣乘法的結果T,是一個數值

    cvInitMatHeader( &T, 1, 1, CV_64FC1, t, CV_AUTOSTEP );

    //在DoG金字塔中計算某點的x方向、y方向以及尺度方向上的偏導數,結果存放在列向量dD中

    dD = deriv_3D( dog_pyr, octv, intvl, r, c );

    //矩陣乘法:T = dD^T * X

    cvGEMM( dD, &X, 1, NULL, 0, &T,  CV_GEMM_A_T );

    cvReleaseMat( &dD );

 

    //返回計算出的對比度值:D + 0.5 * dD^T * X (具體公式推導見SIFT算法說明)

    return pixval32f( dog_pyr[octv][intvl], r, c ) + t[0] * 0.5;

}

 

(5.2.6.4)new_feature()代碼及說明:

答:

/*爲一個feature結構分配空間並初始化

返回值:初始化完成的feature結構的指針*/

static struct feature* new_feature( void )

{

    struct feature* feat;//特徵點指針

    struct detection_data* ddata;//與特徵檢測相關的結構

    feat = malloc( sizeof( struct feature ) );//分配空間

    memset( feat, 0, sizeof( struct feature ) );//清零

    ddata = malloc( sizeof( struct detection_data ) );

    memset( ddata, 0, sizeof( struct detection_data ) );

    feat->feature_data = ddata;//將特徵檢測相關的結構指針賦值給特徵點的feature_data成員

    feat->type = FEATURE_LOWE;//默認是LOWE類型的特徵點

 

    return feat;

}

 

(5.2.6.5)pow()說明:

答:C 庫函數 double pow(double x, double y) 返回 x 的 y 次冪,即 x^y。

參看:C 庫函數 - pow()——https://www.runoob.com/cprogramming/c-function-pow.html

 

5.2.7)is_too_edge_like代碼及說明:

/*去除邊緣響應,即經過計算主曲率比值判斷某點是否邊緣點

參數:

dog_img:此特徵點所在的DoG圖像

r:特徵點所在的行

c:特徵點所在的列

cur_thr:主曲率比值的閾值,用來去除邊緣特徵

返回值:0:此點是非邊緣點;1:此點是邊緣點*/

static int is_too_edge_like( IplImage* dog_img, int r, int c, int curv_thr )

{

    double d, dxx, dyy, dxy, tr, det;

 

    /*某點的主曲率與其海森矩陣的特徵值成正比,爲了不直接計算特徵值,這裏只考慮

    特徵值的比值可經過計算海森矩陣的跡tr(H)和行列式det(H)來計算特徵值的比值

    設a是海森矩陣的較大特徵值,b是較小的特徵值,有a = r*b,r是大小特徵值的比值

    tr(H) = a + b; det(H) = a*b;

    tr(H)^2 / det(H) = (a+b)^2 / ab = (r+1)^2/r

    r越大,越多是邊緣點;伴隨r的增大,(r+1)^2/r 的值也增大,因此可經過(r+1)^2/r 判斷

    主曲率比值是否知足條件*/

    /* principal curvatures are computed using the trace and det of Hessian */

    d = pixval32f(dog_img, r, c);//調用函數pixval32f獲取圖像dog_img的第r行第c列的點的座標值

 

    //用差分近似代替偏導,求出海森矩陣的幾個元素值

    /*  / dxx  dxy \

    \ dxy  dyy /   */

    dxx = pixval32f( dog_img, r, c+1 ) + pixval32f( dog_img, r, c-1 ) - 2 * d;

    dyy = pixval32f( dog_img, r+1, c ) + pixval32f( dog_img, r-1, c ) - 2 * d;

    dxy = ( pixval32f(dog_img, r+1, c+1) - pixval32f(dog_img, r+1, c-1) -

        pixval32f(dog_img, r-1, c+1) + pixval32f(dog_img, r-1, c-1) ) / 4.0;

    tr = dxx + dyy;//海森矩陣的跡

    det = dxx * dyy - dxy * dxy;//海森矩陣的行列式

    //若行列式爲負,代表曲率有不一樣的符號,去除此點

    /* negative determinant -> curvatures have different signs; reject feature */

    if( det <= 0 )

        return 1;//返回1代表此點是邊緣點

    //經過式子:(r+1)^2/r 判斷主曲率的比值是否知足條件,若小於閾值,代表不是邊緣點

    if( tr * tr / det < ( curv_thr + 1.0 )*( curv_thr + 1.0 ) / curv_thr )

        return 0;//不是邊緣點

    return 1;//是邊緣點

}

 

5.2.8)cvSeqPush代碼及說明:

函數原型: char *  cvSeqPush(CvSeq *seq, void *element = NULL);//添加元素到序列的尾部

參看:opencv建立序列cvCreateSeq與插入元素cvSeqPush的運用:https://blog.csdn.net/gdut2015go/article/details/46494677

 

5.3)calc_feature_scales代碼及說明:

/*計算特徵點序列中每一個特徵點的尺度

參數:

features:特徵點序列

sigma:初始高斯平滑參數,即初始尺度

intvls:尺度空間中每組的層數*/

static void calc_feature_scales( CvSeq* features, double sigma, int intvls )

{

    struct feature* feat;

    struct detection_data* ddata;

    double intvl;

    int i, n;

    n = features->total;//總的特徵點個數

    //遍歷特徵點

    for( i = 0; i < n; i++ )

    {

        //調用宏,獲取序列features中的第i個元素,並強制轉換爲struct feature類型

        feat = CV_GET_SEQ_ELEM( struct feature, features, i );

        //調用宏feat_detection_data來提取參數feat中的feature_data成員並轉換爲

        //detection_data類型的指針

        ddata = feat_detection_data( feat );

        //特徵點所在的層數ddata->intvl加上特徵點在層方向上的亞像素偏移量,獲得

        //特徵點的較爲精確的層數

        intvl = ddata->intvl + ddata->subintvl;

        //計算特徵點的尺度(公式見SIFT算法說明),並賦值給scl成員

        feat->scl = sigma * pow( 2.0, ddata->octv + intvl / intvls );

        //計算特徵點所在的組的尺度,給detection_data的scl_octv成員賦值

        ddata->scl_octv = sigma * pow( 2.0, intvl / intvls );

    }

}

 

(5.3.1)CV_GET_SEQ_ELEM函數說明:

答案:用法:從所給序列中取出元素的地址,注意:獲得的是地址,即指針

因此,關鍵在於序列中存放的是那種類型的數據,若存放的爲地址,那用這個宏獲得的就是指針的指針。
例程:
1.序列中存放的爲CvPoint
CvPoint pt=*CV_GET_SEQ_ELEM(CvPoint,hull,i);
2.序列中存放的爲CvPoint*,即指針
CvPoint* pt=*CV_GET_SEQ_ELEM(CvPoint*,hull,i);
或者
CvPoint pt=**CV_GET_SEQ_ELEM(CvPoint*,hull,i);
參看:關於宏CV_GET_SEQ_ELEM——https://blog.csdn.net/u013089125/article/details/20126215

 

5.4)adjust_for_img_dbl代碼及說明:

/*將特徵點序列中每一個特徵點的座標減半(當設置了將圖像放大爲原圖的2倍時,特徵點檢測

完以後調用)

參數:

features:特徵點序列*/

static void adjust_for_img_dbl( CvSeq* features )

{

    struct feature* feat;

    int i, n;

    n = features->total;//總的特徵點個數

    //遍歷特徵點

    for( i = 0; i < n; i++ )

    {

        //調用宏,獲取序列features中的第i個元素,並強制轉換爲struct feature類型

        feat = CV_GET_SEQ_ELEM( struct feature, features, i );

        //將特徵點的x,y座標和尺度都減半

        feat->x /= 2.0;

        feat->y /= 2.0;

        feat->scl /= 2.0;

        feat->img_pt.x /= 2.0;

        feat->img_pt.y /= 2.0;

    }

}

 

附:

1.問題描述:C版本的cvGetTickFrequency()函數和C++版本的getTickFrequency()計算時間的公式不同?

解決方案:參看博客:https://blog.csdn.net/chaipp0607/article/details/71056580

注意:OpenCV  C版本的cvGetTickFrequency()函數和C++版本的getTickFrequency()的單位不同

相關文章
相關標籤/搜索