OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波

来源:互联网 发布:淘宝保证金不能解冻 编辑:程序博客网 时间:2024/05/05 16:56

Refer from http://blog.csdn.net/chenyusiyuan/article/details/8710462

目录(?)[+]

 

 

KAZE系列笔记:

1.  OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波

2.  OpenCV学习笔记(28)KAZE 算法原理与源码分析(二)非线性尺度空间构建

3.  OpenCV学习笔记(29)KAZE 算法原理与源码分析(三)特征检测与描述

4.  OpenCV学习笔记(30)KAZE 算法原理与源码分析(四)KAZE特征的性能分析与比较

5.  OpenCV学习笔记(31)KAZE 算法原理与源码分析(五)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库,另外其计时函数的使用比较复杂,可以用OpenCV的cv::getTickCount代替)

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

5.  Computer Vision Talks 博主Ievgen Khvedchenia将KAZE集成到OpenCV的cv::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更新,对KAZE代码进行了优化)

7.  Matlab 版的接口程序,封装了1.0版的KAZE代码:https://github.com/vlfeat/vlbenchmarks/blob/unstable/%2BlocalFeatures/Kaze.m

 

 

简介

ECCV2012中出现了一种比SIFT更稳定的特征检测算法KAZE ([1])。KAZE的取名是为了纪念尺度空间分析的开创者—日本学者Iijima。KAZE是日语‘风’的谐音,寓意是就像风的形成是空气在空间中非线性的流动过程一样,KAZE特征检测是在图像域中进行非线性扩散处理的过程。

 

传统的SIFT、SURF等特征检测算法都是基于线性的高斯金字塔进行多尺度分解来消除噪声和提取显著特征点。但高斯分解是牺牲了局部精度为代价的,容易造成边界模糊和细节丢失。非线性的尺度分解有望解决这种问题,但传统方法基于正向欧拉法(forward Euler scheme)求解非线性扩散(Non-linear diffusion)方程时迭代收敛的步长太短,耗时长、计算复杂度高。由此,KAZE算法的作者提出采用加性算子分裂算法(Additive Operator Splitting, AOS)来进行非线性扩散滤波,可以采用任意步长来构造稳定的非线性尺度空间。

 

注:KAZE算法的原理与SIFT和SURF有很多相似之处,在深入了解KAZE之前,可以参考以下的博客文章对SIFT和SURF的介绍:

1.  OpenCV】SIFT原理与源码分析 - 小魏的修行路 (很不错的博客,用心、认真、图文并茂

2. SIFT特征提取分析 - Rachel Zhang的专栏 (这里的分析也很详细)

3.  Surf算法学习心得(一)——算法原理 - yang_yong’ blog

4.  Opencv学习笔记(六)SURF学习笔记 - crazy_sparrow的专栏

 

 

1.1 非线性扩散滤波

1.1.1 Perona-Malik扩散方程

具体地,非线性扩散滤波方法是将图像亮度(L)在不同尺度上的变化视为某种形式的流动函数(flow function)的散度(divergence),可以通过非线性偏微分方程来描述:

 

通过设置合适的传导函数 c(x,y,t) ,可以使得扩散自适应于图像的局部结构。传导函数可以是标量、也可以是张量。时间t作为尺度参数,其值越大、则图像的表示形式越简单。Perona和Malik提出了传导函数的构造方式:

 

其中的▽Lσ是高斯平滑后的图像Lσ的梯度(gradient)。函数 g() 的形式有如下几种:

  

   

其中函数g1优先保留高对比度的边缘,g2优先保留宽度较大的区域,g3能够有效平滑区域内部而保留边界信息(KAZE代码中默认采用函数g2)。

函数g1、g2、g3的实现代码如下(在文件 kaze_nldiffusion_functions.cpp 中):

[cpp] view plaincopy
  1. //*************************************************************************************  
  2. //*************************************************************************************  
  3.   
  4. /**  
  5.  * @brief This function computes the Perona and Malik conductivity coefficient g1  
  6.  * g1 = exp(-|dL|^2/k^2)  
  7.  * @param src Input image  
  8.  * @param dst Output image  
  9.  * @param Lx First order image derivative in X-direction (horizontal)  
  10.  * @param Ly First order image derivative in Y-direction (vertical)  
  11.  * @param k Contrast factor parameter  
  12.  */  
  13. void PM_G1(const cv::Mat &src, cv::Mat &dst, cv::Mat &Lx, cv::Mat &Ly, float k )  
  14. {  
  15.     cv::exp(-(Lx.mul(Lx) + Ly.mul(Ly))/(k*k),dst);  
  16. }  
  17.   
  18. //*************************************************************************************  
  19. //*************************************************************************************  
  20.   
  21. /**  
  22.  * @brief This function computes the Perona and Malik conductivity coefficient g2  
  23.  * g2 = 1 / (1 + dL^2 / k^2)  
  24.  * @param src Input image  
  25.  * @param dst Output image  
  26.  * @param Lx First order image derivative in X-direction (horizontal)  
  27.  * @param Ly First order image derivative in Y-direction (vertical)  
  28.  * @param k Contrast factor parameter  
  29.  */  
  30. void PM_G2(const cv::Mat &src, cv::Mat &dst, cv::Mat &Lx, cv::Mat &Ly, float k )  
  31. {  
  32.     dst = 1./(1. + (Lx.mul(Lx) + Ly.mul(Ly))/(k*k));  
  33. }  
  34.   
  35. //*************************************************************************************  
  36. //*************************************************************************************  
  37.   
  38. /**  
  39.  * @brief This function computes Weickert conductivity coefficient g3  
  40.  * @param src Input image  
  41.  * @param dst Output image  
  42.  * @param Lx First order image derivative in X-direction (horizontal)  
  43.  * @param Ly First order image derivative in Y-direction (vertical)  
  44.  * @param k Contrast factor parameter  
  45.  * @note For more information check the following paper: J. Weickert  
  46.  * Applications of nonlinear diffusion in image processing and computer vision,  
  47.  * Proceedings of Algorithmy 2000  
  48.  */  
  49. void Weickert_Diffusivity(const cv::Mat &src, cv::Mat &dst, cv::Mat &Lx, cv::Mat &Ly, float k )  
  50. {  
  51.     cv::Mat modg;  
  52.     cv::pow((Lx.mul(Lx) + Ly.mul(Ly))/(k*k),4,modg);  
  53.     cv::exp(-3.315/modg, dst);  
  54.     dst = 1.0 - dst;  
  55. }  


 

 

参数k是控制扩散级别的对比度因子(contrast factor),能够决定保留多少边缘信息,其值越大,保留的边缘信息越少。在KAZE算法中,参数k的取值是梯度图像▽Lσ的直方图70% 百分位上的值:

 

计算参数k的实现源码如下(在文件 kaze_nldiffusion_functions.cpp 中):

[cpp] view plaincopy
  1. //*************************************************************************************  
  2. //*************************************************************************************  
  3.   
  4. /**  
  5.  * @brief This function computes a good empirical value for the k contrast factor  
  6.  * given an input image, the percentile (0-1), the gradient scale and the number of  
  7.  * bins in the histogram  
  8.  * @param img Input image  
  9.  * @param perc Percentile of the image gradient histogram (0-1)  
  10.  * @param gscale Scale for computing the image gradient histogram  
  11.  * @param nbins Number of histogram bins  
  12.  * @param ksize_x Kernel size in X-direction (horizontal) for the Gaussian smoothing kernel  
  13.  * @param ksize_y Kernel size in Y-direction (vertical) for the Gaussian smoothing kernel  
  14.  * @return k contrast factor  
  15.  */  
  16. float Compute_K_Percentile(const cv::Mat &img, float perc, float gscale, unsigned int nbins, unsigned int ksize_x, unsigned int ksize_y)  
  17. {  
  18.     float kperc = 0.0, modg = 0.0, lx = 0.0, ly = 0.0;  
  19.     unsigned int nbin = 0, nelements = 0, nthreshold = 0, k = 0;  
  20.     float npoints = 0.0;    // number of points of which gradient greater than zero  
  21.     float hmax = 0.0;       // maximum gradient  
  22.   
  23.     // Create the array for the histogram  
  24.     float *hist = new float[nbins];  
  25.   
  26.     // Create the matrices  
  27.     cv::Mat gaussian = cv::Mat::zeros(img.rows,img.cols,CV_32F);  
  28.     cv::Mat Lx = cv::Mat::zeros(img.rows,img.cols,CV_32F);  
  29.     cv::Mat Ly = cv::Mat::zeros(img.rows,img.cols,CV_32F);  
  30.       
  31.     // Set the histogram to zero, just in case  
  32.     for( unsigned int i = 0; i < nbins; i++ )  
  33.     {  
  34.         hist[i] = 0.0;  
  35.     }  
  36.   
  37.     // Perform the Gaussian convolution  
  38.     Gaussian_2D_Convolution(img,gaussian,ksize_x,ksize_y,gscale);  
  39.               
  40.     // Compute the Gaussian derivatives Lx and Ly  
  41.     Image_Derivatives_Scharr(gaussian,Lx,1,0);  
  42.     Image_Derivatives_Scharr(gaussian,Ly,0,1);  
  43.       
  44.     // Skip the borders for computing the histogram  
  45.     for( int i = 1; i < gaussian.rows-1; i++ )  
  46.     {  
  47.         for( int j = 1; j < gaussian.cols-1; j++ )  
  48.         {  
  49.             lx = *(Lx.ptr<float>(i)+j);  
  50.             ly = *(Ly.ptr<float>(i)+j);  
  51.             modg = sqrt(lx*lx + ly*ly);  
  52.       
  53.             // Get the maximum  
  54.             if( modg > hmax )  
  55.             {  
  56.                 hmax = modg;  
  57.             }  
  58.         }  
  59.     }  
  60.   
  61.     // Skip the borders for computing the histogram  
  62.     for( int i = 1; i < gaussian.rows-1; i++ )  
  63.     {  
  64.         for( int j = 1; j < gaussian.cols-1; j++ )  
  65.         {  
  66.             lx = *(Lx.ptr<float>(i)+j);  
  67.             ly = *(Ly.ptr<float>(i)+j);  
  68.             modg = sqrt(lx*lx + ly*ly);     // modg can be stored in a matrix in last step in oder to avoid re-computation  
  69.   
  70.             // Find the correspondent bin  
  71.             if( modg != 0.0 )  
  72.             {  
  73.                 nbin = floor(nbins*(modg/hmax));  
  74.   
  75.                 if( nbin == nbins )  
  76.                 {  
  77.                     nbin--;  
  78.                 }  
  79.   
  80.                 hist[nbin]++;  
  81.                 npoints++;  
  82.             }  
  83.         }  
  84.     }  
  85.       
  86.     // Now find the perc of the histogram percentile  
  87.     nthreshold = (unsigned int)(npoints*perc);  
  88.       
  89.     // find the bin (k) in which accumulated points are greater than 70% (perc) of total valid points (npoints)  
  90.     for( k = 0; nelements < nthreshold && k < nbins; k++)  
  91.     {  
  92.         nelements = nelements + hist[k];  
  93.     }  
  94.       
  95.     if( nelements < nthreshold )  
  96.     {  
  97.         kperc = 0.03;  
  98.     }  
  99.     else  
  100.     {  
  101.         kperc = hmax*((float)(k)/(float)nbins);   
  102.     }  
  103.       
  104.     delete hist;  
  105.     return kperc;  
  106. }  


 

注:有关非线性扩散滤波的应用,参见[2]。

 

 

1.1.2 AOS算法

由于非线性偏微分方程并没有解析解,一般通过数值分析的方法进行迭代求解。传统上采用显式差分格式的求解方法只能采用小步长,收敛缓慢。为此,将方程离散化为以下的隐式差分格式:

 

其中Al是表示图像在各维度(l)上传导性的矩阵。该方程的解如下:

这种求解方法对任意时间步长(τ)都有效。上式中矩阵Al是三对角矩阵并且对角占优(tridiagonal and diagonally dominant matrix),这样的线性系统可以通过Thomas算法快速求解。(有关AOS的应用,参见[3])

 

该算法的实现源码如下(在文件 kaze.cpp 中):

 

[cpp] view plaincopy
  1. //*************************************************************************************  
  2. //*************************************************************************************  
  3.   
  4. /**  
  5.  * @brief This method performs a scalar non-linear diffusion step using AOS schemes  
  6.  * @param Ld Image at a given evolution step  
  7.  * @param Ldprev Image at a previous evolution step  
  8.  * @param c Conductivity image  
  9.  * @param stepsize Stepsize for the nonlinear diffusion evolution  
  10.  * @note If c is constant, the diffusion will be linear  
  11.  * If c is a matrix of the same size as Ld, the diffusion will be nonlinear  
  12.  * The stepsize can be arbitrarilly large  
  13. */  
  14. void KAZE::AOS_Step_Scalar(cv::Mat &Ld, const cv::Mat &Ldprev, const cv::Mat &c, const float stepsize)  
  15. {  
  16.    AOS_Rows(Ldprev,c,stepsize);  
  17.    AOS_Columns(Ldprev,c,stepsize);  
  18.   
  19.    Ld = 0.5*(Lty + Ltx.t());  
  20. }  
  21.   
  22. //*************************************************************************************  
  23. //*************************************************************************************  
  24.   
  25. /**  
  26.  * @brief This method performs a scalar non-linear diffusion step using AOS schemes  
  27.  * Diffusion in each dimension is computed independently in a different thread  
  28.  * @param Ld Image at a given evolution step  
  29.  * @param Ldprev Image at a previous evolution step  
  30.  * @param c Conductivity image  
  31.  * @param stepsize Stepsize for the nonlinear diffusion evolution  
  32.  * @note If c is constant, the diffusion will be linear  
  33.  * If c is a matrix of the same size as Ld, the diffusion will be nonlinear  
  34.  * The stepsize can be arbitrarilly large  
  35. */  
  36. #if HAVE_THREADING_SUPPORT  
  37. void KAZE::AOS_Step_Scalar_Parallel(cv::Mat &Ld, const cv::Mat &Ldprev, const cv::Mat &c, const float stepsize)  
  38. {  
  39.    boost::thread *AOSth1 = new boost::thread(&KAZE::AOS_Rows,this,Ldprev,c,stepsize);  
  40.    boost::thread *AOSth2 = new boost::thread(&KAZE::AOS_Columns,this,Ldprev,c,stepsize);  
  41.      
  42.    AOSth1->join();  
  43.    AOSth2->join();  
  44.      
  45.    Ld = 0.5*(Lty + Ltx.t());  
  46.   
  47.    delete AOSth1;  
  48.    delete AOSth2;  
  49. }  
  50. #endif  
  51.   
  52. //*************************************************************************************  
  53. //*************************************************************************************  
  54.   
  55. /**  
  56.  * @brief This method performs performs 1D-AOS for the image rows  
  57.  * @param Ldprev Image at a previous evolution step  
  58.  * @param c Conductivity image  
  59.  * @param stepsize Stepsize for the nonlinear diffusion evolution  
  60. */  
  61. void KAZE::AOS_Rows(const cv::Mat &Ldprev, const cv::Mat &c, const float stepsize)  
  62. {  
  63.    // Operate on rows  
  64.    for( int i = 0; i < qr.rows; i++ )  
  65.    {  
  66.        for( int j = 0; j < qr.cols; j++ )  
  67.        {  
  68.            *(qr.ptr<float>(i)+j) = *(c.ptr<float>(i)+j) + *(c.ptr<float>(i+1)+j);  
  69.        }  
  70.    }  
  71.      
  72.    for( int j = 0; j < py.cols; j++ )  
  73.    {  
  74.        *(py.ptr<float>(0)+j) = *(qr.ptr<float>(0)+j);  
  75.    }  
  76.   
  77.    for( int j = 0; j < py.cols; j++ )  
  78.    {  
  79.        *(py.ptr<float>(py.rows-1)+j) = *(qr.ptr<float>(qr.rows-1)+j);  
  80.    }  
  81.      
  82.    for( int i = 1; i < py.rows-1; i++ )  
  83.    {  
  84.        for( int j = 0; j < py.cols; j++ )  
  85.        {  
  86.            *(py.ptr<float>(i)+j) = *(qr.ptr<float>(i-1)+j) + *(qr.ptr<float>(i)+j);  
  87.        }  
  88.    }  
  89.   
  90.    // a = 1 + t.*p; (p is -1*p)  
  91.    // b = -t.*q;  
  92.    ay = 1.0 + stepsize*py; // p is -1*p  
  93.    by = -stepsize*qr;  
  94.   
  95.    // Call to Thomas algorithm now  
  96.    Thomas(ay,by,Ldprev,Lty);  
  97.      
  98. }  
  99.   
  100. //*************************************************************************************  
  101. //*************************************************************************************  
  102.   
  103. /**  
  104.  * @brief This method performs performs 1D-AOS for the image columns  
  105.  * @param Ldprev Image at a previous evolution step  
  106.  * @param c Conductivity image  
  107.  * @param stepsize Stepsize for the nonlinear diffusion evolution  
  108. */  
  109. void KAZE::AOS_Columns(const cv::Mat &Ldprev, const cv::Mat &c, const float stepsize)  
  110. {  
  111.    // Operate on columns  
  112.    for( int j = 0; j < qc.cols; j++ )  
  113.    {  
  114.        for( int i = 0; i < qc.rows; i++ )  
  115.        {  
  116.            *(qc.ptr<float>(i)+j) = *(c.ptr<float>(i)+j) + *(c.ptr<float>(i)+j+1);  
  117.        }  
  118.    }  
  119.   
  120.    for( int i = 0; i < px.rows; i++ )  
  121.    {  
  122.        *(px.ptr<float>(i)) = *(qc.ptr<float>(i));  
  123.    }  
  124.   
  125.    for( int i = 0; i < px.rows; i++ )  
  126.    {  
  127.        *(px.ptr<float>(i)+px.cols-1) = *(qc.ptr<float>(i)+qc.cols-1);  
  128.    }  
  129.      
  130.    for( int j = 1; j < px.cols-1; j++ )  
  131.    {  
  132.        for( int i = 0; i < px.rows; i++ )  
  133.        {  
  134.            *(px.ptr<float>(i)+j) = *(qc.ptr<float>(i)+j-1) + *(qc.ptr<float>(i)+j);  
  135.        }  
  136.    }  
  137.   
  138.    // a = 1 + t.*p';  
  139.    ax = 1.0 + stepsize*px.t();  
  140.      
  141.    // b = -t.*q';  
  142.    bx = -stepsize*qc.t();  
  143.      
  144.    // Call Thomas algorithm again  
  145.    // But take care since we need to transpose the solution!!  
  146.    Thomas(ax,bx,Ldprev.t(),Ltx);  
  147. }  
  148.   
  149. //*************************************************************************************  
  150. //*************************************************************************************  
  151.   
  152. /**  
  153.  * @brief This method does the Thomas algorithm for solving a tridiagonal linear system  
  154.  * @note The matrix A must be strictly diagonally dominant for a stable solution  
  155. */  
  156. void KAZE::Thomas(cv::Mat a, cv::Mat b, cv::Mat Ld, cv::Mat x)  
  157. {  
  158.    // Auxiliary variables  
  159.    int n = a.rows;  
  160.    cv::Mat m = cv::Mat::zeros(a.rows,a.cols,CV_32F);  
  161.    cv::Mat l = cv::Mat::zeros(b.rows,b.cols,CV_32F);  
  162.    cv::Mat y = cv::Mat::zeros(Ld.rows,Ld.cols,CV_32F);  
  163.   
  164.    /** A*x = d;                                                                            */  
  165.    /**  / a1 b1  0  0 0  ...    0 \  / x1 \ = / d1 \                                           */  
  166.    /**  | c1 a2 b2  0 0  ...    0 |  | x2 | = | d2 |                                           */  
  167.    /**  |  0 c2 a3 b3 0  ...    0 |  | x3 | = | d3 |                                           */  
  168.    /**  |  :  :  :  : 0  ...    0 |  |  : | = |  : |                                           */  
  169.    /**  |  :  :  :  : 0  cn-1  an |  | xn | = | dn |                                           */  
  170.   
  171.    /** 1. LU decomposition 
  172.    / L = / 1                 \      U = / m1 r1            \ 
  173.    /     | l1 1              |          |    m2 r2         | 
  174.    /     |    l2 1          |           |       m3 r3      | 
  175.    /      |     : : :        |          |       :  :  :    | 
  176.    /      \           ln-1 1 /          \               mn /    */  
  177.   
  178.    for( int j = 0; j < m.cols; j++ )  
  179.    {  
  180.        *(m.ptr<float>(0)+j) = *(a.ptr<float>(0)+j);  
  181.    }  
  182.      
  183.    for( int j = 0; j < y.cols; j++ )  
  184.    {  
  185.        *(y.ptr<float>(0)+j) = *(Ld.ptr<float>(0)+j);  
  186.    }  
  187.      
  188.    // 2. Forward substitution L*y = d for y  
  189.    for( int k = 1; k < n; k++ )  
  190.    {  
  191.        for( int j=0; j < l.cols; j++ )  
  192.        {  
  193.            *(l.ptr<float>(k-1)+j) = *(b.ptr<float>(k-1)+j) / *(m.ptr<float>(k-1)+j);  
  194.        }  
  195.          
  196.        for( int j=0; j < m.cols; j++ )  
  197.        {  
  198.            *(m.ptr<float>(k)+j) = *(a.ptr<float>(k)+j) - *(l.ptr<float>(k-1)+j)*(*(b.ptr<float>(k-1)+j));  
  199.        }  
  200.          
  201.        for( int j=0; j < y.cols; j++ )  
  202.        {  
  203.            *(y.ptr<float>(k)+j) = *(Ld.ptr<float>(k)+j) - *(l.ptr<float>(k-1)+j)*(*(y.ptr<float>(k-1)+j));  
  204.        }  
  205.    }  
  206.      
  207.    // 3. Backward substitution U*x = y  
  208.    for( int j=0; j < y.cols; j++ )  
  209.    {  
  210.        *(x.ptr<float>(n-1)+j) = (*(y.ptr<float>(n-1)+j))/(*(m.ptr<float>(n-1)+j));  
  211.    }  
  212.      
  213.    for( int i = n-2; i >= 0; i-- )  
  214.    {  
  215.        for( int j = 0; j < x.cols; j++ )  
  216.        {  
  217.            *(x.ptr<float>(i)+j) = (*(y.ptr<float>(i)+j) - (*(b.ptr<float>(i)+j))*(*(x.ptr<float>(i+1)+j)))/(*(m.ptr<float>(i)+j));  
  218.        }  
  219.    }  
  220. }  


上面介绍了非线性扩散滤波和AOS求解隐性差分方程的原理,是KAZE算法求解非线性尺度空间的基础,下一节我们将介绍KAZE算法的非线性尺度空间构建、特征检测与描述等内容。

 

待续...

 

 

 

 

Ref:

[1] http://www.robesafe.com/personal/pablo.alcantarilla/papers/Alcantarilla12eccv.pdf

[2] http://manu16.magtech.com.cn/geoprog/CN/article/downloadArticleFile.do?attachType=PDF&id=3146

[3] http://file.lw23.com/8/8e/8ec/8ecd21e4-b030-4e05-9333-40cc2d97bde4.pdf

 


0 0