OpenCV学习笔记(31)KAZE 算法原理与源码分析(五)KAZE的源码优化及与SIFT的比较

来源:互联网 发布:new3dsll怎么关掉网络 编辑:程序博客网 时间:2024/05/19 00:11

 

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 ) )