这章以后的算法不包含在opencv-python中,需要卸载opencv-python,安装opencv-contrib-python的3.4.2.16版本,过程如下:
pip uninstall opencv-python
pip install opencv-contrib-python==3.4.2.16
目录
简介
SIFT算法特点与步骤
Lowe将SIFT算法分解为如下四步:
① 尺度空间极值检测
② 关键点定位
③ 方向确定
④ 关键点描述
关键函数
举例
下面讲解部分摘自:https://blog.csdn.net/g11d111/article/details/79925827
SIFT(Scale-invariant feature transform),也叫尺度不变特征变换算法,是David Lowe于1999年提出的局部特征描述子(Descriptor),并于2004年进行了更深入的发展和完善。Sift特征匹配算法可以处理两幅图像之间发生平移、旋转、仿射变换情况下的匹配问题,具有很强的匹配能力。在Mikolajczyk对包括Sift算子在内的十种局部描述子所做的不变性对比实验中,Sift及其扩展算法已被证实在同类描述子中具有最强的健壮性。
其应用范围包含物体辨识、机器人地图感知与导航、影像缝合、3D模型建立、手势辨识、影像追踪和动作比对。局部影像特征的描述与侦测可以帮助辨识物体,SIFT 特征是基于物体上的一些局部外观的兴趣点而与影像的大小和旋转无关。对于光线、噪声、些微视角改变的容忍度也相当高。基于这些特性,它们是高度显著而且相对容易撷取,在母数庞大的特征数据库中,很容易辨识物体而且鲜有误认。
SIFT算法的实质是:
“不同的尺度空间上查找关键点(特征点),并计算出关键点的方向” ,
SIFT所查找到的关键点是一些十分突出,不会因光照,仿射变换和噪音等因素而变化的点,如角点、边缘点、暗区的亮点及亮区的暗点等。
搜索所有尺度上的图像位置。通过高斯微分函数来识别潜在的对于尺度和旋转不变的兴趣点。
为了在不同的尺度空间检测关键点。这里,尺度空间的获取需要使用尺度空间滤波(scale-space filtering)来实现,Lindeberg等人已证明高斯卷积核是实现尺度变换的唯一变换核,并且是唯一的线性核。尺度规范化的LoG(Laplacion of Gaussian)算子具有真正的尺度不变性,但是由于LoG算法复杂度较高。因此,Lowe使用更为高效的高斯差分算子(Difference of Gaussians)近似LoG算子来进行极值检测,如下:
由上式可以看出,高斯差分算子(Difference of Gaussians)是使用两个不同的σ ,kσ来做高斯模糊差异而得到的。这里,∗*表示卷积操作,G(x,y,σ) 为一个变化尺度的高斯(Gaussian )函数,I(x,y) 表示原图像。
在实际计算时,使用高斯金字塔(Gaussian Pyramid)每组中相邻上下两层图像相减,得到高斯差分图像,如下图所示(关于高斯金字塔中的构建在内容延伸中会介绍):
m,n表示高斯模板的维度(由(6σ+1)(6σ+1)确定。x,y代表图像的像素位置。σ 是尺度空间因子,值越小表示图像被平滑的越少,相应的尺度也就越小。大尺度对应于图像的结构,小尺度对应于图像的细节纹理特征。
当我们得到DoG(Difference of Gaussian)之后,图像在尺度空间中搜寻局部极值(local extrema)。以下图为例,在图像中的某个像素点不但与其附近的8个像素点比较,而且与其前一层(previous scale)的9个像素点和下一层(next scale)的9个像素点进行比较(需为同一Octave)。如果该像素点是局部极值点,那么我们就认为它是一个潜在的KeyPoint(关键点)——最能代表这个scale的点。
在每个候选的位置上,通过一个拟合精细的模型来确定位置和尺度。关键点的选择依据于它们的稳定程度。
由第①步检测得到的极值点是离散空间的极值点。下面,通过拟合三维二次函数来精确确定关键点的位置和尺度,同时去除低对比度的关键点和不稳定的边缘响应点(因为DoG算子会产生较强的边缘响应),以增强匹配稳定性、提高抗噪声能力。
他们使用泰勒级数展开式空间来获得更精确的极值位置,如果这个极值点的强度小于阈值,它就会被拒绝。这个阈值在Opencv中为contrastThreshold。
因为DoG算子会产生较强的边缘响应,所以要去除这些不稳定的边缘响应点。与Harris角点检测的思路相似,获取特征点处的Hessian矩阵,主曲率(principal curvature)通过一个2x2 的Hessian矩阵H求出:
H的特征值α和β分别代表x和y方向的梯度,
这里,Tr(H)为主对角线元素之和,Det(H)表示矩阵H的行列式。假设是α较大的特征值,而β较小的特征值,令α=rβ,则
D的主曲率和H的特征值成正比,跟上面一样,让α为最大特征值,β为最小特征值,则公式(r+1)^2/r的值在两个特征值相等时最小,随着r的增大而增大。r值越大,说明两个特征值的比值越大,即在某一个方向的梯度值越大,而在另一个方向的梯度值越小,边缘恰恰就是一个方向梯度大,一个方向梯度小的情况。所以为了剔除边缘响应点,需要让该比值小于一定的阈值:
将满足条件的关键点保留,否则则扔掉。因此,低对比度和边缘的关键点在本步骤中被去除,只保留最感兴趣的那些点。
基于图像局部的梯度方向,分配给每个关键点位置一个或多个方向。所有后面的对图像数据的操作都相对于关键点的方向、尺度和位置进行变换,从而提供对于这些变换的不变性。
为了使描述符(Descriptors)具有旋转不变性,需要利用图像的局部特征为给每一个关键点分配一个基准方向。使用图像梯度的方法求取局部结构的稳定方向。对于在DoG中检测出的关键点,采集其所在高斯金字塔图像3σ 3σ3σ邻域窗口内像素的梯度和方向分布特征。梯度的模值和方向如下:
在完成关键点的梯度计算后,使用直方图统计邻域内像素的梯度和方向。梯度直方图将0~360度的方向范围分为36个柱(bins),其中每柱10度。如下图所示,直方图的峰值方向代表了关键点的主方向,(为简化,图中只画了八个方向的直方图)。
方向直方图的峰值则代表了该特征点处邻域梯度的方向,以直方图中最大值作为该关键(KeyPoint)的主方向。为了增强匹配的鲁棒性,只保留峰值大于主方向峰值80%的方向作为该关键点的辅方向。
实际编程实现中,就是把该关键点复制成多份关键点,并将方向值分别赋给这些复制后的关键点,并且,离散的梯度方向直方图要进行插值拟合处理,来求得更精确的方向角度值,检测结果如图所示。
至此,将检测出的含有位置、尺度和方向的关键点即是该图像的SIFT特征点。
在每个关键点周围的邻域内,在选定的尺度上测量图像局部的梯度。这些梯度被变换成一种表示,这种表示允许比较大的局部形状的变形和光照变化。
创建一个SIFT对象:
cv2.xfeatures2d.SIFT_create(, nfeatures, nOctaveLayers, contrastThreshold, edgeThreshold, sigma)
检测特征点:
sift.detect(image,keypoints) 或 keypoint = sift.detect(image, None)
Point2f pt:坐标
float size:特征点的邻域直径
float angle:特征点的方向,值为[0,360度),负值表示不使用
float response;
int octave:特征点所在的图像金字塔的组
int class_id:用于聚类的id
绘制特征点:
cv2.drawKeypoint(image, keypoints, outImage, color, flags)
或:outImage = cv2.drawKeypoint(image, keypoints, None, color, flags)
cv2.DRAW_MATCHES_FLAGS_DEFAULT:
默认值,只绘制特征点的坐标点,显示在图像上就是一个个小圆点,每个小圆点的圆心坐标都是特征点的坐标。
cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS:
绘制特征点的时候绘制的是带有方向的圆,这种方法同时显示图像的坐标,size,和方向,是最能显示特征的一种绘制方式。
cv2.DRAW_MATCHES_FLAGS_DRAW_OVER_OUTIMG:
只绘制特征点的坐标点,显示在图像上就是一个个小圆点,每个小圆点的圆心坐标都是特征点的坐标。
cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINT:
单点的特征点不被绘制
最后举个例子:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('test30.jpg')
img1 = img.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
sift = cv2.xfeatures2d.SIFT_create()
kp = sift.detect(gray, None)
cv2.drawKeypoints(gray, kp, img)
cv2.drawKeypoints(gray, kp, img1, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
plt.subplot(121), plt.imshow(img),
plt.title('Dstination'), plt.axis('off')
plt.subplot(122), plt.imshow(img1),
plt.title('Dstination'), plt.axis('off')
plt.show()
结果如下: