SIFT角点检测
关于Sift角点检测,我会从三个方面详细的介绍SIFT算法:1 SIFT简介 2 SIFT算法步骤(这里要分四个小的方面进行介绍) 3 SIFT算法实现的结果分析,下面就开始进行本文的论述
SIFT简介
尺度不变特征转换即SIFT (Scale-invariant feature transform)是一种计算机视觉的算法。它用来侦测与描述影像中的局部性特征,它在空间尺度中寻找极值点,并提取出其位置、尺度、旋转不变量,此算法由 David Lowe在1999年所发表。(SIFT算法的实质是在不同的尺度空间上查找关键点(特征点),并计算出关键点的方向。SIFT所查找到的关键点是一些十分突出,不会因光照,仿射变换和噪音等因素而变化的点,如角点、边缘点、暗区的亮点及亮区的暗点等。)
SIFT算法实现步骤
DOG尺度空间构造
在论述DOG尺度空间之前,我们先学习这几个概念,说的不是很透彻,但是足以让你恍然大悟。首先论述图像尺度的概念。图像处理中的“尺度”可以理解为图像的模糊程度,当一幅图像被放大时,此时图像的细节信息越少。高斯函数是图像处理中对图像构造尺度空间的方法,是使用不同的sigma值(σ)的高斯核对图像进行卷积,已达到对图像进行尺度处理。如图所示,G(x,y,σ)是高斯核函数,L(x,y,σ)是对应σ值尺度处理下的尺度图像,其中𝐼(𝑥,𝑦)是原图像。
对原图像用不同值的sigma进行处理,就会得到不同尺度的图像,也称为不同模糊程度的图像。实验结果如图所示:
到这就介绍了应用高斯函数对图像进行尺度(模糊)处理!
然后再接着介绍图像金字塔的概念:
假设我们有一幅128×128像素的图像,图像金字塔就是对原图像进行下采样处理,比如比例因子(采样因子)为2,就是対一幅图像每隔一行一列取一个点。对于n×n的图像就变为n/2×n/2的图像了。128×128像素的图像就变成了64×64的图像。这里原图像是保留原特征的,会在下一层产生采样后的图像。然后在对第二层图像进行下采样处理,就会产生第三层(高和宽减半)的图像,SIFT算法中用的下采样因子为2,所以图像金字塔的图像尺度逐层减半,其他的比例因子另当别论。(大致这就是图像金字塔了,图像金字塔的层数不是无限延伸的,同样根据自己的需求来设定),如下图所示就是图像金字塔:
综上所述:我们介绍了高斯函数和图像金字塔。高斯函数可以对图像进行不同程度的模糊处理,图像金字塔通过下采样可以生成类似金字塔形状的不同规模尺度的图像。现在让我们结合这两个概念。现在就有了一个新的概念:高斯图像金字塔。高斯图像金字塔融合了上面介绍的两个概念,最后的结论就是对原始图像进行下采样并且对得到的图像进行了一定的模糊处理,是不是一下子就清楚了许多~~!(这里还要补充的就是高斯图像金字塔同样会有很多的层,但是每层又有很多张图像,不同的层级之间通过下采样得到,每层之间的图像又通过不同的高斯核函数处理),高斯金字塔的例图如下图所示:
现在终于到了我们的第一个关键内容:DOG尺度空间的构造。综上我们介绍了高斯图像金字塔,高斯金字塔会得到很多组的图像,而每组又有很多张的图像,在每个层级上面,图像尺度(长宽)是相同的,但是图像的模糊程度不同。DOG(高斯差分尺度空间):就是利用高斯金字塔每层上面的很多图像进行差分(就是下一张图像减上一张图像),因为同层级的不同sigma处理的图像相减,所以相减后的图像保留了原始图像的很多细节特征。高斯图像差分的数学表达式为:𝐷(𝑥,𝑦,σ)=(G(x,y,kσ)−G(x,y,σ))∗I(x,y)=L(x,y,kσ)−L(x,y,σ) (注:其中𝐼(𝑥,𝑦)是原图像,*是卷积符号,L(x,y,σ)对应尺度下的尺度图像)。到这里其实我们就构造了DOG尺度空间,如图所示:
空间极值点检测
关键点(极值点)是由DoG空间的局部极值点组成的。中间画×的检测点和他同尺度的8个相邻点和上下相邻尺度对应的9乘2个点,共26个点比较,以确保在尺度空间和二维图像空间都检测到极值点。(注:极值点的检测只在同一组中从第2层开始至倒数第2层为止的相邻层进行26个点DoG值的比较寻找极大值和极小值)空间极值点检测的例图如下图所示:
到这的话空间极值点检测就论述完了,其实也就是在构造的DOG尺度空间内对层级之间进行关键点检测,找到符合设定条件的关键点(极值点)。(文章末尾我会附上我查看的文献和文章,不懂得朋友可以自己消化)
关键点搜索与定位
可能有的同学疑问上面不是在尺度空间中找到了极值点,为什么还要搜索与定位呢?因为以上极值点的搜索时在离散空间中进行的,检测到的极值点并不是真正意义上的极值点。如下图所示,连续空间中极值与离散空间的区别。通常通过插值的方式(三维二次函数来精确确定关键点的位置和尺度),利用离散的值来插值,求取接近真正的极值的点。同时去除低对比度的关键点和不稳定的边缘响应点。下图显示了二维函数离散空间得到的极值点与连续空间极值点的差别。
空间离散的变换到连续空间的转换要通过插值的方式。这里就要介绍几个数学的概念了:利用DoG函数在尺度空间的Taylor展开式(插值函数)进行插值,对于一维函数,利用泰勒级数,将其展开为二次函数:f(𝑥)≈𝑓(0)+𝑓^′ (0)𝑥+𝑓^′′ (0) 𝑥^2
,对于二维函数,泰勒展开为:
二维二次函数矩阵表示为:
求取f(x)的极值,只需求取∂f/∂x = 0。对于极值点,有x,y,σ三个变量,即为三维空间。利用三维子像元插值,设其函数为D(x, y, σ),令x = (x, y, σ)T(T为转置矩阵),那么在离散空间中找到的极值点进行泰勒展开为:
综上所述,根据空间寻找的极值点作为插值来求连续空间的极值点,已经找到了真的极值点的偏移量,那么最终极值点的位置即为 x+ 𝑥 ̂ 。(极值点的位置就已经确定了)现在已经有了真的极值点的位置坐标,针对于DoG响应较低的极值点(极值较小的点),还要剔除这些点。只需要计算真的极值点的响应值𝐷(𝑥 ̂),响应值小于一定的阈值,即认为该点效应较小,将其剔除。 𝐷(𝑥 ̂)的响应表达式为:(将𝑥 ̂ 的值带入D(x)中)。这里我认为是:比如一个凸凹不平的连续曲线,最原函数的凸点和凹点都是导函数为0的点,也就是极值点,但是凸凹程度各有不同,比如凸点(极大值点)的峰值为20,但也可能存在峰值为2的极大值点,所以要滤除这些极值点,极小值点也同样的道理。这样还不够,为了精准的检测到角点特征,还需要滤除边缘特征的影响。边缘特征与主曲率有关,一个定义不好的高斯差分算子的极值在横跨边缘的地方有较大的主曲率,而在垂直边缘的方向有较小的主曲率。主曲率通过一个2x2 的Hessian矩阵H求出:
可以由二阶差分计算得到,那么接下来计算特征值了。假设两个特征值分别为α,β,看下面两个式子:
其中Tr(H)和Det(H)分别为矩阵的迹和矩阵的行列式。假设α为较大的特征值,且α=γβ,(γ≥1),则:
(r + 1)*2/r的值在两个特征值相等的时候最小,随着r的增大而增大,因此,为了检测主曲率是否在某域值r下,只需检测:因为𝛼远大于𝛽或者𝛽远大于𝛼,这样的点对应的就是边缘,所以要设定𝑟阈值,在Lowe的文章中,取r=10。这样就可以去除边缘影响检测角点特征了~~(到这里真正极值点的空间位置坐标就确定了)
方向幅值
在上文中,已经在DoG中剔除了响应低的极值点和边缘点等特征,但是还不够,还需要考虑“旋转适应性”。利用关键点邻域像素的梯度方向分布特性为每个关键点指定方向参数。某各像素点的x方向的梯度的计算公式可以通过这个像素的左右两边的像素值的差值来计算,而y方向的梯度可以通过该像素点的上下两个像素值的差值来计算。空间极值点 x 方向梯度和y 方向梯度计算如图所示:
有了梯度方向和幅值后,计算每个像素点的的梯度方向和幅值,在这里把梯度方向0-360°分成10°每份,创建了一个具有36个覆盖360度的bin的方向直方图。在实际计算中,我们在以关键点为中心的邻域窗口内采样,并用直方图统计邻域像素的梯度方向。梯度直方图的范围是0~360度,其中每45度一个柱,总共8个柱。论文中还提到要使用高斯函数对直方图进行平滑,减少突变的影响。直方图的峰值则代表了该关键点处邻域的主方向,即作为该关键点的方向。直方图中的峰值就是主方向,并且将其超过80%的任何峰也视为计算方向的辅助方向。它创建的位置和比例相同但方向不同的关键点。它有助于匹配的稳定性。
这里还涉及到旋转性,因为这块涉及挺多内容,就没论述,但是整体大概已经概述完整。(到这里,上一节介绍了极值点的位置,这节极值点的梯度方向也已经确定了)
关键点描述
综上,我们已经对关键点位置,尺度,方向有了了解。接下来就是为每个关键点建立一个描述符,使其不随各种变化而变化,比如,光照变化和视角变化等。接下来以关键点为中心取8×8的窗口。下图左部分的中央黑点为当前关键点的位置,每个小格代表关键点邻域所在尺度空间的一个像素,箭头方向代表该像素的梯度方向,箭头长度代表梯度模值,图中蓝色的圈代表高斯加权的范围(越靠近关键点的像素梯度方向信息贡献越大)。
然后在每4×4的小块上计算8个方向的梯度方向直方图,绘制每个梯度方向的累加值,即可形成一个种子点,如图右部分所示。此图中一个关键点由2×2共4个种子点组成,每个种子点有8个方向向量信息。实际计算过程中,为了增强匹配的稳健性,Lowe建议对每个关键点使用4×4共16个种子点来描述,这样对于一个关键点就可以产生128个数据,即最终形成128维的SIFT特征向量。
此时SIFT特征向量已经去除了尺度变化、旋转等几何变形因素的影响,再继续将特征向量的长度归一化,则可以进一步去除光照变化的影响。
算法小结
到这里SIFT算法就介绍完了,首先通过差分金字塔构造尺度空间,然后再尺度空间进行关键点的搜索与定位然后计算极值点的位置,空间方向幅值等参数,计算完这些好要有空间向量来描述这些关键点,这就是SIFT算法的原理。
SIFT算法实现结果分析
这个算法是受专利保护的,我的opencv版本是4以上的,高版本不支持这个算法,所以要降低cv的版本,我查阅相关资料,是安装了3.2的版本,可以运行。
卸载现有的高版本: pip uninstall opencv-python
安装3.4.2.16版本(这里我的python是3.7,可以使用):
pip install opencv-python==3.4.2.16
pip install opencv-python==3.4.2.16 -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install opencv-contrib-python==3.4.2.16
2022年4月9号更新
最近使用该函数,发现卸载opencv高的版本,仍然不能使用sift算法的问题,大概的问题就是cv2中找不到xfeatures2d函数,我也是纠结了很久,最后还是解决了。解决方案:opencv-python使用3.4.2.16版本,opencv-contrib-python使用4.5.3.56版本就可以使用sift以及surf特征匹配函数了。(你还需要做的就是,如果是用这种一高一低的版本,我推荐的组合:建议代码中的::sift = cv2.xfeatures2d.SIFT_create(600)替换成sift = cv2.SIFT_create(600)是一样的效果与作用!!!)
然后就可以使用sift算法了
python-opencv
import cv2
import numpy as np
def sift_kp(image):
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
sift = cv2.xfeatures2d.SIFT_create(600)
#SIFT算子实例化
kp, des = sift.detectAndCompute(image, None)
# sift.detectAndComputer(gray, None)
# 计算出图像的关键点和sift特征向量
kp_image = cv2.drawKeypoints(gray_image, kp, None)
#ret = cv2.drawKeypoints(gray, kp, img) 在图中画出关键点
#参数说明:gray表示输入图片, kp表示关键点,img表示输出的图片
return kp_image, kp, des
def get_good_match(des1, des2):
bf = cv2.BFMatcher()
#对两个匹配选择最佳的匹配
matches = bf.knnMatch(des1, des2, k=2)
# des1为模板图,des2为匹配图
matches = sorted(matches, key=lambda x: x[0].distance / x[1].distance)
good = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
good.append(m)
return good
img1 = cv2.imread(r'lou.jpg')
img2 = cv2.imread(r'lou1.jpg')
kpimg1, kp1, des1 = sift_kp(img1)
kpimg2, kp2, des2 = sift_kp(img2)
print('descriptor1:', des1.shape, 'descriptor2', des2.shape)
#输出特征点(描述子)的维度
cv2.imshow('img1',np.hstack((img1,kpimg1)))
cv2.imshow('img2',np.hstack((img2,kpimg2)))
goodMatch = get_good_match(des1, des2)
all_goodmatch_img= cv2.drawMatches(img1, kp1, img2, kp2, goodMatch, None, flags=2)
#drawMatches(图1,图1特征点,图2,图2特征点,图1的特征点匹配图2的特征点(所有),颜色都是默认,flags表示有几个图像)
# goodmatch_img自己设置前多少个goodMatch[:50]
goodmatch_img = cv2.drawMatches(img1, kp1, img2, kp2, goodMatch[:50], None, flags=2)
cv2.imshow('all_goodmatch_img', all_goodmatch_img)
cv2.imshow('goodmatch_img', goodmatch_img)
cv2.namedWindow('image2',cv2.WINDOW_NORMAL)
cv2.imshow("image2",goodmatch_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
实验结果
(这里还涉及到角点的匹配算法,文中没有说,还是希望感兴趣的小伙伴可以自行翻阅)
到这里SIFT角点检测算法就介绍完了,理解是一种能力,写出来又需要更深的理解,总之还是写的不够全面,只是大概介绍一下,希望可以帮到有需要的同学~~
参考文献
https://blog.csdn.net/weixin_44072651
https://www.cnblogs.com/JiePro/p/sift_2.html
https://wenku.baidu.com/view/87270d2c2af90242a895e52e.html?re=view
https://blog.csdn.net/zhangziju/article/details/79754652
https://sc.panda321.com/scholar?hl=zh-CN&as_sdt=0%2C5&q=sift+lowe&btnG=