SIFT解析(二)特征点位置确定

最近微博上有人发起投票那篇论文是自己最受益匪浅的论文,不少人说是lowe的这篇介绍SIFT的论文。确实,在图像特征识别领域,SIFT的出现是具有重大意义的,SIFT特征以其稳定的存在,较高的区分度推进了诸多领域的发展,比如识别和配准。上一篇文章,解析了SIFT特征提取的第一步高斯金字塔的构建,并详细分析了高斯金字塔以及差分高斯金字塔如何完成一个连续的尺度空间的构建。构建高斯金字塔不是目的,目的是如何利用高斯金字塔找到极值点。

lowe在论文中阐述了为什么使用差分高斯金字塔:

1)差分高斯图像可以直接由高斯图像相减获得,简单高效

2) 差分高斯函数是尺度规范化的高斯拉普拉斯函数的近似,而高斯拉普拉斯函数的极大值和极小值点是一种非常稳定的特征点(与梯度特征、Hessian特征和Harris角点相比)

有了这些基础,我们就可以放开手脚从差分高斯金字塔中找点了。

特征点的确定主要包括两个过程:确定潜在特征点,精确确定特征点的位置和去除不稳定特征点。

确定潜在特征点

上文已经阐述,高斯拉普拉斯函数的极大值和极小值点是一种非常稳定的特征点,因此我们从差分高斯金字塔中寻找这些潜在特征点。差分高斯金字塔是一个三维空间(平面图像二维,尺度一维),因此我们在三维空间中在寻找极大值点和极小值点。具体方法是比较当前特征点的灰度值和其他26个点的灰度值的大小,这26个点包括:当前尺度下该点的8邻域以及前一尺度和后一尺度下与该点最近的9个点(9*2+8=26),如下图所示:

OpenCV该部分源码:

  1. void SIFT::findScaleSpaceExtrema( const vector<Mat>& gauss_pyr, const vector<Mat>& dog_pyr,
  2. vector<KeyPoint>& keypoints ) const
  3. {
  4. ......
  5. for( int o = 0; o < nOctaves; o++ )//每一个八度
  6. for( int i = 1; i <= nOctaveLayers; i++ )//对八度中的存在具有第1至第nOctaveLayers层高斯差分图像提取特征点
  7. {
  8. ......
  9. for( int r = SIFT_IMG_BORDER; r < rows-SIFT_IMG_BORDER; r++)//图像二维空间.行
  10. {
  11. ......
  12. for( int c = SIFT_IMG_BORDER; c < cols-SIFT_IMG_BORDER; c++)//图像二维空间.列
  13. {
  14. .......
  15. // 当前点与26个点比较,比较两次,分别确定是否是极大值,是否是极小值
  16. if( std::abs(val) > threshold &&
  17. ((val > 0 && val >= currptr[c-1] && val >= currptr[c+1] &&
  18. val >= currptr[c-step-1] && val >= currptr[c-step] && val >= currptr[c-step+1] &&
  19. val >= currptr[c+step-1] && val >= currptr[c+step] && val >= currptr[c+step+1] &&
  20. val >= nextptr[c] && val >= nextptr[c-1] && val >= nextptr[c+1] &&
  21. val >= nextptr[c-step-1] && val >= nextptr[c-step] && val >= nextptr[c-step+1] &&
  22. val >= nextptr[c+step-1] && val >= nextptr[c+step] && val >= nextptr[c+step+1] &&
  23. val >= prevptr[c] && val >= prevptr[c-1] && val >= prevptr[c+1] &&
  24. val >= prevptr[c-step-1] && val >= prevptr[c-step] && val >= prevptr[c-step+1] &&
  25. val >= prevptr[c+step-1] && val >= prevptr[c+step] && val >= prevptr[c+step+1]) ||
  26. (val < 0 && val <= currptr[c-1] && val <= currptr[c+1] &&
  27. val <= currptr[c-step-1] && val <= currptr[c-step] && val <= currptr[c-step+1] &&
  28. val <= currptr[c+step-1] && val <= currptr[c+step] && val <= currptr[c+step+1] &&
  29. val <= nextptr[c] && val <= nextptr[c-1] && val <= nextptr[c+1] &&
  30. val <= nextptr[c-step-1] && val <= nextptr[c-step] && val <= nextptr[c-step+1] &&
  31. val <= nextptr[c+step-1] && val <= nextptr[c+step] && val <= nextptr[c+step+1] &&
  32. val <= prevptr[c] && val <= prevptr[c-1] && val <= prevptr[c+1] &&
  33. val <= prevptr[c-step-1] && val <= prevptr[c-step] && val <= prevptr[c-step+1] &&
  34. val <= prevptr[c+step-1] && val <= prevptr[c+step] && val <= prevptr[c+step+1])))
  35. {
  36. ......
  37. }
  38. }
  39. }
  40. }
  41. }

尺度空间中的极值点已经确定出来了,下面有两个问题需要解决:

(1)这些点是最终我们确定的SIFT特征点集的超集,该超集里包含许多“间谍”-----不稳定的特征点,因此必须去掉这些不稳定的特征点。这些不稳定的特征点主要包含两类:低对比度的点(对噪声敏感)和边缘点。

(2)这一步骤中极值点的坐标还是离散的整数值,如何精确确定特征点的位置。

由于在计算上(2)问题的解决可以捎带解决(1)中低对比度点的问题,因此我们先讨论问题(2)。本部分的OpenCV源码位于sift.cpp文件的adjustLocalExtrema函数中,本文最后会贴出此部分源码,下面首先分析如何解决以上两个问题。

精确确定特征点的位置:

由于图像是一个离散的空间,特征点的位置的坐标都是整数,但是极值点的坐标并不一定就是整数,如下图所示。

因此,如何从离散空间中估计出极值点的精确位置是重要的。为了精确确定极值点坐标,Brown和Lowe使用了三元二次函数,通过迭代确定极值点的位置,具有良好的效果。

主要是根据泰勒公式,泰勒公式作用:用值已知的点A估计点A附近的某点B的值。

求上式极值,对其求导,导数等于0,得到

去除不稳定特征点

去除对比度低的点

以上求出了极值点的精确的位置,将求出的 x 带入原式,得:

我们就利用这个函数去除对比度低的点,lowe文中,当D(x)<=0.03时,去除这个特征点。

去除边缘点

差分高斯金字塔中的极值点会有许多边缘点,边缘点对一些噪声不稳定,因此需要去除这些边缘相应点。

差分高斯金字塔中会有一些不是很好的极值点,这些点的特征是:在跨越边缘的方向有较大的主曲率,在与边缘相切的方向主曲率较小。在本步骤中,需要去除这些不好的边缘相应。主曲率可以通过2阶Hessian方阵获得:

D函数中某点的主曲率和该点的H矩阵的特征值是成比例的,因此我们可以通过H矩阵的特征值来确定某点在差分高斯金字塔中的主曲率。

设矩阵H的特征值分别为α(较大)和β(较小),有如下公式:

通过以上两式,α和β就可以计算出来了,但是,不急!

如上文所述,那些不好的边缘点:跨越边缘的方向有较大的主曲率,与边缘相切的方向主曲率较小。因此,我们通过α/β的比率函数并确定阈值来体现表征那些不好的边缘点,α/β越大,说明这个点就越糟糕,就越应该被删掉,但是这样就要真真切切计算α和β的值,前面让大家不急了,是的,先不用着急计算,设定r=α/β(即 α=rβ),使用如下公式:

以上函数是关于r的增函数(已经假设α是特征值中较大的一个),r 越大,以上函数值就越大,反之,以上函数值越大,r 就是越大的,因此我们可以通过已知的Tr(H)和Det(H)“曲线地”去判断 r的大小!所以在本步骤中,去除不好的边缘点的阈值是:

lowe论文中设定r=10。

到这里,在差分高斯金字塔中提取的特征点就完成了提纯的步骤。

下面是OpenCV源码中特征点精确位置的确定过程以及特征点提纯过程,主要实现函数为sift.cpp中adjustLocalExtrema函数:

[cpp] view plaincopy

  1. // Interpolates a scale-space extremum‘s location and scale to subpixel
  2. // accuracy to form an image feature. Rejects features with low contrast.
  3. // Based on Section 4 of Lowe‘s paper.
  4. static bool adjustLocalExtrema( const vector<Mat>& dog_pyr, KeyPoint& kpt, int octv,
  5. int& layer, int& r, int& c, int nOctaveLayers,
  6. float contrastThreshold, float edgeThreshold, float sigma )
  7. {
  8. const float img_scale = 1.f/(255*SIFT_FIXPT_SCALE);
  9. const float deriv_scale = img_scale*0.5f;
  10. const float second_deriv_scale = img_scale;
  11. const float cross_deriv_scale = img_scale*0.25f;
  12. float xi=0, xr=0, xc=0, contr=0;
  13. int i = 0;
  14. // 如上文所述,迭代计算特征点的精确位置
  15. for( ; i < SIFT_MAX_INTERP_STEPS; i++ )
  16. {
  17. int idx = octv*(nOctaveLayers+2) + layer;
  18. const Mat& img = dog_pyr[idx];
  19. const Mat& prev = dog_pyr[idx-1];
  20. const Mat& next = dog_pyr[idx+1];
  21. Vec3f dD((img.at<sift_wt>(r, c+1) - img.at<sift_wt>(r, c-1))*deriv_scale,
  22. (img.at<sift_wt>(r+1, c) - img.at<sift_wt>(r-1, c))*deriv_scale,
  23. (next.at<sift_wt>(r, c) - prev.at<sift_wt>(r, c))*deriv_scale);
  24. float v2 = (float)img.at<sift_wt>(r, c)*2;
  25. float dxx = (img.at<sift_wt>(r, c+1) + img.at<sift_wt>(r, c-1) - v2)*second_deriv_scale;
  26. float dyy = (img.at<sift_wt>(r+1, c) + img.at<sift_wt>(r-1, c) - v2)*second_deriv_scale;
  27. float dss = (next.at<sift_wt>(r, c) + prev.at<sift_wt>(r, c) - v2)*second_deriv_scale;
  28. float dxy = (img.at<sift_wt>(r+1, c+1) - img.at<sift_wt>(r+1, c-1) -
  29. img.at<sift_wt>(r-1, c+1) + img.at<sift_wt>(r-1, c-1))*cross_deriv_scale;
  30. float dxs = (next.at<sift_wt>(r, c+1) - next.at<sift_wt>(r, c-1) -
  31. prev.at<sift_wt>(r, c+1) + prev.at<sift_wt>(r, c-1))*cross_deriv_scale;
  32. float dys = (next.at<sift_wt>(r+1, c) - next.at<sift_wt>(r-1, c) -
  33. prev.at<sift_wt>(r+1, c) + prev.at<sift_wt>(r-1, c))*cross_deriv_scale;
  34. Matx33f H(dxx, dxy, dxs,
  35. dxy, dyy, dys,
  36. dxs, dys, dss);//通过当前像素点以及周围像素点差值出H矩阵
  37. Vec3f X = H.solve(dD, DECOMP_LU);
  38. xi = -X[2];
  39. xr = -X[1];
  40. xc = -X[0];
  41. //有任何一个维度的偏移超过0.5,会更新当前像素点
  42. //如果每一个维度的偏移都没有超过0.5,当前像素的位置加上偏移就是最终的精确点
  43. if( std::abs(xi) < 0.5f && std::abs(xr) < 0.5f && std::abs(xc) < 0.5f )
  44. break;
  45. if( std::abs(xi) > (float)(INT_MAX/3) ||
  46. std::abs(xr) > (float)(INT_MAX/3) ||
  47. std::abs(xc) > (float)(INT_MAX/3) )
  48. return false;
  49. c += cvRound(xc);
  50. r += cvRound(xr);
  51. layer += cvRound(xi);
  52. if( layer < 1 || layer > nOctaveLayers ||
  53. c < SIFT_IMG_BORDER || c >= img.cols - SIFT_IMG_BORDER  ||
  54. r < SIFT_IMG_BORDER || r >= img.rows - SIFT_IMG_BORDER )
  55. return false;
  56. }
  57. //迭代结束
  58. // ensure convergence of interpolation
  59. if( i >= SIFT_MAX_INTERP_STEPS )
  60. return false;
  61. {
  62. int idx = octv*(nOctaveLayers+2) + layer;
  63. const Mat& img = dog_pyr[idx];
  64. const Mat& prev = dog_pyr[idx-1];
  65. const Mat& next = dog_pyr[idx+1];
  66. Matx31f dD((img.at<sift_wt>(r, c+1) - img.at<sift_wt>(r, c-1))*deriv_scale,
  67. (img.at<sift_wt>(r+1, c) - img.at<sift_wt>(r-1, c))*deriv_scale,
  68. (next.at<sift_wt>(r, c) - prev.at<sift_wt>(r, c))*deriv_scale);
  69. float t = dD.dot(Matx31f(xc, xr, xi));
  70. contr = img.at<sift_wt>(r, c)*img_scale + t * 0.5f;
  71. if( std::abs( contr ) * nOctaveLayers < contrastThreshold )//去除低对比度的点
  72. return false;
  73. // principal curvatures are computed using the trace and det of Hessian
  74. float v2 = img.at<sift_wt>(r, c)*2.f;
  75. float dxx = (img.at<sift_wt>(r, c+1) + img.at<sift_wt>(r, c-1) - v2)*second_deriv_scale;
  76. float dyy = (img.at<sift_wt>(r+1, c) + img.at<sift_wt>(r-1, c) - v2)*second_deriv_scale;
  77. float dxy = (img.at<sift_wt>(r+1, c+1) - img.at<sift_wt>(r+1, c-1) -
  78. img.at<sift_wt>(r-1, c+1) + img.at<sift_wt>(r-1, c-1)) * cross_deriv_scale;
  79. float tr = dxx + dyy;
  80. float det = dxx * dyy - dxy * dxy;
  81. if( det <= 0 || tr*tr*edgeThreshold >= (edgeThreshold + 1)*(edgeThreshold + 1)*det )//去除边缘噪声点
  82. return false;
  83. }
  84. kpt.pt.x = (c + xc) * (1 << octv);
  85. kpt.pt.y = (r + xr) * (1 << octv);
  86. kpt.octave = octv + (layer << 8) + (cvRound((xi + 0.5)*255) << 16);
  87. kpt.size = sigma*powf(2.f, (layer + xi) / nOctaveLayers)*(1 << octv)*2;
  88. kpt.response = std::abs(contr);
  89. return true;
  90. }

以上SIFT源码均摘自OpenCV nonfree模块,lowe对SIFT拥有版权。

符合要求的特征点构建完毕,需要对该特征点进行描述了,请关注本博客SIFT系列的下一篇文章:SIFT解析(三)生成特征描述子

来自为知笔记(Wiz)

时间: 2024-08-14 20:47:17

SIFT解析(二)特征点位置确定的相关文章

SIFT解析(一)建立高斯金字塔

转自:honpey  http://blog.csdn.net/wendy709468104/article/details/8639617 SIFT(Scale-Invariant Feature Transform,尺度不变特征转换)在目标识别.图像配准领域具有广泛的应用,下面按照SIFT特征的算法流程对其进行简要介绍对SIFT特征做简要介绍. 高斯金字塔是SIFT特征提取的第一步,之后特征空间中极值点的确定,都是基于高斯金字塔,因此SIFT特征学习的第一步是如何建立的高斯金字塔. 明白几个

【学习笔记】SIFT尺度不变特征 (配合UCF-CRCV课程视频)

SIFT尺度不变特征 D. Lowe. Distinctive image features from scale-invariant key points, IJCV 2004 -Lecture 05 - Scale-invariant Feature Transform (SIFT) - https://www.youtube.com/watch?v=NPcMS49V5hg 本文是上面UCF-CRCV课程视频的学习笔记. DOG(Difference of Gaussian)角点 / Har

Java生成二维码解析二维码

package QrCode; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.imageio.ImageIO; import com.google.zxing.BarcodeFormat; imp

使用zxing工具包创建和解析二维码

关于二维码是什么,以及二维码是如何生成的,我也没有研究得很深入,就不多说了,以免误导大家.请参看: java 二维码原理以及用java实现的二维码的生成.解码 二维码的生成细节和原理 下面是一个可以生成和解析二维码的工具类,该类用到了zxing工具包,我通过Maven去下载的: <dependencies>     <!-- JavaSE包依赖于Core包,因此Core包不需要直接依赖了     <dependency>         <groupId>com.

Java使用ZXing生成/解析二维码图片

ZXing是一种开源的多格式1D/2D条形码图像处理库,在Java中的实现.重点是在手机上使用内置摄像头来扫描和解码设备上的条码,而不与服务器通信.然而,该项目也可以用于对桌面和服务器上的条形码进行编码和解码.目前支持这些格式: UPC-A and UPC-E EAN-8 and EAN-13 Code 39 Code 93 Code 128 ITF Codabar RSS-14 (all variants) RSS Expanded (most variants) QR Code Data M

ZXing解析二维码

上一篇文件已经说过如何用ZXing进行生成二维码和带图片的二维码,下面说下如何解析二维码 二维码的解析和生成类似,也可以参考google的一个操作类 BufferedImageLuminanceSource类,该类可在google的测试包中找到,另外j2se中也有该类,你可以将该类直接拷贝到源码中使用,你也可以自己写个. import java.awt.Graphics2D; import java.awt.geom.AffineTransform; import java.awt.image.

Java使用QRCode.jar生成与解析二维码

正题:Java使用QRCode.jar生成与解析二维码demo 欢迎新手共勉,大神监督指正 # 不知道QRCode的请移步wiki,自行了解,这里不多做解释 *******创建二维码之前的工作******** 去下面给出的地址下载QRCode.jar包,此jar包已经包括 生成与解析 . 官网下载到的jar包是没有解析的 https://files.cnblogs.com/files/bigroc/QRCode.zip ***创建好你的测试类导好jar包开始吧*** 第一部分:生成二维码 pac

使用zxing生成和解析二维码

二维码: 是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的: 在代码编制上巧妙的利用构成计算机内部逻辑基础的0和1比特流的概念,使用若干个与二进制相对应的几何形体来表示文字数值信息,通过图像输入设备或光电扫描设备自动识读以实现信息自动处理: 二维码能够在横向和纵向两个方位同时表达信息,因此能在很小的面积内表达大量的信息: 二维码相对于条形码的优势就是省空间: zxing简介: zxing是一个开放源码的,用Java实现的多种格式的1D/2D条码图像处理库

C#使用zxing,zbar,thoughtworkQRcode解析二维码,附源代码

最近做项目需要解析二维码图片,找了一大圈,发现没有人去整理下开源的几个库案例,花了点时间 做了zxing,zbar和thoughtworkqrcode解析二维码案例,希望大家有帮助. zxing是谷歌开源的二维码库,zbar,thoughtworkQRcode也是开源的,三者之间比较各有优劣 下面通过一个案例demo源码,来认识学习下这三者的实际解码效果, 第一次上传demo源码,献丑了 zbar解析关键代码: Image primaryImage = Image.FromFile(fileNa