1. 程式人生 > >OpenCV學習筆記(31)KAZE 演算法原理與原始碼分析(五)KAZE的原始碼優化及與SIFT的比較

OpenCV學習筆記(31)KAZE 演算法原理與原始碼分析(五)KAZE的原始碼優化及與SIFT的比較

 

KAZE系列筆記:

1.  OpenCV學習筆記(27KAZE 演算法原理與原始碼分析(一)非線性擴散濾波

2.  OpenCV學習筆記(28KAZE 演算法原理與原始碼分析(二)非線性尺度空間構建

3.  OpenCV學習筆記(29KAZE 演算法原理與原始碼分析(三)特徵檢測與描述

4.  OpenCV學習筆記(30KAZE 演算法原理與原始碼分析(四)KAZE特徵的效能分析與比較

5.  OpenCV學習筆記(31KAZE 演算法原理與原始碼分析(五)KAZE的原始碼優化及與SIFT的比較

 

 

KAZE演算法資源:

1.  論文:  http://www.robesafe.com/personal/pablo.alcantarilla/papers/Alcantarilla12eccv.pdf

2.  專案主頁:http://www.robesafe.com/personal/pablo.alcantarilla/kaze.html

3.  作者程式碼:http://www.robesafe.com/personal/pablo.alcantarilla/code/kaze_features_1_4.tar


(需要boost庫,另外其計時函式的使用比較複雜,可以用OpenCVcv::getTickCount代替)

4.  Computer Vision Talks的評測:http://computer-vision-talks.com/2013/03/porting-kaze-features-to-opencv/

5.  Computer Vision Talks 博主Ievgen KhvedcheniaKAZE整合到OpenCVcv::Feature2D類,但需要重新編譯OpenCV,並且沒有實現演算法引數調整和按Mask過濾特徵點的功能:https://github.com/BloodAxe/opencv/tree/kaze-features

6.  我在Ievgen的專案庫中提取出KAZE,封裝成繼承cv::Feature2D的類,無需重新編譯OpenCV,實現了引數調整和Mask過濾的功能: https://github.com/yuhuazou/kaze_opencv 2013-03-28更新,速度顯著提升,推薦使用

7.  Matlab 版的介面程式,封裝了1.0版的KAZE程式碼:https://github.com/vlfeat/vlbenchmarks/blob/unstable/%2BlocalFeatures/Kaze.m

 

 

2.4 KAZE原始碼的效能優化

    最近通過對KAZE原始碼的深入分析,發現作者的程式碼有較大的優化空間。我通過以下方法對程式碼進行了系列優化:

1)  儘量減少多層 for 迴圈。特別是對矩陣的運算,如果矩陣是連續的(即 matA. isContinuous () = true),則可以只用單層迴圈遍歷所有元素。(參見kaze.cpp中的AOS_Rows()函式)

2)  直接使用指標來訪問元素。例如 *(matA.ptr<float>(i)+j) = val; 。這種方式原始碼作者也用的很多。

3)  儘量消除 for 迴圈內的 if 判斷,或者將 if 判斷移動到 for 迴圈外。這樣可以顯著提高運算速度。可以通過建立和查詢列表的方式避免if判斷(參見kaze_nldiffusion_functions.cppCompute_Scharr_Derivatives()函式)。

4)  使用 OpenMP for 迴圈進行平行計算。需要注意的是OpenMP儘量放在for迴圈的最外層,而且如果for迴圈內的執行模組耗時很短,例如是元素的訪問和四則運算等,就不要加OpenMP了,核心和執行緒的切換本身也需要時間,頻繁的執行緒切換反而會降低運算速度。

5)  使用Boost/thread庫來執行耗時較長的函式。例如特徵點檢測中需要在所有金字塔層級中執行對區域性極大值點的尋找,就可以用多執行緒來完成。(參見kaze.cpp中的Determinant_Hessian_Parallel()函式)

    通過上述方法,KAZE的特徵檢測時間已經能夠接近甚至低於SURF了。下面將通過系列實驗結果對KAZE作進一步的分析和比較。這裡我使用了KAZE作者在論文中提到的測試影象庫(http://www.robots.ox.ac.uk/~vgg/research/affine/ )。這個影象庫包含了8組高解析度的影象,每組有6幅影象,分別在光照、模糊、視角、縮放、旋轉、壓縮損失等方面有由淺入深、由簡單到複雜的變化,可以很好地檢驗特徵檢測演算法的效能。其中,bikes和trees圖集用於檢驗演算法對影象高斯模糊的魯棒性;graf和wall圖集用於檢驗演算法對視角變換的魯棒性;bark和boats圖集用於檢驗演算法對旋轉和縮放的魯棒性;leuven圖集用於檢驗演算法對光照變化的魯棒性;ubc圖集則用於檢驗演算法對壓縮重構造成的影象質量損失的魯棒性。

                                   圖1

    這裡採用上述8組資料中編號為1的影象進行測試,測試程式碼用的是Computer Vision Talks博主Ievgengithub上的OpenCV-Features-Comparison測試中KAZE的引數設定如下:omax=2 (即nOctaves=2),nsublevels=4 (即 nOctaveLayers=4),其它引數取預設值;其它特徵演算法取OpenCV的預設引數。從下面的執行時間檢測結果看,經過優化後,在整幅影象的特徵檢測上KAZESURF要快,就單個特徵點的檢測來說KAZE也和SURF相差不大了。

                                

圖2

 

2.5 KAZESIFT的比較

    下面我們再詳細比較 KAZE 與 SIFT 的效能。根據測試結果,兩種演算法對於ubc、bikes、trees和boat這四種圖集都有很好的魯棒性,能夠準確將編號1影象與編號2-6的影象分別匹配起來。兩種演算法的差異主要是在bark、graf、leuven和wall圖集中表現出來的。如圖3所示:

(1)bark圖集主要檢驗特徵演算法對旋轉和縮放的魯棒性。從圖3可見KAZE演算法有效檢測的特徵點少於SIFT演算法,沒能成功匹配Img-1和Img-6,而SIFT演算法則能成功匹配所有5對影象;結合圖4我們可以發現,KAZE演算法在尺度不變性上是遜於SIFT的,當縮放係數低於0.6以後,KAZE的正確匹配率就會明顯下降,而SIFT則能保持60%以上的正確匹配率。通過對SIFT的原始碼分析可以知道,SIFT中每組Octave的層數nOctaveLayer預設為3層,而組數nOctaves則是根據影象的大小自動生成的:

    int nOctaves = actualNOctaves > 0 ? actualNOctaves : cvRound(std::log( (double)std::min( base.cols, base.rows ) ) / std::log(2.) - 2) - firstOctave;


    這使得 SIFT 能夠根據影象尺寸選擇合適的尺度範圍,在不同尺度上都能檢測到關鍵點,保證其尺度不變性。測試中KAZE演算法的nOctaves=2, nLayers=4,包含的尺度範圍較少。在原始碼裡這兩個引數的調整需要使用者輸入。可以仿照SIFT那樣自動計算nOctave,但由於KAZE構造非線性尺度空間耗時較長,太多的nOctave卻會降低KAZE的效率。

P.S. 我後來把KAZE的Octave組數恢復為預設的4組,雖然耗時增加了,但是特徵點的數量以及特徵的匹配準確率卻提高了,bark和leuven圖集中Img1-Img6能夠成功匹配;graf圖集的Img1-Img5能夠成功匹配,而SIFT則不能

(2)graf和wall圖集側重檢驗演算法對視角變化的魯棒性。可以看到KAZE演算法有效檢測的特徵數和成功匹配的點對數均比SIFT高,不過兩者都不能將graf圖集的Img1與Img6匹配起來,而SIFT演算法也不能匹配出wall圖集的Img-1和Img-6.

(3)leuven圖集側重於光照變化方面的檢驗。KAZE演算法和SIFT演算法都表現穩定,對光照變化不敏感。

    在後期的進一步試驗發現,KAZE特徵的匹配對引數的設定比較敏感。我github上最新的樣例 azeOpenCV.cpp 中使用 BFMatcher 或 FlannBasedMatcher 進行特徵匹配,預設情況下會對匹配後的結果作初步過濾(filterMatches=true),篩選出小於2倍最小距離的配對特徵,然後再尋找Homography。實驗發現,這樣的初步過濾在大部分情況下可以有效排除冗餘配對的干擾,找出正確的Homography;但在極限情況下(例如視角變換大、明暗差異大、尺度差異大等),初步過濾又會減少配對數量,從而找不到有效的Homography。而SIFT則比較穩定,做不做過濾都能找到Homography。可能KAZE的描述向量還是有改進的空間,後期可以測試下用作者最新的G-SURF描述向量,或者用其它型別的描述符來搭配測試

   

P.S. 1. 進一步的測試表明,採用 KnnMatch (k=2, maxRatio=0.75) 的方法,不需要初步過濾,無論BFMatcher還是FlannBasedMatcher都能取得穩定的匹配結果。大家可以參考我github程式碼裡的KazeOpenCV.cpp自行測試驗證。

    2. 此外我還測試了 KAZE 特徵點與SIFT、SURF、BRIEF、FREAK描述向量相結合的效果,結果如圖5所示。可以看到KAZE+SIFT的組合在光照變化、高斯模糊和尺度變化三方面都比 純KAZE 方法略好,但意外的是 KAZE+SIFT 不具有旋轉不變性。與 純KAZE 方法較接近的是 KAZE+FREAK 的組合,而 KAZE+BRIEF 則不具有旋轉不變性,KAZE+SURF對高斯模糊效果較差純 KAZE 方法的綜合表現還是最高的,這應該表明基於非線性尺度空間的 KAZE 特徵點不適合使用基於線性尺度空間的特徵描述演算法來表徵

   

 

void bfMatch( Mat& descriptors_1, Mat& descriptors_2, vector<DMatch>& good_matches, bool filterMatches = true )
{
    //-- Matching descriptor vectors using Brute-Force matcher
    cout << "--> Use BFMatcher..." << endl;
    BFMatcher matcher(cv::NORM_L2, true);
    vector< DMatch > matches;
    matcher.match( descriptors_1, descriptors_2, matches );

    if (!filterMatches)
    {
        good_matches = matches;
    } 
    else
    {
        double max_dist = 0, min_dist = 100, thresh = 0;

        //-- Quick calculation of max and min distances between keypoints
        for( int i = 0; i < matches.size(); i++ )
        { 
            double dist = matches[i].distance;
            if( dist < min_dist ) min_dist = dist;
            if( dist > max_dist ) max_dist = dist;
        }
        //thresh = MAX(2*min_dist, min_dist + 0.5*(max_dist - min_dist));
        thresh = 2*min_dist;

        //-- Find initial good matches (i.e. whose distance is less than 2*min_dist )
        for( int i = 0; i < matches.size(); i++ )
        { 
            if( matches[i].distance < thresh )	
            { 
                good_matches.push_back( matches[i]); 
            }
        }
    }
}


 

  圖3 (KAZE: nOctaves = 4, nOctaveLayer = 4; SIFT: nOctave = log(min(img.rows,img.cols)) / log(2) - 2, nOctaveLayer = 3 )

                                              

   圖4

圖5 (image: boat/img1.ppm, MatchRatio = Number_Correct_Match / min( Number_Keypoints_Image_1,  Number_Keypoints_Image_2 ) )