【图像特征提取12】OpenCv的SIFT图像局部特征提取描述算法C++代码的实现

SIFT:尺度不变特征变换匹配算法详解

本章将要讲解的内容如下所示:

   1)SIFT算法的简介

   2)SIFT算法的实现细节

   3)SIFT算法的应用领域

   4)SIFT算法的改进与扩展

 SIFT算法的简介

  1)传统的特征提取方法

成像匹配的核心问题是:将同一目标在不同时间、不同分辨率、不同光照、不同位姿情况下所成的像相对应。传统的匹配算法往往是直接提取角点或者边缘,对环境中的适应能力较差,急需提出一种棒性强、能够适应不同光照、不同位姿等情况下的有效识别目标的方法。

  2SIFT算法提出的意义

1999年,British Columbia大学大卫.劳伊教授总结了现有的基于不变量技术特征检测方法,并正式提出一种基于尺度空间的、对图像缩放、旋转甚至仿射变换保持不变性的图像局部特征描述算子--SIFT(尺度不变特征变换算法),这种算法在2004年被加以完善。

一副图像映射为一个局部特征向量集特征向量具有平移缩放旋转不变性,同时对光照变化、仿射以及投影也具有一定的不变性。

  3SIFT算法的特点

1)SIFT特征是图像的局部特征,其对旋转、尺度缩放、亮度变化保持不变性,对视角变化、仿射变换、也保持一定程度的稳定性。

2)独特性好,信息量丰富,适用于在海量特征数据库中进行快速、准确的匹配。

3)可扩展性,可以很方便的与其他形式的特征向量进行联合。

  4)SIFT算法可以解决的问题

目标的自身状态、场景所处的环境和成像器材的成像特性等因素影响图像配准/目标识别跟踪的性能。而SIFT算法在一定程度上可以解决:

1)目标的旋转、缩放、平移(RST)

2)图像仿射/投影变换

3)光照影响

4)目标遮挡

5)杂物场景

6)噪声

5)SIFT算法实现步骤简述

   SIFT算法的实质可以归为在不同尺度空间上查找特征点(关键点)的问题

1)原图像---->特征点检测------->特征点描述----->目标的特征点集

2)目标图像-->特征点检测------->特征点描述----->目标的特征点集

3)两幅图片的特征点集合进行:特征点匹配-------->匹配点矫正

   SIFT算法实现物体的识别主要有三大工序

1)提取关键点

2)对关键点附加详细的信息(局部特征),也就是所谓的描述器

3)通过两方的特征点(附带上特征向量的关键点)的两两比较找出相互匹配的若干对特征点,也就建立了景物间的对应关系。

6)SIFT算法实现步骤

1)关键点检测

2)关键点描述

3)关键点匹配

4)消除错匹配点

[cpp]  view plain  copy
 
  1. /************************************************************************************************************************* 
  2. *文件说明: 
  3. *        SIFT算法的实现头文件 
  4. *开发环境: 
  5. *        Win10+VS2012+OpenCv2.4.8 
  6. *时间地点: 
  7. *        陕西师范大学.文津楼 2016.12.30 
  8. *再次修改时间: 
  9. *        陕西师范大学.文津楼 2017.2.24 
  10. *作者信息: 
  11. *        九月 
  12. **************************************************************************************************************************/  
  13. #ifndef SIFT_H  
  14. #define SIFT_H  
  15.   
  16. #include   
  17. #include   
  18. #include   
  19.   
  20. using namespace cv;  
  21.   
  22. typedef double pixel_t;                             //【1】像素类型  
  23.   
  24. #define INIT_SIGMA 0.5                               //【2】初始sigma  
  25. #define SIGMA 1.6  
  26. #define INTERVALS 3                                  //【3】高斯金字塔中每组图像中有三层/张图片  
  27.   
  28. #define RATIO 10                                     //【4】半径r  
  29. #define MAX_INTERPOLATION_STEPS 5                    //【5】最大空间间隔  
  30. #define DXTHRESHOLD 0.03                             //【6】|D(x^)| < 0.03   0.03极值点太多  
  31.   
  32. #define ORI_HIST_BINS 36                             //【7】bings=36  
  33. #define ORI_SIGMA_TIMES 1.5  
  34. #define ORI_WINDOW_RADIUS 3.0 * ORI_SIGMA_TIMES   
  35. #define ORI_SMOOTH_TIMES 2  
  36. #define ORI_PEAK_RATIO 0.8  
  37. #define FEATURE_ELEMENT_LENGTH 128  
  38. #define DESCR_HIST_BINS 8  
  39. #define IMG_BORDER 5   
  40. #define DESCR_WINDOW_WIDTH 4  
  41. #define DESCR_SCALE_ADJUST 3  
  42. #define DESCR_MAG_THR 0.2  
  43. #define INT_DESCR_FCTR 512.0  
  44. /********************************************************************************************* 
  45. *模块说明: 
  46. *        关键点/特征点的结构体声明 
  47. *注意点1: 
  48. *        在高斯金字塔构建的过程中,一幅图像可以产生好几组(octave)图像,一组图像包含几层(inteval) 
  49. *        图像 
  50. *********************************************************************************************/  
  51. struct Keypoint  
  52. {  
  53.     int    octave;                                        //【1】关键点所在组  
  54.     int    interval;                                      //【2】关键点所在层  
  55.     double offset_interval;                               //【3】调整后的层的增量  
  56.   
  57.     int    x;                                             //【4】x,y坐标,根据octave和interval可取的层内图像  
  58.     int    y;  
  59.     double scale;                                         //【5】空间尺度坐标scale = sigma0*pow(2.0, o+s/S)  
  60.       
  61.     double dx;                                            //【6】特征点坐标,该坐标被缩放成原图像大小   
  62.     double dy;  
  63.   
  64.     double offset_x;  
  65.     double offset_y;  
  66.   
  67.     //============================================================  
  68.     //1---高斯金字塔组内各层尺度坐标,不同组的相同层的sigma值相同  
  69.     //2---关键点所在组的组内尺度  
  70.     //============================================================  
  71.     double octave_scale;                                  //【1】offset_i;  
  72.     double ori;                                           //【2】方向  
  73.     int    descr_length;  
  74.     double descriptor[FEATURE_ELEMENT_LENGTH];            //【3】特征点描述符              
  75.     double val;                                           //【4】极值  
  76. };  
  77. /********************************************************************************* 
  78. *模块说明: 
  79. *        SIFT算法内,所有成员函数的声明 
  80. *********************************************************************************/  
  81. void read_features(Vector&features, const char*file);  
  82. void write_features(const Vector&features, const char*file);  
  83.   
  84. void testInverse3D();  
  85.   
  86. void write_pyr(const Vector& pyr, const char* dir);  
  87. void DrawKeyPoints(Mat &src, Vector& keypoints);  
  88.   
  89. const char* GetFileName(const char* dir, int i);  
  90.   
  91. void ConvertToGray(const Mat& src, Mat& dst);  
  92. void DownSample(const Mat& src, Mat& dst);  
  93. void UpSample(const Mat& src, Mat& dst);  
  94.   
  95. void GaussianTemplateSmooth(const Mat &src, Mat &dst, double);  
  96. void GaussianSmooth2D(const Mat &src, Mat &dst, double sigma);  
  97. void GaussianSmooth(const Mat &src, Mat &dst, double sigma);  
  98.   
  99. void Sift(const Mat &src, Vector& features, double sigma=SIGMA, int intervals=INTERVALS);  
  100.   
  101. void CreateInitSmoothGray(const Mat &src, Mat &dst, double);  
  102. void GaussianPyramid(const Mat &src, Vector&gauss_pyr, int octaves, int intervals, double sigma);  
  103.   
  104. void Sub(const Mat& a, const Mat& b, Mat & c);  
  105.   
  106. void DogPyramid(const Vector& gauss_pyr, Vector& dog_pyr, int octaves, int intervals);  
  107. void DetectionLocalExtrema(const Vector& dog_pyr, Vector& extrema, int octaves, int intervals);  
  108. void DrawSiftFeatures(Mat& src, Vector& features);  
  109.   
  110. #endif  

[cpp]  view plain  copy
 
  1. /************************************************************************************************************************* 
  2. *文件说明: 
  3. *        SIFT算法的头文件所对应的实现文件 
  4. *开发环境: 
  5. *        Win10+VS2012+OpenCv2.4.8 
  6. *时间地点: 
  7. *        陕西师范大学.文津楼 2016.12.30 
  8. *再次修改时间: 
  9. *        陕西师范大学.文津楼 2017.2.24 
  10. *作者信息: 
  11. *        九月 
  12. **************************************************************************************************************************/  
  13. #include"sift.h"  
  14. #include  
  15. #include  
  16.   
  17. using namespace std;  
  18. using namespace cv;  
  19. /************************************************************************************************************************* 
  20. *模块说明: 
  21. *        图像灰度化函数----将彩色图像转为灰度图像 
  22. **************************************************************************************************************************/  
  23. void ConvertToGray(const Mat& src,Mat& dst)  
  24. {  
  25.     cv::Size size = src.size();  
  26.     if(dst.empty())  
  27.         dst.create(size,CV_64F);                              //[1]利用Mat类的成员函数创建Mat容器  
  28.       
  29.     uchar*    srcData = src.data;                             //[2]指向存储所有像素值的矩阵的数据区域  
  30.     pixel_t*  dstData = (pixel_t*)dst.data;                   //[3]指向dst的矩阵体数据区域  
  31.       
  32.     int       dstStep = dst.step/sizeof(dstData[0]);         //[4]一行图像含有多少字节数  
  33.   
  34.     for(int j=0;j
  35.     {  
  36.         for(int i=0;i
  37.         {  
  38.             double b = (srcData+src.step*i+src.channels()*j)[0]/255.0;  
  39.             double g = (srcData+src.step*i+src.channels()*j)[1]/255.0;  
  40.             double r = (srcData+src.step*i+src.channels()*j)[2]/255.0;  
  41.             *(dstData+dstStep*i+dst.channels()*j) = (r+g+b)/3.0;  
  42.         }  
  43.     }  
  44. }  
  45. /************************************************************************************************************************* 
  46. *模块说明: 
  47. *        隔点采样 
  48. **************************************************************************************************************************/  
  49. void DownSample(const Mat& src, Mat& dst)  
  50. {  
  51.     if(src.channels() != 1)  
  52.         return;  
  53.   
  54.     if(src.cols <=1 || src.rows <=1)  
  55.     {  
  56.         src.copyTo(dst);  
  57.         return;  
  58.     }  
  59.   
  60.     dst.create((int)(src.rows/2), (int)(src.cols/2), src.type());  
  61.   
  62.     pixel_t* srcData = (pixel_t*)src.data;  
  63.     pixel_t* dstData = (pixel_t*)dst.data;  
  64.   
  65.     int srcStep = src.step/sizeof(srcData[0]);  
  66.     int dstStep = dst.step/sizeof(dstData[0]);  
  67.   
  68.     int m = 0, n = 0;  
  69.     for(int j = 0; j < src.cols; j+=2, n++)  
  70.     {  
  71.         m = 0;  
  72.         for(int i = 0; i < src.rows; i+=2, m++)  
  73.         {  
  74.             pixel_t sample = *(srcData + srcStep * i + src.channels() * j);  
  75.             if(m < dst.rows && n < dst.cols)  
  76.             {  
  77.                 *(dstData + dstStep * m + dst.channels() * n) = sample;  
  78.             }  
  79.         }  
  80.     }  
  81.   
  82. }  
  83. /************************************************************************************************************************* 
  84. *模块说明: 
  85. *        线性插值放大 
  86. **************************************************************************************************************************/  
  87. void UpSample(const Mat &src, Mat &dst)  
  88. {  
  89.     if(src.channels() != 1)  
  90.         return;  
  91.     dst.create(src.rows*2, src.cols*2, src.type());  
  92.   
  93.     pixel_t* srcData = (pixel_t*)src.data;  
  94.     pixel_t* dstData = (pixel_t*)dst.data;  
  95.   
  96.     int srcStep = src.step/sizeof(srcData[0]);  
  97.     int dstStep = dst.step/sizeof(dstData[0]);  
  98.   
  99.     int m = 0, n = 0;  
  100.     for(int j = 0; j < src.cols-1; j++, n+=2)  
  101.     {  
  102.         m = 0;  
  103.         for(int i = 0; i < src.rows-1; i++, m+=2)  
  104.         {  
  105.             double sample = *(srcData + srcStep * i + src.channels() * j);  
  106.             *(dstData + dstStep * m + dst.channels() * n) = sample;  
  107.               
  108.             double rs = *(srcData + srcStep * (i) + src.channels()*j)+(*(srcData + srcStep * (i+1) + src.channels()*j));  
  109.             *(dstData + dstStep * (m+1) + dst.channels() * n) = rs/2;  
  110.             double cs = *(srcData + srcStep * i + src.channels()*(j))+(*(srcData + srcStep * i + src.channels()*(j+1)));  
  111.             *(dstData + dstStep * m + dst.channels() * (n+1)) = cs/2;  
  112.   
  113.             double center = (*(srcData + srcStep * (i+1) + src.channels() * j))  
  114.                         + (*(srcData + srcStep * i + src.channels() * j))  
  115.                         + (*(srcData + srcStep * (i+1) + src.channels() * (j+1)))  
  116.                         + (*(srcData + srcStep * i + src.channels() * (j+1)));  
  117.   
  118.             *(dstData + dstStep * (m+1) + dst.channels() * (n+1)) = center/4;  
  119.   
  120.         }  
  121.   
  122.     }  
  123.   
  124.   
  125.   
  126.     if(dst.rows < 3 || dst.cols < 3)  
  127.         return;  
  128.   
  129.     //最后两行两列  
  130.     for(int k = dst.rows-1; k >=0; k--)  
  131.     {  
  132.         *(dstData + dstStep *(k) + dst.channels()*(dst.cols-2))=*(dstData + dstStep *(k) + dst.channels()*(dst.cols-3));  
  133.         *(dstData + dstStep *(k) + dst.channels()*(dst.cols-1))=*(dstData + dstStep *(k) + dst.channels()*(dst.cols-3));  
  134.     }  
  135.     for(int k = dst.cols-1; k >=0; k--)  
  136.     {  
  137.         *(dstData + dstStep *(dst.rows-2) + dst.channels()*(k))=*(dstData + dstStep *(dst.rows-3) + dst.channels()*(k));  
  138.         *(dstData + dstStep *(dst.rows-1) + dst.channels()*(k))=*(dstData + dstStep *(dst.rows-3) + dst.channels()*(k));  
  139.     }  
  140.   
  141. }  
  142.   
  143. /************************************************************************************************************************* 
  144. *模块说明: 
  145. *        OpenCv中的高斯平滑函数 
  146. **************************************************************************************************************************/  
  147. void GaussianSmooth(const Mat &src, Mat &dst, double sigma)  
  148. {   
  149.     GaussianBlur(src, dst, Size(0,0), sigma, sigma);   
  150. }  
  151. /************************************************************************************************************************* 
  152. *模块说明: 
  153. *        模块1--创建初始灰度图像------初始图像先将原图像灰度化,再扩大一倍后,使用高斯模糊平滑 
  154. *功能说明: 
  155. *        在最开始建立高斯金字塔的时候,要预先模糊输入的图像来作为第0个组的第0层图像,这时相当于丢弃了最高的空域的采样率。因 
  156. *        此,通常的做法是先将图像的尺度扩大一倍来生成第-1组。我们假定初始的输入图像为了抗击混淆现象,已经对其进行了sigma=0.5 
  157. *        的高斯模糊,如果输入图像的尺寸用双线性插值扩大一倍,那么相当于sigma=1.0 
  158. *这样做的原因: 
  159. *        在检测极值点前,对原始图像的高斯平滑以致图像丢失高频信息,所以Lowe建议在建立尺度空间前,首先对原始图像长宽扩展一倍, 
  160. *        以便保留原始图像的信息(特别是图像的高频信息,比如边缘,角点),增加特征点的数量。 
  161. *函数功能: 
  162. *        这个函数的作用是用于创建高斯金字塔的-1层图像 
  163. *代码解读: 
  164. *        2017.2.24----陕西师范大学 
  165. **************************************************************************************************************************/  
  166. void CreateInitSmoothGray(const Mat &src, Mat &dst, double sigma = SIGMA)  
  167. {  
  168.     cv::Mat gray;                                  //[1]保存原始图像的灰度图像          
  169.     cv::Mat up;                                    //[2]保存原始图像的上采样图像  
  170.       
  171.     ConvertToGray(src, gray);  
  172.     UpSample(gray, up);                            //[3]图像的上采样  
  173.                                                    //[4]高斯金字塔的-1组的sigma的计算公式  
  174.     double  sigma_init = sqrt(sigma * sigma - (INIT_SIGMA * 2) * (INIT_SIGMA * 2));//[1]-1层的sigma  
  175.   
  176.     GaussianSmooth(up, dst, sigma_init);           //[5]高斯平滑  
  177. }  
  178. /************************************************************************************************************************* 
  179. *模块说明: 
  180. *        模块二:3.3 图像高斯金字塔的构建 
  181. **************************************************************************************************************************/  
  182. void GaussianPyramid(const Mat &src, vector&gauss_pyr, int octaves, int intervals = INTERVALS, double sigma = SIGMA)  
  183. {  
  184.     double *sigmas = new double[intervals+3];  
  185.     double k = pow(2.0, 1.0/intervals);  
  186.   
  187.     sigmas[0] = sigma;  
  188.   
  189.     double sig_prev;  
  190.     double sig_total;  
  191.   
  192.     for(int i = 1; i < intervals + 3; i++ )  
  193.     {  
  194.         sig_prev = pow( k, i - 1 ) * sigma;  
  195.         sig_total = sig_prev * k;  
  196.         sigmas[i] = sqrt( sig_total * sig_total - sig_prev * sig_prev );  
  197.     }  
  198.   
  199.     for(int o = 0; o < octaves; o++)  
  200.     {  
  201.         //每组多三层  
  202.         for(int i = 0; i < intervals+3; i++)  
  203.         {  
  204.             Mat mat;  
  205.             if(o == 0 && i == 0)  
  206.             {  
  207.                 src.copyTo(mat);  
  208.             }  
  209.             else if(i == 0)  
  210.             {  
  211.                 DownSample(gauss_pyr[(o-1)*(intervals+3)+intervals], mat);  
  212.             }  
  213.             else  
  214.             {  
  215.                 GaussianSmooth(gauss_pyr[o * (intervals+3)+i-1], mat, sigmas[i]);  
  216.             }  
  217.             gauss_pyr.push_back(mat);  
  218.         }  
  219.     }  
  220.       
  221.     delete[] sigmas;  
  222. }  
  223. /************************************************************************************************************************* 
  224. *模块说明: 
  225. *        图像的差分 
  226. **************************************************************************************************************************/  
  227. void Sub(const Mat& a, const Mat& b, Mat & c)  
  228. {  
  229.     if(a.rows != b.rows || a.cols != b.cols || a.type() != b.type())  
  230.         return;  
  231.     if(!c.empty())  
  232.         return;  
  233.     c.create(a.size(), a.type());  
  234.   
  235.     pixel_t* ap = (pixel_t*)a.data;  
  236.     pixel_t* ap_end = (pixel_t*)a.dataend;  
  237.     pixel_t* bp = (pixel_t*)b.data;  
  238.     pixel_t* cp = (pixel_t*)c.data;  
  239.     int step = a.step/sizeof(pixel_t);  
  240.   
  241.     while(ap != ap_end)  
  242.     {  
  243.         *cp++ = *ap++ - *bp++;  
  244.     }  
  245. }  
  246. /************************************************************************************************************************* 
  247. *模块说明: 
  248. *       模块三:3.4 高斯差分金字塔 
  249. *功能说明: 
  250. *       1--2002年,Mikolajczyk在详细的实验比较中发现尺度归一化的高斯拉普拉斯函数的极大值和极小值同其他的特征提取函数,例如: 
  251. *          梯度,Hessian或者Harris角点特征比较,能够产生稳定的图像特征。 
  252. *       2--而Lindberg早在1994年发现高斯差分函数(Difference of Gaussian,简称DOG算子)与尺度归一化的拉普拉斯函数非常相似,因此, 
  253. *          利用图像间的差分代替微分。 
  254. **************************************************************************************************************************/  
  255. void DogPyramid(const Vector& gauss_pyr, Vector& dog_pyr, int octaves, int intervals=INTERVALS)  
  256. {  
  257.     for(int o = 0; o < octaves; o ++)  
  258.     {  
  259.         for(int i = 1; i < intervals+3; i++)  
  260.         {  
  261.             Mat mat;  
  262.             Sub(gauss_pyr[o*(intervals+3)+i], gauss_pyr[o*(intervals+3)+i-1], mat);  
  263.             dog_pyr.push_back(mat);  
  264.         }  
  265.     }  
  266. }  
  267. /************************************************************************************************************************* 
  268. *模块说明: 
  269. *       模块四的第一步:3.4-空间极值点的初步检测(关键点的初步探查) 
  270. *功能说明: 
  271. *       1--在高斯差分函数之后,得到一系列的关键点的疑似点,我们需要对这些关键点的疑似点初步进行检测和筛选 
  272. *       2--此块代码所根据的原理为CSDN博客中的:3.5空间极值点的检测  
  273. **************************************************************************************************************************/  
  274. bool isExtremum(int x, int y, const Vector& dog_pyr, int index)  
  275. {  
  276.     pixel_t * data = (pixel_t *)dog_pyr[index].data;  
  277.     int      step = dog_pyr[index].step/sizeof(data[0]);  
  278.     pixel_t   val  = *(data+y*step+x);  
  279.   
  280.     if(val > 0)  
  281.     {  
  282.         for(int i = -1; i <= 1; i++)  
  283.         {  
  284.             int stp = dog_pyr[index+i].step/sizeof(data[0]);  
  285.             for(int j = -1; j <= 1; j++)  
  286.             {  
  287.                 for(int k = -1; k <= 1; k++)  
  288.                 {  
  289.                     if( val < *((pixel_t*)dog_pyr[index+i].data+stp*(y+j)+(x+k)))  
  290.                     {  
  291.                         return false;  
  292.                     }  
  293.                 }  
  294.             }  
  295.         }  
  296.     }  
  297.     else  
  298.     {  
  299.         for(int i = -1; i <= 1; i++)  
  300.         {  
  301.             int stp = dog_pyr[index+i].step/sizeof(pixel_t);  
  302.             for(int j = -1; j <= 1; j++)  
  303.             {  
  304.                 for(int k = -1; k <= 1; k++)  
  305.                 {  
  306.                     //检查最小极值  
  307.                     if( val > *((pixel_t*)dog_pyr[index+i].data+stp*(y+j)+(x+k)))  
  308.                     {  
  309.                         return false;  
  310.                     }  
  311.                 }  
  312.             }  
  313.         }  
  314.     }  
  315.     return true;  
  316. }  
  317. /************************************************************************************************************************* 
  318. *模块说明: 
  319. *       模块四的第三步:4.2--消除边缘响应点 
  320. *功能说明: 
  321. *       1)一个定义不好的高斯差分算子的极值在横跨边缘的地方有较大的住主曲率,在垂直边缘的方向有较小的主曲率。 
  322. *       2)DOG算子会产生较强的边缘响应,需要剔除不稳定的边缘响应点,获取特征点处的Hessian矩阵,主曲率通过一个2*2的Hessian矩 
  323. *          阵H求出 
  324. *       3)主曲率D和Hessian矩阵的特征值成正比,公式(r+1)*(r+1)/r的值在两个特征值相等时最小;这个值越大,说明两个特征值的比值 
  325. *          越大,即在某一个方向的梯度值越大,而在另一个方向的梯度值越小,而边缘恰恰就是这种情况。所以,为了剔除边缘响应点, 
  326. *          需要让该比值小于一定的阈值,因此,为了检测主曲率是否在某阈值r下,只需检测。CSDN论文中的公式(4-7成立),成立,将关 
  327. *          键点保留,反之,剔除。 
  328. **************************************************************************************************************************/  
  329. #define DAt(x, y) (*(data+(y)*step+(x)))   
  330. bool passEdgeResponse(int x, int y, const Vector& dog_pyr, int index, double r = RATIO)  
  331. {  
  332.     pixel_t *data = (pixel_t *)dog_pyr[index].data;  
  333.     int step = dog_pyr[index].step/sizeof(data[0]);  
  334.     pixel_t val = *(data+y*step+x);  
  335.   
  336.     double Dxx;  
  337.     double Dyy;  
  338.     double Dxy;  
  339.     double Tr_h;                                                         //[1]Hessian矩阵的迹  
  340.     double Det_h;                                                        //[2]Hessian矩阵所对应的行列式的值  
  341.        
  342.     Dxx = DAt(x+1,y) + DAt(x-1,y) - 2*val;  
  343.     Dyy = DAt(x,y+1) + DAt(x,y-1) - 2*val;  
  344.     Dxy = (DAt(x+1,y+1) + DAt(x-1,y-1) - DAt(x-1,y+1) - DAt(x+1,y-1))/4.0;  
  345.   
  346.     Tr_h  = Dxx + Dyy;  
  347.     Det_h = Dxx * Dyy - Dxy * Dxy;  
  348.   
  349.     if(Det_h <=0)return false;  
  350.       
  351.     if(Tr_h * Tr_h / Det_h < (r + 1) * (r + 1) / r) return true;  
  352.   
  353.     return false;  
  354. }  
  355. /************************************************************************************************************************* 
  356. *模块说明: 
  357. *       有限差分求导? 
  358. **************************************************************************************************************************/  
  359. #define Hat(i, j) (*(H+(i)*3 + (j)))  
  360.   
  361. double PyrAt(const Vector& pyr, int index, int x, int y)  
  362. {  
  363.     pixel_t *data = (pixel_t*)pyr[index].data;  
  364.     int      step = pyr[index].step/sizeof(data[0]);  
  365.     pixel_t   val = *(data+y*step+x);  
  366.       
  367.     return val;  
  368. }  
  369. /************************************************************************************************************************* 
  370. *模块说明: 
  371. *       有限差分求导? 
  372. **************************************************************************************************************************/  
  373. #define At(index, x, y) (PyrAt(dog_pyr, (index), (x), (y)))  
  374.   
  375. //3维D(x)一阶偏导,dx列向量  
  376. void DerivativeOf3D(int x, int y, const Vector& dog_pyr, int index, double *dx)  
  377. {  
  378.     double Dx = (At(index, x+1, y)-At(index, x-1, y))/2.0;  
  379.     double Dy = (At(index, x, y+1)-At(index, x, y-1))/2.0;  
  380.     double Ds = (At(index+1, x, y)-At(index-1, x, y))/2.0;  
  381.   
  382.     dx[0] = Dx;  
  383.     dx[1] = Dy;  
  384.     dx[2] = Ds;  
  385. }  
  386.   
  387. //3维D(x)二阶偏导,即Hessian矩阵  
  388. void Hessian3D(int x, int y, const Vector& dog_pyr, int index, double *H)  
  389. {  
  390.     double val, Dxx, Dyy, Dss, Dxy, Dxs, Dys;  
  391.       
  392.     val = At(index, x, y);  
  393.   
  394.     Dxx = At(index, x+1, y) + At(index, x-1, y) - 2*val;  
  395.     Dyy = At(index, x, y+1) + At(index, x, y-1) - 2*val;  
  396.     Dss = At(index+1, x, y) + At(index-1, x, y) - 2*val;  
  397.   
  398.     Dxy = (At(index, x+1, y+1) + At(index, x-1, y-1)  
  399.          - At(index, x+1, y-1) - At(index, x-1, y+1))/4.0;  
  400.   
  401.     Dxs = (At(index+1, x+1, y) + At(index-1, x-1, y)  
  402.          - At(index-1, x+1, y) - At(index+1, x-1, y))/4.0;  
  403.   
  404.     Dys = (At(index+1, x, y+1) + At(index-1, x, y-1)  
  405.          - At(index+1, x, y-1) - At(index-1, x, y+1))/4.0;  
  406.   
  407.     Hat(0, 0) = Dxx;  
  408.     Hat(1, 1) = Dyy;  
  409.     Hat(2, 2) = Dss;  
  410.   
  411.     Hat(1, 0) = Hat(0, 1) = Dxy;  
  412.     Hat(2, 0) = Hat(0 ,2) = Dxs;  
  413.     Hat(2, 1) = Hat(1, 2) = Dys;  
  414. }  
  415. /************************************************************************************************************************* 
  416. *模块说明: 
  417. *       4.4 三阶矩阵求逆 
  418. **************************************************************************************************************************/  
  419. #define HIat(i, j) (*(H_inve+(i)*3 + (j)))  
  420. //3*3阶矩阵求逆  
  421. bool Inverse3D(const double *H, double *H_inve)  
  422. {  
  423.   
  424.     double A = Hat(0, 0)*Hat(1, 1)*Hat(2, 2)   
  425.              + Hat(0, 1)*Hat(1, 2)*Hat(2, 0)  
  426.              + Hat(0, 2)*Hat(1, 0)*Hat(2, 1)  
  427.              - Hat(0, 0)*Hat(1, 2)*Hat(2, 1)  
  428.              - Hat(0, 1)*Hat(1, 0)*Hat(2, 2)  
  429.              - Hat(0, 2)*Hat(1, 1)*Hat(2, 0);  
  430.   
  431.     if(fabs(A) < 1e-10) return false;  
  432.   
  433.     HIat(0, 0) = Hat(1, 1) * Hat(2, 2) - Hat(2, 1)*Hat(1, 2);  
  434.     HIat(0, 1) = -(Hat(0, 1) * Hat(2, 2) - Hat(2, 1) * Hat(0, 2));  
  435.     HIat(0, 2) = Hat(0, 1) * Hat(1, 2) - Hat(0, 2)*Hat(1, 1);  
  436.       
  437.     HIat(1, 0) = Hat(1, 2) * Hat(2, 0) - Hat(2, 2)*Hat(1, 0);  
  438.     HIat(1, 1) = -(Hat(0, 2) * Hat(2, 0) - Hat(0, 0) * Hat(2, 2));  
  439.     HIat(1, 2) = Hat(0, 2) * Hat(1, 0) - Hat(0, 0)*Hat(1, 2);  
  440.       
  441.     HIat(2, 0) = Hat(1, 0) * Hat(2, 1) - Hat(1, 1)*Hat(2, 0);  
  442.     HIat(2, 1) = -(Hat(0, 0) * Hat(2, 1) - Hat(0, 1) * Hat(2, 0));  
  443.     HIat(2, 2) = Hat(0, 0) * Hat(1, 1) - Hat(0, 1)*Hat(1, 0);  
  444.   
  445.     for(int i = 0; i < 9; i++)  
  446.     {  
  447.         *(H_inve+i) /= A;  
  448.     }  
  449.     return true;  
  450. }  
  451. /************************************************************************************************************************* 
  452. *模块说明: 
  453. *        
  454. **************************************************************************************************************************/  
  455. //计算x^  
  456. void GetOffsetX(int x, int y, const Vector& dog_pyr, int index, double *offset_x)  
  457. {  
  458.     //x^ = -H^(-1) * dx; dx = (Dx, Dy, Ds)^T  
  459.     double H[9], H_inve[9]= {0};  
  460.     Hessian3D(x, y, dog_pyr, index, H);  
  461.     Inverse3D(H, H_inve);  
  462.     double dx[3];  
  463.     DerivativeOf3D(x, y, dog_pyr, index, dx);  
  464.       
  465.     for(int i = 0; i < 3; i ++)  
  466.     {  
  467.         offset_x[i] = 0.0;  
  468.         for(int j = 0; j < 3; j++)  
  469.         {  
  470.             offset_x[i] += H_inve[i*3 + j] * dx[j];  
  471.         }  
  472.         offset_x[i] = -offset_x[i];  
  473.     }  
  474. }  
  475.   
  476. //计算|D(x^)|  
  477. double GetFabsDx(int x, int y, const Vector& dog_pyr, int index, const double* offset_x)  
  478. {  
  479.     //|D(x^)|=D + 0.5 * dx * offset_x; dx=(Dx, Dy, Ds)^T  
  480.     double dx[3];  
  481.     DerivativeOf3D(x, y, dog_pyr, index, dx);  
  482.   
  483.     double term = 0.0;  
  484.     for(int i = 0; i < 3; i++)  
  485.         term += dx[i] * offset_x[i];  
  486.   
  487.     pixel_t *data = (pixel_t *)dog_pyr[index].data;  
  488.     int step = dog_pyr[index].step/sizeof(data[0]);  
  489.     pixel_t val = *(data+y*step+x);  
  490.   
  491.     return fabs(val + 0.5 * term);  
  492. }  
  493. /************************************************************************************************************************* 
  494. *模块说明: 
  495. *       模块四的第二步:修正极值点,删除不稳定的点 
  496. *功能说明: 
  497. *       1--根据高斯差分函数产生的极值点并不全都是稳定的特征点,因为某些极值点的响应较弱,而且DOG算子会产生较强的边缘响应 
  498. *       2--以上方法检测到的极值点是离散空间的极值点,下面通过拟合三维二次函数来精确定位关键点的位置和尺度,同时去除对比度 
  499. *          低和不稳定的边缘响应点(因为DOG算子会产生较强的边缘响应),以增强匹配的稳定性、提高抗噪声的能力。 
  500. *       3--修正极值点,删除不稳定点,|D(x)| < 0.03 Lowe 2004 
  501. **************************************************************************************************************************/  
  502. Keypoint* InterploationExtremum(int x, int y, const Vector& dog_pyr, int index, int octave, int interval, double dxthreshold = DXTHRESHOLD)  
  503. {  
  504.     //计算x=(x,y,sigma)^T  
  505.     //x^ = -H^(-1) * dx; dx = (Dx, Dy, Ds)^T  
  506.     double offset_x[3]={0};  
  507.   
  508.     const Mat &mat = dog_pyr[index];  
  509.   
  510.     int idx   = index;  
  511.     int intvl = interval;  
  512.     int i     = 0;   
  513.   
  514.     while(i < MAX_INTERPOLATION_STEPS)  
  515.     {  
  516.         GetOffsetX(x, y, dog_pyr, idx, offset_x);  
  517.         //4. Accurate keypoint localization.  Lowe  
  518.         //如果offset_x 的任一维度大于0.5,it means that the extremum lies closer to a different sample point.  
  519.         if(fabs(offset_x[0]) < 0.5 && fabs(offset_x[1]) < 0.5 && fabs(offset_x[2]) < 0.5)  
  520.             break;  
  521.   
  522.         //用周围的点代替  
  523.         x += cvRound(offset_x[0]);  
  524.         y += cvRound(offset_x[1]);  
  525.         interval += cvRound(offset_x[2]);  
  526.   
  527.         idx = index - intvl + interval;  
  528.         //此处保证检测边时 x+1,y+1和x-1, y-1有效  
  529.         if( interval < 1 || interval > INTERVALS ||x >= mat.cols-1 || x < 2 ||y >= mat.rows-1 || y < 2)    
  530.         {  
  531.             return NULL;  
  532.         }  
  533.   
  534.         i++;  
  535.     }  
  536.   
  537.     //窜改失败  
  538.     if(i >= MAX_INTERPOLATION_STEPS)  
  539.         return NULL;  
  540.   
  541.     //rejecting unstable extrema  
  542.     //|D(x^)| < 0.03取经验值  
  543.     if(GetFabsDx(x, y, dog_pyr, idx, offset_x) < dxthreshold/INTERVALS)  
  544.     {  
  545.         return NULL;  
  546.     }  
  547.   
  548.     Keypoint *keypoint = new Keypoint;  
  549.   
  550.   
  551.     keypoint->x = x;  
  552.     keypoint->y = y;  
  553.   
  554.     keypoint->offset_x        = offset_x[0];  
  555.     keypoint->offset_y        = offset_x[1];  
  556.   
  557.     keypoint->interval        = interval;  
  558.     keypoint->offset_interval = offset_x[2];  
  559.   
  560.     keypoint->octave          = octave;  
  561.   
  562.     keypoint->dx              = (x + offset_x[0])*pow(2.0, octave);  
  563.     keypoint->dy              = (y + offset_x[1])*pow(2.0, octave);  
  564.   
  565.     return keypoint;  
  566. }  
  567. /************************************************************************************************************************* 
  568. *模块说明: 
  569. *       模块四:3.5 空间极值点的检测(关键点的初步探查) 
  570. *功能说明: 
  571. *       1--关键点是由DOG空间的局部极值点组成的,关键点的初步探查是通过同一组内各DoG相邻两层图像之间的比较完成的。为了寻找DoG 
  572. *          函数的极值点,每一个像素点都要和它所有相邻的点比较,看其是否比它的图像域和尺度域相邻的点大还是小。 
  573. *       2--当然这样产生的极值点并不全都是稳定的特征点,因为某些极值点相应较弱,而且DOG算子会产生较强的边缘响应。 
  574. **************************************************************************************************************************/  
  575. void DetectionLocalExtrema(const Vector& dog_pyr, Vector& extrema, int octaves, int intervals=INTERVALS)  
  576. {  
  577.   
  578.     double  thresh = 0.5 * DXTHRESHOLD / intervals;  
  579.   
  580.     for(int o = 0; o < octaves; o++)  
  581.     {  
  582.         //第一层和最后一层极值忽略  
  583.         for(int i = 1; i < (intervals + 2) - 1; i++)  
  584.         {  
  585.             int index    = o*(intervals+2)+i;                              //[1]图片索引的定位  
  586.             pixel_t *data = (pixel_t *)dog_pyr[index].data;                //[2]获取图片的矩阵体的首地址  
  587.             int step     = dog_pyr[index].step/sizeof(data[0]);           //[3]说明矩阵在存储空间中的存储是以线性空间的方式存放的  
  588.   
  589.       
  590.             for(int y = IMG_BORDER; y < dog_pyr[index].rows-IMG_BORDER; y++)  
  591.             {  
  592.                 for(int x = IMG_BORDER; x < dog_pyr[index].cols-IMG_BORDER; x++)  
  593.                 {  
  594.                     pixel_t val = *(data+y*step+x);  
  595.                     if(std::fabs(val) > thresh )                           //[4]排除阈值过小的点  
  596.                     {         
  597.                         if(isExtremum(x,y, dog_pyr, index))                //[5]判断是否是极值  
  598.                         {  
  599.                             Keypoint *extrmum = InterploationExtremum(x, y, dog_pyr, index, o, i);  
  600.                             if(extrmum)  
  601.                             {  
  602.                                 if(passEdgeResponse(extrmum->x,extrmum->y, dog_pyr, index))  
  603.                                 {     
  604.                                     extrmum->val = *(data+extrmum->y*step+extrmum->x);  
  605.                                     extrema.push_back(*extrmum);  
  606.                                 }  
  607.   
  608.                                 delete extrmum;  
  609.       
  610.                             }//extrmum  
  611.                         }//isExtremum  
  612.                     }//std::fabs  
  613.                 }//for x  
  614.             }//for y  
  615.           
  616.         }  
  617.     }  
  618. }  
  619. /************************************************************************************************************************* 
  620. *模块说明: 
  621. *       模块五: 
  622. *功能说明: 
  623. *      
  624. **************************************************************************************************************************/  
  625. void CalculateScale(Vector& features, double sigma = SIGMA, int intervals = INTERVALS)  
  626. {  
  627.     double intvl = 0;  
  628.     for(int i = 0; i < features.size(); i++)  
  629.     {  
  630.         intvl                    = features[i].interval + features[i].offset_interval;  
  631.         features[i].scale        = sigma * pow(2.0, features[i].octave + intvl/intervals);  
  632.         features[i].octave_scale = sigma * pow(2.0, intvl/intervals);  
  633.     }  
  634.   
  635. }  
  636.   
  637. //对扩大的图像特征缩放  
  638. void HalfFeatures(Vector& features)  
  639. {  
  640.     for(int i = 0; i < features.size(); i++)  
  641.     {  
  642.         features[i].dx /= 2;  
  643.         features[i].dy /= 2;  
  644.   
  645.         features[i].scale /= 2;  
  646.     }  
  647. }  
  648. /******************************************************************************************************************************** 
  649. *模块说明: 
  650. *        模块六---步骤2:计算关键点的梯度和梯度方向 
  651. *功能说明: 
  652. *        1)计算关键点(x,y)处的梯度幅值和梯度方向 
  653. *        2)将所计算出来的梯度幅值和梯度方向保存在变量mag和ori中 
  654. *********************************************************************************************************************************/  
  655. bool CalcGradMagOri(const Mat& gauss, int x, int y, double& mag, double& ori)  
  656. {  
  657.     if(x > 0 && x < gauss.cols-1 && y > 0 && y < gauss.rows -1)  
  658.     {  
  659.         pixel_t *data = (pixel_t*)gauss.data;  
  660.         int step     = gauss.step/sizeof(*data);  
  661.   
  662.         double dx = *(data+step*y+(x+1))-(*(data+step*y+(x-1)));           //[1]利用X方向上的差分代替微分dx  
  663.         double dy = *(data+step*(y+1)+x)-(*(data+step*(y-1)+x));           //[2]利用Y方向上的差分代替微分dy  
  664.   
  665.         mag = sqrt(dx*dx + dy*dy);                                          //[3]计算该关键点的梯度幅值  
  666.         ori = atan2(dy, dx);                                                //[4]计算该关键点的梯度方向  
  667.         return true;  
  668.     }  
  669.     else  
  670.         return false;  
  671. }  
  672. /******************************************************************************************************************************** 
  673. *模块说明: 
  674. *        模块六---步骤1:计算梯度的方向直方图 
  675. *功能说明: 
  676. *        1)直方图以每10度为一个柱,共36个柱,柱代表的方向为为像素点的梯度方向,柱的长短代表了梯度幅值。 
  677. *        2)根据Lowe的建议,直方图统计采用3*1.5*sigma 
  678. *        3)在直方图统计时,每相邻三个像素点采用高斯加权,根据Lowe的建议,模板采用[0.25,0.5,0.25],并且连续加权两次 
  679. *结    论: 
  680. *        图像的关键点检测完毕后,每个关键点就拥有三个信息:位置、尺度、方向;同时也就使关键点具备平移、缩放和旋转不变性 
  681. *********************************************************************************************************************************/  
  682. double* CalculateOrientationHistogram(const Mat& gauss, int x, int y, int bins, int radius, double sigma)  
  683. {  
  684.     double* hist = new double[bins];                           //[1]动态分配一个double类型的数组  
  685.     for(int i = 0; i < bins; i++)                               //[2]给这个数组初始化  
  686.         *(hist + i) = 0.0;  
  687.   
  688.     double  mag;                                                //[3]关键点的梯度幅值                                            
  689.     double  ori;                                                //[4]关键点的梯度方向  
  690.     double  weight;  
  691.   
  692.     int           bin;  
  693.     const double PI2   = 2.0*CV_PI;  
  694.     double        econs = -1.0/(2.0*sigma*sigma);  
  695.   
  696.     for(int i = -radius; i <= radius; i++)  
  697.     {  
  698.         for(int j = -radius; j <= radius; j++)  
  699.         {  
  700.             if(CalcGradMagOri(gauss, x+i, y+j, mag, ori))       //[5]计算该关键点的梯度幅值和方向  
  701.             {  
  702.                 weight = exp((i*i+j*j)*econs);  
  703.                 bin    = cvRound(bins * (CV_PI - ori)/PI2);     //[6]对一个double行的数进行四舍五入,返回一个整形的数  
  704.                 bin    = bin < bins ? bin : 0;  
  705.   
  706.                 hist[bin] += mag * weight;                      //[7]统计梯度的方向直方图  
  707.             }  
  708.         }  
  709.     }  
  710.   
  711.     return hist;  
  712. }  
  713. /******************************************************************************************************************************** 
  714. *模块说明: 
  715. *        模块六---步骤3:对梯度方向直方图进行连续两次的高斯平滑 
  716. *功能说明: 
  717. *        1)在直方图统计时,每相邻三个像素点采用高斯加权,根据Lowe的建议,模板采用[0.25,0.5,0.25],并且连续加权两次 
  718. *        2)对直方图进行两次平滑 
  719. *********************************************************************************************************************************/  
  720. void GaussSmoothOriHist(double *hist, int n)  
  721. {  
  722.     double prev = hist[n-1];  
  723.     double temp;  
  724.     double h0   = hist[0];  
  725.   
  726.     for(int i = 0; i < n; i++)  
  727.     {  
  728.         temp    = hist[i];  
  729.         hist[i] = 0.25 * prev + 0.5 * hist[i] + 0.25 * (i+1 >= n ? h0 : hist[i+1]);//对方向直方图进行高斯平滑  
  730.         prev    = temp;  
  731.     }  
  732. }  
  733. /******************************************************************************************************************************** 
  734. *模块说明: 
  735. *        模块六---步骤4:计算方向直方图中的主方向 
  736. *********************************************************************************************************************************/  
  737. double DominantDirection(double *hist, int n)  
  738. {  
  739.     double maxd = hist[0];  
  740.     for(int i = 1; i < n; i++)  
  741.     {  
  742.         if(hist[i] > maxd)                            //求取36个柱中的最大峰值  
  743.             maxd = hist[i];  
  744.     }  
  745.     return maxd;  
  746. }  
  747. void CopyKeypoint(const Keypoint& src, Keypoint& dst)  
  748. {  
  749.     dst.dx = src.dx;  
  750.     dst.dy = src.dy;  
  751.   
  752.     dst.interval        = src.interval;  
  753.     dst.octave          = src.octave;  
  754.     dst.octave_scale    = src.octave_scale;  
  755.     dst.offset_interval = src.offset_interval;  
  756.   
  757.     dst.offset_x = src.offset_x;  
  758.     dst.offset_y = src.offset_y;  
  759.       
  760.     dst.ori      = src.ori;  
  761.     dst.scale    = src.scale;  
  762.     dst.val      = src.val;  
  763.     dst.x        = src.x;  
  764.     dst.y        = src.y;  
  765. }  
  766. /******************************************************************************************************************************** 
  767. *模块说明: 
  768. *        模块六---步骤5:计算更加精确的关键点主方向----抛物插值 
  769. *功能说明: 
  770. *        1)方向直方图的峰值则代表了该特征点的方向,以直方图中的最大值作为该关键点的主方向。为了增强匹配的鲁棒性,只保留峰值大于主 
  771. *           方向峰值80%的方向作为改关键点的辅方向。因此,对于同一梯度值得多个峰值的关键点位置,在相同位置和尺度将会有多个关键点被 
  772. *           创建但方向不同。仅有15%的关键点被赋予多个方向,但是可以明显的提高关键点的稳定性。 
  773. *        2)在实际编程中,就是把该关键点复制成多份关键点,并将方向值分别赋给这些复制后的关键点 
  774. *        3)并且,离散的梯度直方图要进行【插值拟合处理】,来求得更加精确的方向角度值 
  775. *********************************************************************************************************************************/  
  776. #define Parabola_Interpolate(l, c, r) (0.5*((l)-(r))/((l)-2.0*(c)+(r)))   
  777. void CalcOriFeatures(const Keypoint& keypoint, Vector& features, const double *hist, int n, double mag_thr)  
  778. {  
  779.     double  bin;  
  780.     double  PI2 = CV_PI * 2.0;  
  781.     int l;  
  782.     int r;  
  783.   
  784.     for(int i = 0; i < n; i++)  
  785.     {  
  786.         l = (i == 0) ? n-1 : i -1;  
  787.         r = (i+1)%n;  
  788.   
  789.         //hist[i]是极值  
  790.         if(hist[i] > hist[l] && hist[i] > hist[r] && hist[i] >= mag_thr)  
  791.         {  
  792.             bin = i + Parabola_Interpolate(hist[l], hist[i], hist[r]);  
  793.             bin = (bin < 0) ? (bin + n) : (bin >=n ? (bin -n) : bin);  
  794.   
  795.             Keypoint new_key;  
  796.   
  797.             CopyKeypoint(keypoint, new_key);  
  798.   
  799.             new_key.ori = ((PI2 * bin)/n) - CV_PI;  
  800.             features.push_back(new_key);  
  801.         }  
  802.     }  
  803. }  
  804. /******************************************************************************************************************************** 
  805. *模块说明: 
  806. *        模块六:5 关键点方向分配 
  807. *功能说明: 
  808. *        1)为了使描述符具有旋转不变性,需要利用图像的局部特征为每一个关键点分配一个基准方向。使用图像梯度的方法求取局部结构的稳定 
  809. *           方向。 
  810. *        2)对于在DOG金字塔中检测出来的关键点,采集其所在高斯金字塔图像3sigma邻域窗口内像素的梯度和方向梯度和方向特征。 
  811. *        3)梯度的模和方向如下所示: 
  812. *        4) 在完成关键点的梯度计算后,使用直方图统计邻域内像素的梯度和方向。梯度直方图将0~360度的方向范围分为36个柱,其中每柱10度, 
  813. *           如图5.1所示,直方图的峰值方向代表了关键点的主方向 
  814. *********************************************************************************************************************************/  
  815. void OrientationAssignment(Vector& extrema, Vector& features, const Vector& gauss_pyr)  
  816. {  
  817.     int n = extrema.size();  
  818.     double *hist;  
  819.   
  820.     for(int i = 0; i < n; i++)  
  821.     {  
  822.   
  823.         hist = CalculateOrientationHistogram(gauss_pyr[extrema[i].octave*(INTERVALS+3)+extrema[i].interval],  
  824.             extrema[i].x, extrema[i].y, ORI_HIST_BINS, cvRound(ORI_WINDOW_RADIUS*extrema[i].octave_scale),   
  825.             ORI_SIGMA_TIMES*extrema[i].octave_scale);                             //[1]计算梯度的方向直方图  
  826.       
  827.         for(int j = 0; j < ORI_SMOOTH_TIMES; j++)  
  828.             GaussSmoothOriHist(hist, ORI_HIST_BINS);                              //[2]对方向直方图进行高斯平滑  
  829.         double highest_peak = DominantDirection(hist, ORI_HIST_BINS);            //[3]求取方向直方图中的峰值  
  830.                                                                                   //[4]计算更加精确的关键点主方向  
  831.         CalcOriFeatures(extrema[i], features, hist, ORI_HIST_BINS, highest_peak*ORI_PEAK_RATIO);  
  832.   
  833.         delete[] hist;  
  834.       
  835.     }  
  836. }  
  837.   
  838. void InterpHistEntry(double ***hist, double xbin, double ybin, double obin, double mag, int bins, int d)  
  839. {  
  840.     double d_r, d_c, d_o, v_r, v_c, v_o;  
  841.     double** row, * h;  
  842.     int r0, c0, o0, rb, cb, ob, r, c, o;  
  843.   
  844.     r0 = cvFloor( ybin );  
  845.     c0 = cvFloor( xbin );  
  846.     o0 = cvFloor( obin );  
  847.     d_r = ybin - r0;  
  848.     d_c = xbin - c0;  
  849.     d_o = obin - o0;  
  850.   
  851.     /* 
  852.         做插值: 
  853.         xbin,ybin,obin:种子点所在子窗口的位置和方向 
  854.         所有种子点都将落在4*4的窗口中 
  855.         r0,c0取不大于xbin,ybin的正整数 
  856.         r0,c0只能取到0,1,2 
  857.         xbin,ybin在(-1, 2) 
  858.  
  859.         r0取不大于xbin的正整数时。 
  860.         r0+0 <= xbin <= r0+1 
  861.         mag在区间[r0,r1]上做插值 
  862.  
  863.         obin同理 
  864.     */  
  865.   
  866.     for( r = 0; r <= 1; r++ )  
  867.     {  
  868.         rb = r0 + r;  
  869.         if( rb >= 0  &&  rb < d )  
  870.         {  
  871.             v_r = mag * ( ( r == 0 )? 1.0 - d_r : d_r );  
  872.             row = hist[rb];  
  873.             for( c = 0; c <= 1; c++ )  
  874.             {  
  875.                 cb = c0 + c;  
  876.                 if( cb >= 0  &&  cb < d )  
  877.                 {  
  878.                     v_c = v_r * ( ( c == 0 )? 1.0 - d_c : d_c );  
  879.                     h = row[cb];  
  880.                     for( o = 0; o <= 1; o++ )  
  881.                     {  
  882.                         ob = ( o0 + o ) % bins;  
  883.                         v_o = v_c * ( ( o == 0 )? 1.0 - d_o : d_o );  
  884.                         h[ob] += v_o;  
  885.                     }  
  886.                 }  
  887.             }  
  888.         }  
  889.     }  
  890.   
  891.       
  892. }  
  893. /******************************************************************************************************************************** 
  894. *模块说明: 
  895. *        模块七--步骤1:计算描述子的直方图 
  896. *功能说明: 
  897. *       
  898. *********************************************************************************************************************************/  
  899. double*** CalculateDescrHist(const Mat& gauss, int x, int y, double octave_scale, double ori, int bins, int width)  
  900. {  
  901.     double ***hist = new double**[width];  
  902.   
  903.     for(int i = 0; i < width; i++)  
  904.     {  
  905.         hist[i] = new double*[width];  
  906.         for(int j = 0; j < width; j++)  
  907.         {  
  908.             hist[i][j] = new double[bins];  
  909.         }  
  910.     }  
  911.       
  912.     for(int r = 0; r < width; r++ )  
  913.         for(int c = 0; c < width; c++ )  
  914.             for(int o = 0; o < bins; o++ )  
  915.                 hist[r][c][o]=0.0;  
  916.   
  917.   
  918.     double cos_ori = cos(ori);  
  919.     double sin_ori = sin(ori);  
  920.   
  921.     //6.1高斯权值,sigma等于描述字窗口宽度的一半  
  922.     double sigma = 0.5 * width;  
  923.     double conste = -1.0/(2*sigma*sigma);  
  924.   
  925.     double PI2 = CV_PI * 2;  
  926.   
  927.     double sub_hist_width = DESCR_SCALE_ADJUST * octave_scale;  
  928.   
  929.     //【1】计算描述子所需的图像领域区域的半径  
  930.     int    radius   = (sub_hist_width*sqrt(2.0)*(width+1))/2.0 + 0.5;    //[1]0.5取四舍五入  
  931.     double grad_ori;  
  932.     double grad_mag;  
  933.   
  934.     for(int i = -radius; i <= radius; i++)  
  935.     {  
  936.         for(int j =-radius; j <= radius; j++)  
  937.         {  
  938.             double rot_x = (cos_ori * j - sin_ori * i) / sub_hist_width;  
  939.             double rot_y = (sin_ori * j + cos_ori * i) / sub_hist_width;  
  940.   
  941.             double xbin = rot_x + width/2 - 0.5;                         //[2]xbin,ybin为落在4*4窗口中的下标值  
  942.             double ybin = rot_y + width/2 - 0.5;  
  943.   
  944.             if(xbin > -1.0 && xbin < width && ybin > -1.0 && ybin < width)  
  945.             {  
  946.                 if(CalcGradMagOri(gauss, x+j, y + i, grad_mag, grad_ori)) //[3]计算关键点的梯度方向  
  947.                 {  
  948.                     grad_ori = (CV_PI-grad_ori) - ori;  
  949.                     while(grad_ori < 0.0)  
  950.                         grad_ori += PI2;  
  951.                     while(grad_ori >= PI2)  
  952.                         grad_ori -= PI2;  
  953.   
  954.                     double obin = grad_ori * (bins/PI2);  
  955.   
  956.                     double weight = exp(conste*(rot_x*rot_x + rot_y * rot_y));  
  957.   
  958.                     InterpHistEntry(hist, xbin, ybin, obin, grad_mag*weight, bins, width);  
  959.   
  960.                 }  
  961.             }  
  962.         }  
  963.     }  
  964.   
  965.     return hist;  
  966. }  
  967.   
  968. void NormalizeDescr(Keypoint& feat)  
  969. {  
  970.     double cur, len_inv, len_sq = 0.0;  
  971.     int i, d = feat.descr_length;  
  972.   
  973.     for( i = 0; i < d; i++ )  
  974.     {  
  975.         cur = feat.descriptor[i];  
  976.         len_sq += cur*cur;  
  977.     }  
  978.     len_inv = 1.0 / sqrt( len_sq );  
  979.     for( i = 0; i < d; i++ )  
  980.         feat.descriptor[i] *= len_inv;  
  981. }  
  982. /******************************************************************************************************************************** 
  983. *模块说明: 
  984. *        模块七--步骤2:直方图到描述子的转换 
  985. *功能说明: 
  986. *       
  987. *********************************************************************************************************************************/  
  988. void HistToDescriptor(double ***hist, int width, int bins, Keypoint& feature)  
  989. {  
  990.     int int_val, i, r, c, o, k = 0;  
  991.   
  992.     for( r = 0; r < width; r++ )  
  993.         for( c = 0; c < width; c++ )  
  994.             for( o = 0; o < bins; o++ )  
  995.             {  
  996.                 feature.descriptor[k++] = hist[r][c][o];  
  997.             }  
  998.   
  999.     feature.descr_length = k;  
  1000.     NormalizeDescr(feature);                           //[1]描述子特征向量归一化  
  1001.   
  1002.     for( i = 0; i < k; i++ )                           //[2]描述子向量门限  
  1003.         if( feature.descriptor[i] > DESCR_MAG_THR )      
  1004.             feature.descriptor[i] = DESCR_MAG_THR;  
  1005.   
  1006.     NormalizeDescr(feature);                           //[3]描述子进行最后一次的归一化操作  
  1007.   
  1008.     for( i = 0; i < k; i++ )                           //[4]将单精度浮点型的描述子转换为整形的描述子  
  1009.     {  
  1010.         int_val = INT_DESCR_FCTR * feature.descriptor[i];  
  1011.         feature.descriptor[i] = min( 255, int_val );  
  1012.     }  
  1013. }  
  1014. /******************************************************************************************************************************** 
  1015. *模块说明: 
  1016. *        模块七:6 关键点描述 
  1017. *功能说明: 
  1018. *        1)通过以上步骤,对于一个关键点,拥有三个信息:位置、尺度、方向 
  1019. *        2)接下来就是为每个关键点建立一个描述符,用一组向量来将这个关键点描述出来,使其不随各种变化而变化,比如光照、视角变化等等 
  1020. *        3)这个描述子不但包括关键点,也包含关键点周围对其贡献的像素点,并且描述符应该有较高的独特性,以便于特征点正确的匹配概率 
  1021. *        1)SIFT描述子----是关键点邻域高斯图像梯度统计结果的一种表示。 
  1022. *        2)通过对关键点周围图像区域分块,计算块内梯度直方图,生成具有独特性的向量 
  1023. *        3)这个向量是该区域图像信息的一种表述和抽象,具有唯一性。 
  1024. *Lowe论文: 
  1025. *    Lowe建议描述子使用在关键点尺度空间内4*4的窗口中计算的8个方向的梯度信息,共4*4*8=128维向量来表征。具体的步骤如下所示: 
  1026. *        1)确定计算描述子所需的图像区域 
  1027. *        2)将坐标轴旋转为关键点的方向,以确保旋转不变性,如CSDN博文中的图6.2所示;旋转后邻域采样点的新坐标可以通过公式(6-2)计算 
  1028. *        3)将邻域内的采样点分配到对应的子区域,将子区域内的梯度值分配到8个方向上,计算其权值 
  1029. *        4)插值计算每个种子点八个方向的梯度 
  1030. *        5)如上统计的4*4*8=128个梯度信息即为该关键点的特征向量。特征向量形成后,为了去除光照变化的影响,需要对它们进行归一化处理, 
  1031. *           对于图像灰度值整体漂移,图像各点的梯度是邻域像素相减得到的,所以也能去除。得到的描述子向量为H,归一化之后的向量为L 
  1032. *        6)描述子向量门限。非线性光照,相机饱和度变化对造成某些方向的梯度值过大,而对方向的影响微弱。因此,设置门限值(向量归一化 
  1033. *           后,一般取0.2)截断较大的梯度值。然后,在进行一次归一化处理,提高特征的鉴别性。 
  1034. *        7)按特征点的尺度对特征描述向量进行排序 
  1035. *        8)至此,SIFT特征描述向量生成。 
  1036. *********************************************************************************************************************************/  
  1037. void DescriptorRepresentation(Vector& features, const Vector& gauss_pyr, int bins, int width)  
  1038. {  
  1039.     double ***hist;  
  1040.   
  1041.     for(int i = 0; i < features.size(); i++)  
  1042.     {                                                                       //[1]计算描述子的直方图  
  1043.         hist = CalculateDescrHist(gauss_pyr[features[i].octave*(INTERVALS+3)+features[i].interval],  
  1044.             features[i].x, features[i].y, features[i].octave_scale, features[i].ori, bins, width);  
  1045.   
  1046.         HistToDescriptor(hist, width, bins, features[i]);                   //[2]直方图到描述子的转换  
  1047.   
  1048.         for(int j = 0; j < width; j++)  
  1049.         {  
  1050.             for(int k = 0; k < width; k++)  
  1051.             {  
  1052.                 delete[] hist[j][k];  
  1053.             }  
  1054.             delete[] hist[j];  
  1055.         }  
  1056.         delete[] hist;  
  1057.     }  
  1058. }  
  1059.   
  1060. bool FeatureCmp(Keypoint& f1, Keypoint& f2)  
  1061. {  
  1062.     return f1.scale < f2.scale;  
  1063. }  
  1064. /******************************************************************************************************************* 
  1065. *函数说明: 
  1066. *        最大的模块1:SIFT算法模块 
  1067. *函数参数说明: 
  1068. *        1---const Mat &src---------------准备进行特征点检测的原始图片 
  1069. *        2---Vector& features---用来存储检测出来的关键点 
  1070. *        3---double sigma-----------------sigma值 
  1071. *        4---int intervals----------------关键点所在的层数 
  1072. ********************************************************************************************************************/  
  1073. void Sift(const Mat &src, Vector& features, double sigma, int intervals)  
  1074. {  
  1075.     std::cout<<"【Step_one】Create -1 octave gaussian pyramid image"<
  1076.     cv::Mat          init_gray;  
  1077.     CreateInitSmoothGray(src, init_gray, sigma);                                     
  1078.     int octaves = log((double)min(init_gray.rows, init_gray.cols))/log(2.0) - 2;             //计算高斯金字塔的层数  
  1079.     std::cout<<"【1】The height and width of init_gray_img = "<"*"<
  1080.     std::cout<<"【2】The octaves of the gauss pyramid      = "<
  1081.   
  1082.   
  1083.     std::cout <<"【Step_two】Building gaussian pyramid ..."<
  1084.     std::vector gauss_pyr;                                                    
  1085.     GaussianPyramid(init_gray, gauss_pyr, octaves, intervals, sigma);              
  1086.     write_pyr(gauss_pyr, "gausspyramid");  
  1087.       
  1088.   
  1089.     std::cout <<"【Step_three】Building difference of gaussian pyramid..."<
  1090.     Vector dog_pyr;                                                            
  1091.     DogPyramid(gauss_pyr, dog_pyr, octaves, intervals);                             
  1092.     write_pyr(dog_pyr, "dogpyramid");  
  1093.   
  1094.   
  1095.   
  1096.     std::cout <<"【Step_four】Deatecting local extrema..."<
  1097.     Vector extrema;                                                       
  1098.     DetectionLocalExtrema(dog_pyr, extrema, octaves, intervals);                    
  1099.     std::cout <<"【3】keypoints cout: "<< extrema.size()<<" --"<
  1100.     std::cout <<"【4】extrema detection finished."<
  1101.     std::cout <<"【5】please look dir gausspyramid, dogpyramid and extrema.txt.--"<
  1102.   
  1103.   
  1104.   
  1105.     std::cout <<"【Step_five】CalculateScale..."<
  1106.     CalculateScale(extrema, sigma, intervals);                                      
  1107.     HalfFeatures(extrema);  
  1108.   
  1109.   
  1110.   
  1111.     std::cout << "【Step_six】Orientation assignment..."<
  1112.     OrientationAssignment(extrema, features, gauss_pyr);                           
  1113.     std::cout << "【6】features count: "<
  1114.         
  1115.   
  1116.   
  1117.     std::cout << "【Step_seven】DescriptorRepresentation..."<
  1118.     DescriptorRepresentation(features, gauss_pyr, DESCR_HIST_BINS, DESCR_WINDOW_WIDTH);  
  1119.     sort(features.begin(), features.end(), FeatureCmp);                             
  1120.     cout << "finished."<
  1121. }  
  1122. /******************************************************************************************************************* 
  1123. *函数说明: 
  1124. *        画出SIFT特征点的具体函数 
  1125. ********************************************************************************************************************/  
  1126. void DrawSiftFeature(Mat& src, Keypoint& feat, CvScalar color)  
  1127. {  
  1128.     int len, hlen, blen, start_x, start_y, end_x, end_y, h1_x, h1_y, h2_x, h2_y;  
  1129.     double scl, ori;  
  1130.     double scale = 5.0;  
  1131.     double hscale = 0.75;  
  1132.     CvPoint start, end, h1, h2;  
  1133.   
  1134.     /* compute points for an arrow scaled and rotated by feat's scl and ori */  
  1135.     start_x = cvRound( feat.dx );  
  1136.     start_y = cvRound( feat.dy );  
  1137.     scl = feat.scale;  
  1138.     ori = feat.ori;  
  1139.     len = cvRound( scl * scale );  
  1140.     hlen = cvRound( scl * hscale );  
  1141.     blen = len - hlen;  
  1142.     end_x = cvRound( len *  cos( ori ) ) + start_x;  
  1143.     end_y = cvRound( len * -sin( ori ) ) + start_y;  
  1144.     h1_x = cvRound( blen *  cos( ori + CV_PI / 18.0 ) ) + start_x;  
  1145.     h1_y = cvRound( blen * -sin( ori + CV_PI / 18.0 ) ) + start_y;  
  1146.     h2_x = cvRound( blen *  cos( ori - CV_PI / 18.0 ) ) + start_x;  
  1147.     h2_y = cvRound( blen * -sin( ori - CV_PI / 18.0 ) ) + start_y;  
  1148.     start = cvPoint( start_x, start_y );  
  1149.     end = cvPoint( end_x, end_y );  
  1150.     h1 = cvPoint( h1_x, h1_y );  
  1151.     h2 = cvPoint( h2_x, h2_y );  
  1152.   
  1153.     line( src, start, end, color, 1, 8, 0 );  
  1154.     line( src, end, h1, color, 1, 8, 0 );  
  1155.     line( src, end, h2, color, 1, 8, 0 );  
  1156. }  
  1157. /******************************************************************************************************************* 
  1158. *函数说明: 
  1159. *         最大的模块3:画出SIFT特征点 
  1160. ********************************************************************************************************************/  
  1161. void DrawSiftFeatures(Mat& src, Vector& features)  
  1162. {  
  1163.     CvScalar color = CV_RGB( 0, 255, 0 );  
  1164.     for(int i = 0; i < features.size(); i++)  
  1165.     {  
  1166.         DrawSiftFeature(src, features[i], color);  
  1167.     }  
  1168. }  
  1169. /******************************************************************************************************************* 
  1170. *函数说明: 
  1171. *         最大的模块2:画出关键点KeyPoints 
  1172. ********************************************************************************************************************/  
  1173. void DrawKeyPoints(Mat &src, Vector& keypoints)  
  1174. {  
  1175.     int j = 0;  
  1176.     for(int i = 0; i < keypoints.size(); i++)  
  1177.     {  
  1178.       
  1179.         CvScalar color = {255, 0 ,0};  
  1180.         circle(src, Point(keypoints[i].dx, keypoints[i].dy), 3, color);  
  1181.         j++;  
  1182.     }  
  1183. }  
  1184.   
  1185. const char* GetFileName(const char* dir, int i)  
  1186. {  
  1187.     char *name = new char[50];  
  1188.     sprintf(name, "%s\\%d\.jpg",dir, i);  
  1189.     return name;  
  1190. }  
  1191.   
  1192. void cv64f_to_cv8U(const Mat& src, Mat& dst)  
  1193. {  
  1194.     double* data = (double*)src.data;  
  1195.     int step = src.step/sizeof(*data);  
  1196.   
  1197.     if(!dst.empty())  
  1198.         return;  
  1199.     dst.create(src.size(), CV_8U);  
  1200.   
  1201.     uchar* dst_data = dst.data;  
  1202.   
  1203.     for(int i = 0, m = 0; i < src.cols; i++, m++)  
  1204.     {  
  1205.         for(int j = 0, n = 0; j < src.rows; j++, n++)  
  1206.         {  
  1207.             *(dst_data+dst.step*j+i) = (uchar)(*(data+step*j+i)*255);     
  1208.         }  
  1209.     }  
  1210. }  
  1211.   
  1212.   
  1213. //通过转换后保存的图像,会失真,和imshow显示出的图像相差很大  
  1214. void writecv64f(const char* filename, const Mat& mat)  
  1215. {  
  1216.     Mat dst;  
  1217.     cv64f_to_cv8U(mat, dst);  
  1218.     imwrite(filename, dst);  
  1219. }  
  1220.   
  1221. void write_pyr(const Vector& pyr, const char* dir)  
  1222. {  
  1223.     for(int i = 0; i < pyr.size(); i++)  
  1224.     {  
  1225.                 writecv64f(GetFileName(dir, i), pyr[i]);  
  1226.     }  
  1227. }  
  1228.   
  1229. void read_features(Vector&features, const char*file)  
  1230. {  
  1231.     ifstream in(file);  
  1232.     int n = 0, dims = 0;  
  1233.     in >> n >> dims;   
  1234.     cout <" " <
  1235.     for(int i = 0; i < n; i++)  
  1236.     {  
  1237.         Keypoint key;  
  1238.         in >> key.dy >> key.dx  >> key.scale >> key.ori;  
  1239.         for(int j = 0; j < dims; j++)  
  1240.         {  
  1241.             in >> key.descriptor[j];  
  1242.         }  
  1243.         features.push_back(key);  
  1244.     }  
  1245.     in.close();  
  1246. }  
  1247. /******************************************************************************************************************* 
  1248. *函数说明: 
  1249. *         最大的模块4:将特征点写入文本文件 
  1250. ********************************************************************************************************************/  
  1251. void write_features(const Vector&features, const char*file)  
  1252. {  
  1253.     ofstream dout(file);  
  1254.     dout << features.size()<<" "<< FEATURE_ELEMENT_LENGTH<
  1255.     for(int i = 0; i < features.size(); i++)  
  1256.     {  
  1257.         dout <" " << features[i].dx<<" "<" "<
  1258.         for(int j = 0; j < FEATURE_ELEMENT_LENGTH; j++)  
  1259.         {  
  1260.             if(j % 20 == 0)  
  1261.                 dout<
  1262.             dout << features[i].descriptor[j]<<" ";   
  1263.         }  
  1264.         dout<
  1265.     }  
  1266.     dout.close();  
  1267. }  
[cpp]  view plain  copy
 
  1. /************************************************************************************************************************* 
  2. *文件说明: 
  3. *        SIFT算法的实现 
  4. *开发环境: 
  5. *        Win10+VS2012+OpenCv2.4.8 
  6. *时间地点: 
  7. *        陕西师范大学.文津楼 2016.12.30 
  8. *再次修改时间: 
  9. *        陕西师范大学.文津楼 2017.2.24 
  10. *作者信息: 
  11. *        九月 
  12. **************************************************************************************************************************/  
  13. #include   
  14. #include   
  15. #include   
  16. #include   
  17. #include   
  18.   
  19. #include "sift.h"  
  20.   
  21.   
  22. using namespace std;  
  23. using namespace cv;  
  24.   
  25.   
  26. int main(int argc, char **argv)  
  27. {  
  28.     cv::Mat src = imread("jobs_512.jpg");  
  29.   
  30.     if(src.empty())  
  31.     {  
  32.         cout << "jobs_512.jpg open error! "<
  33.         getchar();  
  34.         return -1;  
  35.     }  
  36.   
  37.     if(src.channels()!=3) return -2;  
  38.   
  39.     cv::Vector features;  
  40.   
  41.     Sift(src, features, 1.6);                           //【1】SIFT特征点检测和特征点描述  
  42.     DrawKeyPoints(src, features);                       //【2】画出关键点(特征点)  
  43.     DrawSiftFeatures(src, features);                    //【3】画出SIFT特征点  
  44.     write_features(features, "descriptor.txt");         //【4】将特征点写入文本  
  45.   
  46.     cv::imshow("src", src);  
  47.     cv::waitKey();  
  48.   
  49.     return 0;  
  50. }  

参考资料:

1)http://blog.csdn.NET/maweifei/article/details/53932782

2)http://wenku.baidu.com/view/e10ed068561252d380eb6eec.html?re=view


你可能感兴趣的:(CV_计算机视觉,OpenCv专栏,目标检测/目标跟踪,图像特征分析)