zoukankan      html  css  js  c++  java
  • 【特征匹配】SIFT原理与C源代码剖析

    相关: KD树+BBF算法解析

               SURF原理与源代码解析

         SIFT的原理已经有非常多大牛的博客上做了解析,本文重点将以Rob Hess等人用C实现的代码做解析,结合代码SIFT原理会更easy理解。一些难理解点的用了标注。

          欢迎大家批评指正。

        转载请注明出处:http://blog.csdn.net/luoshixian099/article/details/47377611

         SIFT(Scale-invariant feature transform)即尺度不变特征转换,提取的局部特征点具有尺度不变性,且对于旋转。亮度,噪声等有非常高的稳定性。


    下图中,涉及到图像的旋转,仿射,光照等变化,SIFT算法依旧有非常好的匹配效果。


    SIFT特征点提取

    本文将下面函数为參照顺序介绍SIFT特征点提取与描写叙述方法。

     1.图像预处理

     2.构建高斯金字塔(不同尺度下的图像)

     3.生成DOG尺度空间

     4.关键点搜索与定位

     5.计算特征点所在的尺度

     6.为特征点分配方向角

     7.构建特征描写叙述子


    /**
       Finds SIFT features in an image using user-specified parameter values.  All
       detected features are stored in the array pointed to by a feat.
    */
    int _sift_features( IplImage* img, struct feature** feat, int intvls,
    		    double sigma, double contr_thr, int curv_thr,
    		    int img_dbl, int descr_width, int descr_hist_bins )
    {
      IplImage* init_img;
      IplImage*** gauss_pyr, *** dog_pyr;
      CvMemStorage* storage;
      CvSeq* features;
      int octvs, i, n = 0;
      
      /* check arguments */
      if( ! img )
        fatal_error( "NULL pointer error, %s, line %d",  __FILE__, __LINE__ );
      if( ! feat )
        fatal_error( "NULL pointer error, %s, line %d",  __FILE__, __LINE__ );
    
      /* build scale space pyramid; smallest dimension of top level is ~4 pixels */
      init_img = create_init_img( img, img_dbl, sigma );                            //对进行图片预处理       
      octvs = log( MIN( init_img->width, init_img->height ) ) / log(2) - 2;  //计算高斯金字塔的组数(octave),同一时候保证顶层至少有4个像素点
      gauss_pyr = build_gauss_pyr( init_img, octvs, intvls, sigma );  //建立高斯金字塔
      dog_pyr = build_dog_pyr( gauss_pyr, octvs, intvls );   //DOG尺度空间
      
      storage = cvCreateMemStorage( 0 );
      features = scale_space_extrema( dog_pyr, octvs, intvls, contr_thr,   //极值点检測,并去除不稳定特征点
    				  curv_thr, storage );
      calc_feature_scales( features, sigma, intvls );                      //计算特征点所在的尺度
      if( img_dbl )
        adjust_for_img_dbl( features );                                       //假设图像初始被扩大了2倍。全部坐标与尺度要除以2
      calc_feature_oris( features, gauss_pyr );                               //计算特征点所在尺度内的方向角
      compute_descriptors( features, gauss_pyr, descr_width, descr_hist_bins );//计算特征描写叙述子 128维向量
    
      /* sort features by decreasing scale and move from CvSeq to array */
      cvSeqSort( features, (CvCmpFunc)feature_cmp, NULL );   //对特征点按尺度排序
      n = features->total;
      *feat = calloc( n, sizeof(struct feature) );
      *feat = cvCvtSeqToArray( features, *feat, CV_WHOLE_SEQ );       
      for( i = 0; i < n; i++ )
        {
          free( (*feat)[i].feature_data );
          (*feat)[i].feature_data = NULL;
        }
      
      cvReleaseMemStorage( &storage );
      cvReleaseImage( &init_img );
      release_pyr( &gauss_pyr, octvs, intvls + 3 );
      release_pyr( &dog_pyr, octvs, intvls + 2 );
      return n;
    }


      —————————————————————————————————————————————————————


     1.图像预处理


    /************************ Functions prototyped here **************************/
    
    /*
      Converts an image to 8-bit grayscale and Gaussian-smooths it.  The image is
      optionally doubled in size prior to smoothing.
    
      @param img input image
      @param img_dbl if true, image is doubled in size prior to smoothing
      @param sigma total std of Gaussian smoothing
    */
    static IplImage* create_init_img( IplImage* img, int img_dbl, double sigma )
    {
      IplImage* gray, * dbl;
      double sig_diff;
    
      gray = convert_to_gray32( img );   //转换为32位灰度图
      if( img_dbl )                                  // 图像被放大二倍
        {
          sig_diff = sqrt( sigma * sigma - SIFT_INIT_SIGMA * SIFT_INIT_SIGMA * 4 );   //  sigma = 1.6 , SIFT_INIT_SIGMA = 0.5  lowe觉得图像在尺度0.5下最清晰
          dbl = cvCreateImage( cvSize( img->width*2, img->height*2 ),
    			   IPL_DEPTH_32F, 1 );
          cvResize( gray, dbl, CV_INTER_CUBIC );  //双三次插值方法 放大图像
          cvSmooth( dbl, dbl, CV_GAUSSIAN, 0, 0, sig_diff, sig_diff );     //高斯平滑
          cvReleaseImage( &gray );
          return dbl;
        }
      else
        {
          sig_diff = sqrt( sigma * sigma - SIFT_INIT_SIGMA * SIFT_INIT_SIGMA );
          cvSmooth( gray, gray, CV_GAUSSIAN, 0, 0, sig_diff, sig_diff ); // 高斯平滑
          return gray;
        }
    }
    lowe建议把初始图像放大二倍。能够得到很多其它的特征点,提取到很多其它细节,而且觉得图像在尺度σ = 0.5时图像最清晰,初始高斯尺度为σ = 1.6。
    第19行由于图像被放大二倍,此时σ = 1.0 。

    由于对二倍化后的图像平滑是在σ = 0.5 上叠加的高斯模糊。

      所以有模糊系数有sig_diff = sqrt (sigma *sigma - 0.5*0.5*4)=sqrt(1.6*1.6 -1) ;

     2.构建高斯金字塔


    构建高斯金字塔过程即构建出图像在不同尺度上图像,提取到的特征点可有具有尺度不变性。
    图像的尺度空间L(x,y,σ)能够用一个高斯函数G(x,y,σ)与图像I(x,y)卷积产生,即L(x,y,σ) = G(x,y,σ) * I(x,y) 
    当中二维高斯核的计算为             
    不同的尺度空间即用不同的高斯核函数平滑图像, 平滑系数越大。图像越模糊。即模拟出动物的视觉效果,由于事先不知道物体的大小,在不同的尺度下,图像的细节会表现的不同。当尺度由小变大的过程中,是一个细节逐步简化的过程,图像中特征不够明显的物体,就模糊的多了。而有些物体还能够看得到大致的轮廓。所以要在不同尺度下。观察物体的尺度响应,提取到的特征才干具有尺度不变性。


    SIFT算法採用高斯金字塔实现连续的尺度空间的图像。金字塔共分为O(octave)组。每组有S(intervals)层 ,下一组是由上一组隔点採样得到(即降2倍分辨率),这是为了减轻卷积运算的工作量。
    构建高斯金字塔(octave = 5, intervals +3=6):
                            
    所有空间尺度为: 
                                             

    ☆1.这个尺度因子都是在原图上进行的。而在算法实现过程中,採用高斯平滑是在上一层图像上再叠加高斯平滑。即我们在程序中看到的平滑因子为   

                
    Eg. 在第一层上为了得到kσ的高斯模糊图像,能够在原图上直接採用kσ平滑,也能够在上一层图像上(已被σ平滑)的图像上採用平滑因子为平滑图像,效果是一样的。
     ☆2.我们在源代码上同一时候也没有看到组间的2倍的关系,实际在对每组的平滑因子都是一样的,2倍的关系是因为在降採样的过程中产生的,第二层的第一张图是由第一层的平滑因子为2σ的图像上(即倒数第三张)降採样得到,此时图像平滑因子为σ,所以继续採用以上的平滑因子。

    但在原图上看。形成了所有的空间尺度。

    ☆3.每组(octave)有S+3层图像,是因为在DOG尺度空间上寻找极值点的方法是在一个立方体内进行,即上下层比較。所以不在DOG空间的第一层与最后一层寻找,即DOG须要S+2层图像,因为DOG尺度空间是由高斯金字塔相邻图像相减得到,即每组须要S+3层图像。


    /*
      Builds Gaussian scale space pyramid from an image
      @param base base image of the pyramid
      @param octvs number of octaves of scale space
      @param intvls number of intervals per octave
      @param sigma amount of Gaussian smoothing per octave
    
      @return Returns a Gaussian scale space pyramid as an octvs x (intvls + 3)
        array 
    	
    给定组数(octave)和层数(intvls)。以及初始平滑系数sigma,构建高斯金字塔
    返回的每组中层数为intvls+3
    */
    static IplImage*** build_gauss_pyr( IplImage* base, int octvs,
    			     int intvls, double sigma )
    {
      IplImage*** gauss_pyr;
      const int _intvls = intvls;             // lowe 採用了每组层数(intvls)为 3
     // double  sig_total, sig_prev;
    	 double  k;
      int i, o;
      double *sig = (double *)malloc(sizeof(int)*(_intvls+3));  //存储每组的高斯平滑因子,每组相应的平滑因子都同样
    
      gauss_pyr = calloc( octvs, sizeof( IplImage** ) );              
      for( i = 0; i < octvs; i++ )
        gauss_pyr[i] = calloc( intvls + 3, sizeof( IplImage *) );
    
      /*
        precompute Gaussian sigmas using the following formula:
    
        sigma_{total}^2 = sigma_{i}^2 + sigma_{i-1}^2
    
        sig[i] is the incremental sigma value needed to compute 
        the actual sigma of level i. Keeping track of incremental
        sigmas vs. total sigmas keeps the gaussian kernel small.
      */
      k = pow( 2.0, 1.0 / intvls );                 // k = 2^(1/S)
      sig[0] = sigma;
      sig[1] = sigma * sqrt( k*k- 1 );
      for (i = 2; i < intvls + 3; i++)
          sig[i] = sig[i-1] * k;                       //每组相应的平滑因子为 σ ,  sqrt(k^2 -1)* σ, sqrt(k^2 -1)* kσ , ...
    
      for( o = 0; o < octvs; o++ )
        for( i = 0; i < intvls + 3; i++ )
          {
    	if( o == 0  &&  i == 0 )
    	  gauss_pyr[o][i] = cvCloneImage(base);                       //第一组。第一层为原图
    
    	/* base of new octvave is halved image from end of previous octave */
    	else if( i == 0 )
    	  gauss_pyr[o][i] = downsample( gauss_pyr[o-1][intvls] );  //第一层图像由上一层倒数第三张隔点採样得到
    	  
    	/* blur the current octave's last image to create the next one */
    	else
    	  {
    	    gauss_pyr[o][i] = cvCreateImage( cvGetSize(gauss_pyr[o][i-1]),
    					     IPL_DEPTH_32F, 1 );
    	    cvSmooth( gauss_pyr[o][i-1], gauss_pyr[o][i],
    		      CV_GAUSSIAN, 0, 0, sig[i], sig[i] );                       //高斯平滑
    	  }
          }
         
       
      return gauss_pyr;
    }

    3.生成DOG尺度空间

    Lindeberg发现高斯差分函数(Difference of Gaussian 。简称DOG算子)与尺度归一化的高斯拉普拉斯函数很近似,且






     
    差分近似:

    lowe建议採用相邻尺度的图像相减来获得高斯差分图像D(x,y,σ)来近似LOG来进行极值检測。
    D(x,y,σ) = G(x,y,kσ)*I(x,y)-G(x,y,σ)*I(x,y)
                  =L(x,y,kσ) - L(x,y,σ)
    对高斯金字塔的每组内相邻图像相减。形成DOG尺度空间,这时DOG中每组有S+2层图像


    static IplImage*** build_dog_pyr( IplImage*** gauss_pyr, int octvs, int intvls )
    {
      IplImage*** dog_pyr;
      int i, o;
    
      dog_pyr = calloc( octvs, sizeof( IplImage** ) );
      for( i = 0; i < octvs; i++ )
        dog_pyr[i] = calloc( intvls + 2, sizeof(IplImage*) );
    
      for( o = 0; o < octvs; o++ )
        for( i = 0; i < intvls + 2; i++ )
          {
    	dog_pyr[o][i] = cvCreateImage( cvGetSize(gauss_pyr[o][i]),
    				       IPL_DEPTH_32F, 1 );
    	cvSub( gauss_pyr[o][i+1], gauss_pyr[o][i], dog_pyr[o][i], NULL );   //相邻两层图像相减,结果放在dog_pyr数组内
          }
    
      return dog_pyr;
    }


     4.关键点搜索与定位

      在DOG尺度空间上,首先寻找极值点,插值处理,找到准确的极值点坐标,再排除不稳定的特征点(边界点)
    /*
      Detects features at extrema in DoG scale space.  Bad features are discarded
      based on contrast and ratio of principal curvatures.
    
      @return Returns an array of detected features whose scales, orientations,
        and descriptors are yet to be determined.
    */
    static CvSeq* scale_space_extrema( IplImage*** dog_pyr, int octvs, int intvls,
    				   double contr_thr, int curv_thr,
    				   CvMemStorage* storage )
    {
      CvSeq* features;
      double prelim_contr_thr = 0.5 * contr_thr / intvls; //极值比較前的阈值处理
      struct feature* feat;
      struct detection_data* ddata;
      int o, i, r, c;
    
      features = cvCreateSeq( 0, sizeof(CvSeq), sizeof(struct feature), storage );
      for( o = 0; o < octvs; o++ )                     //对DOG尺度空间上,遍历从第二层图像開始到倒数第二层图像上。每一个像素点
        for( i = 1; i <= intvls; i++ )
          for(r = SIFT_IMG_BORDER; r < dog_pyr[o][0]->height-SIFT_IMG_BORDER; r++)        
    	for(c = SIFT_IMG_BORDER; c < dog_pyr[o][0]->width-SIFT_IMG_BORDER; c++)
    	  /* perform preliminary check on contrast */
    	  if( ABS( pixval32f( dog_pyr[o][i], r, c ) ) > prelim_contr_thr )    // 排除像素值小于阈值prelim_contr_thr的点,提高稳定性
    	    if( is_extremum( dog_pyr, o, i, r, c ) )             //与周围26个像素值比較,是否极大值或者极小值点
    	      {
    		feat = interp_extremum(dog_pyr, o, i, r, c, intvls, contr_thr); //插值处理,找到准确的特征点坐标
    		if( feat )
    		  {
    		    ddata = feat_detection_data( feat );
    		    if( ! is_too_edge_like( dog_pyr[ddata->octv][ddata->intvl],   //依据Hessian矩阵 推断是否为边缘上的点
    					    ddata->r, ddata->c, curv_thr ) )
    		      {
    			cvSeqPush( features, feat );          //是特征点进入特征点序列
    		      }
    		    else
    		      free( ddata );
    		    free( feat );
    		  }
    	      }
      
      return features;
    }

    4.1

    寻找极值点

    在DOG尺度空间上。每组有S+2层图像。每一组都从第二层開始每个像素点都要与它相邻的像素点比較,看是否比它在图像域或尺度域的全部点的值大或者小。

    与它同尺度的相邻像素点有8个,上下相邻尺度的点共同拥有2×9=18。共同拥有26个像素点。也就在一个3×3的立方体内进行。搜索的过程是第二层開始到倒数第二层结束,共检測了octave组。每组S层。

      
    /*
      Determines whether a pixel is a scale-space extremum by comparing it to it's
      3x3x3 pixel neighborhood.
    */
    static int is_extremum( IplImage*** dog_pyr, int octv, int intvl, int r, int c )
    {
      double val = pixval32f( dog_pyr[octv][intvl], r, c );
      int i, j, k;
    
      /* check for maximum */
      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;
        }
    
      /* check for minimum */
      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;
    }

    4.2

    准确定位特征点

          以上的极值点搜索是在离散空间进行的,极值点不真正意义上的极值点。通过对空间尺度函数拟合。能够得到亚像素级像素点坐标。
    尺度空间的Taylor展开式:
                                          ,当中
    求导并令其为0,得到亚像素级:
                                               
    相应的函数值为:
                                            

     
    是一个三维矢量,矢量在不论什么一个方向上的偏移量大于0.5时,意味着已经偏离了原像素点,这种特征坐标位置须要更新或者继续插值计算。算法实现过程中,为了保证插值可以收敛,设置了最大插值次数(lowe 设置了5次)。

    同一时候当时(本文阈值採用了0.04/S) ,特征点才被保留,由于响应值过小的点。easy受噪声的干扰而不稳定。

    对离散空间进行函数拟合(插值):
    /*
      Performs one step of extremum interpolation.  Based on Eqn. (3) in Lowe's
      paper.
    
      r,c 为特征点位置,xi,xr,xc,保存三个方向的偏移量
    */
    
    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 };
      
      dD = deriv_3D( dog_pyr, octv, intvl, r, c );      //计算三个方向的梯度
      H = hessian_3D( dog_pyr, octv, intvl, r, c );    // 计算3维空间的hessian矩阵
      H_inv = cvCreateMat( 3, 3, CV_64FC1 );
      cvInvert( H, H_inv, CV_SVD );           //计算逆矩阵
      cvInitMatHeader( &X, 3, 1, CV_64FC1, x, CV_AUTOSTEP );
      cvGEMM( H_inv, dD, -1, NULL, 0, &X, 0 );   //广义乘法 
      
      cvReleaseMat( &dD );
      cvReleaseMat( &H );
      cvReleaseMat( &H_inv );
    
      *xi = x[2];
      *xr = x[1];
      *xc = x[0];
    }
    /*
      Interpolates a scale-space extremum's location and scale to subpixel
      accuracy to form an image feature. 
    */
    static struct feature* interp_extremum( IplImage*** dog_pyr, int octv,          //通过拟合求取准确的特征点位置
    					int intvl, int r, int c, int intvls,
    					double contr_thr )
    {
      struct feature* feat;
      struct detection_data* ddata;
      double xi, xr, xc, contr;
      int i = 0;
      
      while( i < SIFT_MAX_INTERP_STEPS )   //在最大迭代次数范围内进行
        {
          interp_step( dog_pyr, octv, intvl, r, c, &xi, &xr, &xc );          //插值后得到的三个方向的偏移量(xi,xr,xc)
          if( ABS( xi ) < 0.5  &&  ABS( xr ) < 0.5  &&  ABS( xc ) < 0.5 )
    	break;
          
          c += cvRound( xc );    //更新位置
          r += cvRound( xr );
          intvl += cvRound( xi );
          
          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++;
        }
      
      /* ensure convergence of interpolation */
      if( i >= SIFT_MAX_INTERP_STEPS )   
        return NULL;
      
      contr = interp_contr( dog_pyr, octv, intvl, r, c, xi, xr, xc );     //计算插值后相应的函数值
      if( ABS( contr ) < contr_thr / intvls )   //小于阈值(0.04/S)的点。则丢弃
        return NULL;
    
      feat = new_feature();
      ddata = feat_detection_data( feat );
      feat->img_pt.x = feat->x = ( c + xc ) * pow( 2.0, 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;              // 层方向的偏移量
    
      return feat;
    }


    4.3

    删除边缘效应

    为了得到稳定的特征点。要删除掉落在图像边缘上的点。

    一个落在边缘上的点。能够依据主曲率计算推断。主曲率能够通过2维的 Hessian矩阵求出;


    在边缘上的点,必然使得Hessian矩阵的两个特征值相差比較大。而特征值与矩阵元素有下面关系;

    令α=rβ ,所以有:

    我们能够推断上述公式的比值大小,大于阈值(lowe採用 r =10)的点排除。

    static int is_too_edge_like( IplImage* dog_img, int r, int c, int curv_thr )
    {
      double d, dxx, dyy, dxy, tr, det;
    
      /* principal curvatures are computed using the trace and det of Hessian */            
      d = pixval32f(dog_img, r, c);                                                                             //计算Hessian 矩阵内的4个元素值
      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;
    
      if( tr * tr / det < ( curv_thr + 1.0 )*( curv_thr + 1.0 ) / curv_thr )   //比值小于阈值的特征点被保留  curv_thr = 10
        return 0;
      return 1;
    }

     5.计算特征点相应的尺度

    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++ )
        {
          feat = CV_GET_SEQ_ELEM( struct feature, features, i );
          ddata = feat_detection_data( feat );
          intvl = ddata->intvl + ddata->subintvl;                        
          feat->scl = sigma * pow( 2.0, ddata->octv + intvl / intvls );      // feat->scl 保存特征点在整体上尺度
          ddata->scl_octv = sigma * pow( 2.0, intvl / intvls );     // feat->feature_data->scl__octv 保存特征点在组内的尺度,用来以下计算方向角
        }
    }


     6.为特征点分配方向角

    这部分包含:计算邻域内梯度直方图,平滑直方图,复制特征点(有辅方向的特征点)
    static void calc_feature_oris( CvSeq* features, IplImage*** gauss_pyr )  
    {
      struct feature* feat;
      struct detection_data* ddata;
      double* hist;
      double omax;
      int i, j, n = features->total;
    
      for( i = 0; i < n; i++ )
        {
          feat = malloc( sizeof( struct feature ) );
          cvSeqPopFront( features, feat );
          ddata = feat_detection_data( feat );
          hist = ori_hist( gauss_pyr[ddata->octv][ddata->intvl],     // 计算邻域内的梯度直方图,邻域半径radius = 3*1.5*sigma;  高斯加权系数= 1.5 *sigma 
    		       ddata->r, ddata->c, SIFT_ORI_HIST_BINS,
    		       cvRound( SIFT_ORI_RADIUS * ddata->scl_octv ),
    		       SIFT_ORI_SIG_FCTR * ddata->scl_octv );
          for( j = 0; j < SIFT_ORI_SMOOTH_PASSES; j++ )
    	smooth_ori_hist( hist, SIFT_ORI_HIST_BINS );    // 对直方图平滑
          omax = dominant_ori( hist, SIFT_ORI_HIST_BINS ); // 返回直方图的主方向
          add_good_ori_features( features, hist, SIFT_ORI_HIST_BINS,//大于主方向的80%为辅方向
    			     omax * SIFT_ORI_PEAK_RATIO, feat );
          free( ddata );
          free( feat );
          free( hist );
        }
    }

    6.1

    建立特征点邻域内的直方图

    上一步scl_octv保存了每一个特征点所在的组内尺度。以下计算特征点所在尺度内的高斯图像,以3×1.5×scl_octv为半径的区域内的全部像素点的梯度幅值与幅角;
    计算公式:

    在计算全然部特征点的幅值与幅角后。使用直方图统计。直方图横轴为梯度方向角,纵轴为相应幅值的累加值(与权重),梯度方向范围为0~360度,划分为36个bin,每一个bin的宽度为10。

    下图描写叙述的划分为8个bin,每一个bin的宽度为45的效果图:

    其次。每一个被增加直方图的幅值,要进行权重处理,权重也是採用高斯加权函数。当中高斯系数为1.5×scl_octv。通过高斯加权使特征点附近的点有较大的权重,能够弥补部分因没有仿射不变性而产生的不稳定问题;
    即每一个bin值按以下的公式累加,mag是幅值,后面为权重;i,j,为偏离特征点距离:

    程序上能够帮助你理解上面的概念:
    static double* ori_hist( IplImage* img, int r, int c, int n, int rad,
    			 double sigma )
    {
      double* hist;
      double mag, ori, w, exp_denom, PI2 = CV_PI * 2.0;
      int bin, i, j;
    
      hist = calloc( n, sizeof( double ) );
      exp_denom = 2.0 * sigma * sigma;
      for( i = -rad; i <= rad; i++ )
        for( j = -rad; j <= rad; j++ )
          if( calc_grad_mag_ori( img, r + i, c + j, &mag, &ori ) )    //计算领域像素点的梯度幅值mag与方向ori
    	{
    	  w = exp( -( i*i + j*j ) / exp_denom );                    //高斯权重
    	  bin = cvRound( n * ( ori + CV_PI ) / PI2 );
    	  bin = ( bin < n )?

    bin : 0; hist[bin] += w * mag; //直方图上累加 } return hist; //返回累加完毕的直方图 }

    6.2

    平滑直方图

    lowe建议对直方图进行平滑,降低突变的影响。
    static void smooth_ori_hist( double* hist, int n )
    {
      double prev, tmp, h0 = hist[0];
      int i;
    
      prev = hist[n-1];
      for( i = 0; i < n; i++ )
        {
          tmp = hist[i];
          hist[i] = 0.25 * prev + 0.5 * hist[i] + 
    	0.25 * ( ( i+1 == n )? h0 : hist[i+1] );
          prev = tmp;
        }
    }
    6.3

    复制特征点

    在上面的直方图上,我们已经找到了特征点主方向的峰值omax,当存在还有一个大于主峰值80%的峰值时,将这个方向作为特征点的辅方向,即一个特征点有多个方向,这能够增强匹配的鲁棒性。在算法上,即把该特征点复制多份作为新的特征点。新特征点的方向为这些辅方向,其它属性保持一致。

    static void add_good_ori_features( CvSeq* features, double* hist, int n,
    				   double mag_thr, struct feature* feat )
    {
      struct feature* new_feat;
      double bin, PI2 = CV_PI * 2.0;
      int l, r, i;
    
      for( i = 0; i < n; i++ )                   //检查全部的方向
        {
          l = ( i == 0 )?

    n - 1 : i-1; r = ( i + 1 ) % n; if( hist[i] > hist[l] && hist[i] > hist[r] && hist[i] >= mag_thr ) //推断是不是幅方向 { bin = i + interp_hist_peak( hist[l], hist[i], hist[r] ); //插值离散处理。取得更精确的方向 bin = ( bin < 0 )? n + bin : ( bin >= n )?

    bin - n : bin; new_feat = clone_feature( feat ); //复制特征点 new_feat->ori = ( ( PI2 * bin ) / n ) - CV_PI;// 为特征点方向赋值[-180,180] cvSeqPush( features, new_feat ); // free( new_feat ); } } }

     7.构建特征描写叙述子

    眼下每一个特征点具有属性有位置、方向、尺度三个信息,如今要用一个向量去描写叙述这个特征点,使其具有高度的唯一特征性。

    1.lowe採用了把特征点邻域划分成 d×d (lowe建议d=4) 个子区域,然后再统计每一个子区域的方向直方图(8个方向),直方图横轴有8个bin,纵轴为梯度幅值(×权重)的累加。这样描写叙述这个特征点的向量为4×4×8=128维。每一个子区域的宽度建议为3×octv,octv为组内的尺度。考虑到插值问题。特征点的邻域范围边长为3×octv×(d+1)。考虑到旋转问题,邻域的范围边长为3×octv×(d+1)×sqrt(2)。最后半径为:

    2.把坐标系旋转到主方向位置。再次统计邻域内全部像素点的梯度幅值与方向,计算所在子区域。并把幅值×权重累加到这个子区域的直方图上。
    算法上即统计每一个邻域的方向直方图时。所有是相对于这个特征点的主方向的方向。

    假设主方向为30度,某个像素点的梯度方向为50度。这时统计到该子区域直方图上就成了20度。同一时候因为旋转,这时权重也必须是按旋转后的坐标。


    计算所在的子区域的位置:
      
    权重按高斯加权函数。系数为描写叙述子宽度的一半,即0.5d:
    static double*** descr_hist( IplImage* img, int r, int c, double ori,
    			     double scl, int d, int n )
    {
      double*** hist;
      double cos_t, sin_t, hist_width, exp_denom, r_rot, c_rot, grad_mag,
        grad_ori, w, rbin, cbin, obin, bins_per_rad, PI2 = 2.0 * CV_PI;
      int radius, i, j;
    
      hist = calloc( d, sizeof( double** ) );
      for( i = 0; i < d; i++ )
        {
          hist[i] = calloc( d, sizeof( double* ) );
          for( j = 0; j < d; j++ )
    	hist[i][j] = calloc( n, sizeof( double ) );
        }
      
      cos_t = cos( ori );
      sin_t = sin( ori );
      bins_per_rad = n / PI2;
      exp_denom = d * d * 0.5;
      hist_width = SIFT_DESCR_SCL_FCTR * scl;
      radius = hist_width * sqrt(2) * ( d + 1.0 ) * 0.5 + 0.5;   //计算邻域范围半径,+0.5为了取得很多其它元素
      for( i = -radius; i <= radius; i++ )
        for( j = -radius; j <= radius; j++ )
          {
    	/*
    	  Calculate sample's histogram array coords rotated relative to ori.
    	  Subtract 0.5 so samples that fall e.g. in the center of row 1 (i.e.
    	  r_rot = 1.5) have full weight placed in row 1 after interpolation.
    	*/
    	c_rot = ( j * cos_t - i * sin_t ) / hist_width;              //
    	r_rot = ( j * sin_t + i * cos_t ) / hist_width;
    	rbin = r_rot + d / 2 - 0.5;                                        //旋转后相应的x``及y''
    	cbin = c_rot + d / 2 - 0.5;
    	
    	if( rbin > -1.0  &&  rbin < d  &&  cbin > -1.0  &&  cbin < d )
    	  if( calc_grad_mag_ori( img, r + i, c + j, &grad_mag, &grad_ori ))       //计算每一个像素点的梯度方向、幅值、
    	    {
    	      grad_ori -= ori;              //每一个像素点相对于特征点的梯度方向
    	      while( grad_ori < 0.0 )
    		grad_ori += PI2;
    	      while( grad_ori >= PI2 )
    		grad_ori -= PI2;
    	      
    	      obin = grad_ori * bins_per_rad;     //像素梯度方向转换成8个方向
    	      w = exp( -(c_rot * c_rot + r_rot * r_rot) / exp_denom );     //每一个子区域内像素点,相应的权重
    	      interp_hist_entry( hist, rbin, cbin, obin, grad_mag * w, d, n );   //为了减小突变影响对附近三个bin值三线性插值处理
    	    }
          }
    
      return hist;
    }
    每一个维度上bin值累加方法,即计算一个像素的幅值对于相邻的方向,以及位置的贡献,dr,dc为相邻位置,do为相邻方向
    这就是128维向量的数据,计算方法
    static void interp_hist_entry( double*** hist, double rbin, double cbin,
    			       double obin, double mag, int d, int n )
    {
      double d_r, d_c, d_o, v_r, v_c, v_o;
      double** row, * h;
      int r0, c0, o0, rb, cb, ob, r, c, o;
    
      r0 = cvFloor( rbin );
      c0 = cvFloor( cbin );
      o0 = cvFloor( obin );
      d_r = rbin - r0;
      d_c = cbin - c0;
      d_o = obin - o0;
    
      /*
        The entry is distributed into up to 8 bins.  Each entry into a bin
        is multiplied by a weight of 1 - d for each dimension, where d is the
        distance from the center value of the bin measured in bin units.
      */
      for( r = 0; r <= 1; r++ )
        {
          rb = r0 + r;
          if( rb >= 0  &&  rb < d )
    	{
    	  v_r = mag * ( ( r == 0 )? 1.0 - d_r : d_r );
    	  row = hist[rb];
    	  for( c = 0; c <= 1; c++ )
    	    {
    	      cb = c0 + c;
    	      if( cb >= 0  &&  cb < d )
    		{
    		  v_c = v_r * ( ( c == 0 )? 1.0 - d_c : d_c );
    		  h = row[cb];
    		  for( o = 0; o <= 1; o++ )
    		    {
    		      ob = ( o0 + o ) % n;
    		      v_o = v_c * ( ( o == 0 )? 1.0 - d_o : d_o );
    		      h[ob] += v_o;
    		    }
    		}
    	    }
    	}
        }
    }
    最后为了去除光照的影响。对128维向量进行归一化处理。同一时候设置门限,大于0.2的梯度幅值截断
    static void hist_to_descr( double*** hist, int d, int n, struct feature* feat )
    {
      int int_val, i, r, c, o, k = 0;
    
      for( r = 0; r < d; r++ )
        for( c = 0; c < d; c++ )
          for( o = 0; o < n; o++ )
    	feat->descr[k++] = hist[r][c][o];
    
      feat->d = k;
      normalize_descr( feat );          //向量归一化
      for( i = 0; i < k; i++ )
        if( feat->descr[i] > SIFT_DESCR_MAG_THR )   //设置门限,门限为0.2
          feat->descr[i] = SIFT_DESCR_MAG_THR;
      normalize_descr( feat );      //向量归一化
    
      /* convert floating-point descriptor to integer valued descriptor */
      for( i = 0; i < k; i++ )              //换成整形值
        {
          int_val = SIFT_INT_DESCR_FCTR * feat->descr[i];    
          feat->descr[i] = MIN( 255, int_val );
        }
    }

    最后对特征点按尺度大小进行排序,强特征点放在前面;
    这样每一个特征点就相应一个128维的向量,接下来能够用能够用向量做以后的匹配工作了。

    特征点匹配原理后序文章会更新~

    ------------------------------------------------------------------------------------

    在此很感谢CSDN上几位图像上的大牛,我也是通过他们的文章去学习研究的,本文也是參考了他们的文章才写成

    推荐看大牛们的文章。原理写的非常好!

    http://blog.csdn.net/abcjennifer/article/details/7639681

    http://blog.csdn.net/zddblog/article/details/7521424

    http://blog.csdn.net/chen825919148/article/details/7685952

    http://blog.csdn.net/xiaowei_cqu/article/details/8069548

  • 相关阅读:
    109 01 Android 零基础入门 02 Java面向对象 03 综合案例(学生信息管理) 03 新增功能及实现 05 问题解析--通过一个方法完成学生和专业的双向关联
    108 01 Android 零基础入门 02 Java面向对象 03 综合案例(学生信息管理) 03 新增功能及实现 04 问题解析--数组未实例化造成的空指针异常
    107 01 Android 零基础入门 02 Java面向对象 03 综合案例(学生信息管理) 03 新增功能及实现 03 编写方法完成学生个数统计功能
    106 01 Android 零基础入门 02 Java面向对象 03 综合案例(学生信息管理) 03 新增功能及实现 02 新增属性完成学生信息存储
    105 01 Android 零基础入门 02 Java面向对象 03 综合案例(学生信息管理) 03 新增功能及实现 01 新增需求及分析
    session与cookie的区别和联系
    session和cookie的区别
    Web服务器主动推送技术
    webSocket的场景应用
    TCP、Http和Socket 优劣比较
  • 原文地址:https://www.cnblogs.com/liguangsunls/p/6785878.html
Copyright © 2011-2022 走看看