在OpenCV里使用SIFT

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/caimouse/article/details/102656666

目标:学习SIFT的算法和SIFT关键点、及特征。

 

理论:

在前面我们学习图像特征检测--角点检测,比如Harris角检测。不过,前面学习的角点检测,只是具有旋转不变性,比如图片进行旋转了,还可以从图像里找到相应的角点。因为图像进行旋转了,角的朝向也跟着旋转,所以没有改角的组成。如果把角进行放大,是否还具有不变性呢?下面就来讨论一下这个问题,其实把图像里的角进行放大之后,角点有可能就不是一个角了。举一个例子来说明,如下图:

在上图里,当把左边的小图进行放大,然后还用相同的窗口大小去滑动检测角时,发现在小窗口里已经不是一个角了。因此,Harris角检测不具有放大不变的特征。

所以在2004年,D.Lowe提出一个新的算法Scale Invariant Feature Transform (SIFT),这个算法在论文《Distinctive Image Features from Scale-Invariant Keypoints》里发表。这个算法主要有以下四步:

  1. 尺度空间极值检测( Scale-space Extrema Detection)

从上面讨论可知,对于检测不同缩放下图像的关键特征就不能使用相同的窗口大小了,对于小图像的角要用小窗口,对于放大图像要使用大窗口。对于这个处理就叫做尺度空间滤波器。在尺度空间滤波里,主要使用高斯拉普拉斯变换(LoG)的不同σ值来实现,LoG其实就是块检测器,通过改变它的不同σ值就可以不同大小尺度空间里检测块。也就是说,σ值就是当作尺度变换系数。在上面图像例子里,我们可以使用一个小σ值的高斯核来计算小图像的极值,而使用一个大σ值的高斯核来计算大图像的极值。因此,我们可以通过(x,y,σ)来表示不同尺度空间里的不变的关键点,就是在σ尺度空间里坐标点(x,y)。由于LoG计算非常费时间,所以在SIFT算法里使用高斯差分变换DoG(Difference of Gaussians)来近似替换LoG。高斯差分变换是一幅图像采用不同σ值的高斯平滑计算得来,可以假定其中一个是使用σ,另一个是使用kσ。在整个处理过程中,每组图像里使用了高斯金字塔来实现,如下图所示:

    如果不是很懂这个,可以参考前面《在OpenCV里生成图像金字塔》,可以表示为下图:

第一排是同一组的高斯平滑的照片,这样就可以计算下面边缘的DoG。当DoG计算出来之后,就可以在里面寻找尺度空间里的局部极值点。例如,一个图像中的一个像素点就可以与它相邻元素进行比较,与同一个平面里8个相邻元素进行比较,再与相邻两个平面里各9个元素进行比较,如果它是一个极值,那么就认为它是一个潜在的关键点,也基本上认为这个点就是在这个空间里是最佳表示了,比较周围元素如下图所示:

该算法的作者在论文中给出了 SIFT 参数的最优经验值: octaves=4(通过降低采样从而减小图像尺寸,构成尺寸减小的图像金字塔(4 层)),尺度空间为 5,也就是每个尺寸使用 5 个不同方差的高斯核进行卷积,初始方差是 1.6,k 等于2的开方等。   

  1. 关键点定位(key point localization)

一当找到所有可能潜在关键点之后,需要更进一步进行筛选。第一步利用DoG函数在尺度空间的Taylor展开式(拟合函数),获得极值更准确的位置,如果它的极值强度小于某一个值(论文里用0.03),就要把该点删除,在OpenCV把这个值叫做contrastThreshold。由于DoG对边缘信息很敏感,因而接着下一步就是想办法把边缘信息也去掉。这里采用一个很像Harris角检测的方式,他们用2x2的hessian矩阵(h)来计算主曲率。我们知道Harris角检测中是一个特征值大于另一个特征值,在这里他们也使用一个类似的功能,采用H矩阵的特征来计算比率,如果比率大于指定的阈值,就要把这点删除。在OpenCV里,把这个比率叫做edgeThreshold ,论文建议阈值为10。

其中r就是edgeThreshold值。

当删除上面提到两类极点之后,就剩下最好的极点了。

  1. 方向分配(orientation assignment)

为了达到图像旋转不变性,需要使用一个角度来表示关键点方向。根据尺度空间在关键点位置周围取一个邻域,并计算该区域的梯度大小和方向。对于在DOG金字塔中检测出的关键点点,采集其所在高斯金字塔图像3σ邻域窗口内像素的梯度和方向分布特征。在完成关键点的梯度计算后,使用直方图统计邻域内像素的梯度和方向。梯度直方图将0~360度的方向范围分为36个柱(bins),其中每柱10度。如下图所示,直方图的峰值方向代表了关键点的主方向,(为简化,图中只画了八个方向的直方图)。

方向直方图的峰值则代表了该特征点处邻域梯度的方向,以直方图中最大值作为该关键点的主方向。为了增强匹配的鲁棒性,只保留峰值大于主方向峰值80%的方向作为该关键点的辅方向。因此,对于同一梯度值的多个峰值的关键点位置,在相同位置和尺度将会有多个关键点被创建但方向不同。仅有15%的关键点被赋予多个方向,但可以明显的提高关键点匹配的稳定性。

  1. 关键点描述(Key Point descriptor)

通过以上步骤,对于每一个关键点,拥有三个信息:位置、尺度以及方向。接下来就是为每个关键点建立一个描述符,用一组向量将这个关键点描述出来,使其不随各种变化而改变,比如光照变化、视角变化等等。这个描述子不但包括关键点,也包含关键点周围对其有贡献的像素点,并且描述符应该有较高的独特性,以便于提高特征点正确匹配的概率。 SIFT描述子是关键点邻域高斯图像梯度统计结果的一种表示。通过对关键点周围图像区域分块,计算块内梯度直方图,生成具有独特性的向量,这个向量是该区域图像信息的一种抽象,具有唯一性。论文建议描述子使用在关键点尺度空间内4*4的窗口中计算的8个方向的梯度信息,共4*4*8=128维向量表征。

运行下面例子:

#python 3.7.4,opencv4.1
#蔡军生 https://blog.csdn.net/caimouse/article/details/51749579
#
import numpy as np
import cv2
from matplotlib import pyplot as plt

#读取文件
img = cv2.imread('szcen1.png')
gray= cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

sift = cv2.xfeatures2d.SIFT_create()
kp = sift.detect(gray,None)

img = cv2.drawKeypoints(gray,kp,img)
 

#显示图片
cv2.imshow('img',img)
           
cv2.waitKey(0)
cv2.destroyAllWindows()

会出错提示如下:

    sift = cv2.xfeatures2d.SIFT_create()

cv2.error: OpenCV(4.1.1) C:\projects\opencv-python\opencv_contrib\modules\xfeatures2d\src\sift.cpp:1207: error: (-213:The function/feature is not implemented) This algorithm is patented and is excluded in this configuration; Set OPENCV_ENABLE_NONFREE CMake option and rebuild the library in function 'cv::xfeatures2d::SIFT::create'

可以采用旧版本来解决:

pip install opencv-python==3.4.2.16

pip install opencv-contrib-python==3.4.2.16

 

可以参考的文章:

https://blog.csdn.net/u010440456/article/details/81483145

https://blog.csdn.net/caimouse/article/details/51749579

猜你喜欢

转载自blog.csdn.net/caimouse/article/details/102656666