OPENCV学习笔记 - SIFT 尺度不变特征变换 Python
树袋熊的前言
这个算法设计步骤之多,细节之复杂,让我整个人都想拿脑袋撞墙。刚开始在youtube上找了国外教授的CV课来理解,英语让我想撞完墙去跳河。然后在bilibili上找到了一个比较清晰的教程,感谢up主:会飞的吴克 https://www.bilibili.com/video/av47799675?p=2 有条件看视频的同学请移步,(教程部分图片不适合公共场合观看…emmmm…)然后其中遇到不懂的地方又查询了博主https://blog.csdn.net/dcrmg/article/details/52561656的博文!
虽然说对算法大概的思想和步骤有了理解,但很多细节仍有许许多多不清楚,远远没到可以看懂/写出源码的程度!要继续加油鸭~
为什么我们需要SIFT尺度不变特征变换?
OPENCV TUTORIAL 的解释(图三)
在前面两节我们学习了一些角点检测技术,比如 Harris 等。它们具有旋转不变特性,即使图片发生了旋转,我们也能找到同样的角点。很明显即使图像发生旋转之后角点还是角点。那如果我们对图像进行缩放呢?角点可能就不再是角点了。以下图为例,在一副小图中使用一个小的窗口可以检测到一个角点,但是如果图像被放大,再使用同样的窗口就检测不到角点了。
更进一步的理解(图一/二)
我们用摄像机拍摄的照片,往往遵循近大远小,近处清晰,远处模糊的定理。也就是说,除非是从图一的45°视角拍摄的图片(图一)可以用单一大小的窗口实现检测,其他具有透视的照片(图二)我们都需要对不同的角点使用不同的窗口,以实现更加精确的加测,这就是SIFT要实现的目标
.
.
.
.
.
第一,建立高斯差分金字塔
补充:我们使用高斯金字塔主要是为了模拟摄像机中近大远小,近处清晰远处模糊的成像原理。且有数学证明,仅高斯模糊可以达到这个效果,任何其他的模糊算子都不能模拟摄像机的成像。
高斯金字塔构建过程:
-
先将原图像扩大一倍之后作为高斯金字塔的第1组第1层,将第1组第1层图像经高斯卷积(其实就是高斯平滑或称高斯滤波)之后作为第1组金字塔的第2层,高斯卷积函数为:
-
对于参数σ,在Sift算子中取的是固定值1.6。
-
将σ乘以一个比例系数k,等到一个新的平滑因子σ=k*σ,用它来平滑第1组第2层图像,结果图像作为第3层。
-
如此这般重复,最后得到L层图像,在同一组中,每一层图像的尺寸都是一样的,只是平滑系数不一样。它们对应的平滑系数分别为:0,σ,kσ,k2σ,k3σ……k^(L-2)σ。
-
将第1组倒数第三层图像作比例因子为2的降采样,得到的图像作为第2组的第1层,然后对第2组的第1层图像做平滑因子为σ的高斯平滑,得到第2组的第2层,就像步骤2中一样,如此得到第2组的L层图像,同组内它们的尺寸是一样的,对应的平滑系数分别为:0,σ,kσ,k2σ,k3σ……k^(L-2)σ。但是在尺寸方面第2组是第1组图像的一半。
这样反复执行,就可以得到一共O组,每组L层,共计O*L个图像,这些图像一起就构成了高斯金字塔,结构如下:
高斯差分金字塔的构建过程:
然后我们把同一层相邻两层相减就得到了高斯金字塔
这些长得黑乎乎的图像就是差分金字塔的实际显示效果,只在第1组第1层差分图像上模糊可以看到一个轮廓。但其实这里边包含了大量特征点信息,只是我们人眼已经分辨不出来了。
下边对这些DOG图像进行归一化,可有很明显的看到差分图像所蕴含的特征,并且有一些特征是在不同模糊程度、不同尺度下都存在的,这些特征正是Sift所要提取的“稳定”特征:
第二,极值点的精确定位
1. 阈值化(去除那些噪声极致点)
如果 abs(val)>0.5*T/n,那么,就认为这些点是噪声而不是极值点
其中T=0.04(经验值)
其中n=每一组的高斯差分金字塔中要取特征的图片层数(即高斯金字塔一组层数-3)
2. 在高斯差分金字塔中寻找极致点
去除噪声点后,我们就可以在查分金字塔的三层中寻找极值点了。对于图像中的一个像素点而言,它需要与自己周围的 8 邻域,以及尺度空间中上下两层中的相邻的 18(2x9)个点相比。如果是局部最大值,它就可能是一个关键点。基本上来说关键点是图像在相应尺度空间中的最好代表。
3. 在高斯差分金字塔中寻找极致点
但是,因为图像信息本来就是离散的,而层数之间的σ也是离散的,所以我们找的并不是真正的极值点,而只是真正极值点周围的一个点。为了找到准确的极值点,我们对极值点进行三元二阶的泰勒展开如下
(哭了,并不知道这里为什么要用泰勒展开式来求得精确的极值点,哪位大神能告诉我!)
然后我们进行求导,来确定这个展开式的极值点
注:其中的计算遵循有限差分求导
4. 舍去低对比度的点
如果 abs(f(x))<T/n,则舍去
其中 T=0.04
其中n=每一组的高斯差分金字塔中要取特征的图片层数(即高斯金字塔一组层数-3)
5.边缘效应的去除
-
这一步我们希望去除那些是edge而不是corner的点,(同harris corner中的概念),也就是说我们希望曲线的两个曲率相差不大。
-
在数学中,Hessian matrix 的特征值与曲线的曲率成正比,所以我们希望Hessian matrix(如下)的特征值相差不大。
Dxx(x,y)代表x的二阶偏导, Dxy(x,y)代表x,y的混合偏导 -
但是一般我们是不会作死去算特征值的,我们还是通过trace和det转换这个问题
-
设大的特征值为α,小的特征值为β,Γ = α/β
Tr(H) = Dxx + Dyy = α + β
Det(H) = DxxDyy-(Dxy)^2 = αβ
希望把 “α和β相差不大” 的问题转化成 “trace,det的不等式” -
若Det(H)<0,即α和β异号,直接抛弃(相差太多)
-
若Det(H)>0
我们希望 “α和β相差不大”,就是希望 "Γ尽量接近于1”(这里建议取Γ=10)
因为α>β,所以Γ>1,而上式在[1, +∞]上单调递增
所以转换, "Γ尽量接近于1” 为以下等式
第三,确定关键点的主方向
现在我们要为每一个关键点赋予一个反向参数,这样它才会具有旋转不变性。获取关键点(所在尺度空间)的邻域,然后计算这个区域的梯度级和方向。根据计算得到的结果创建一个含有 36 个 bins(每 10 度一个 bin)的方向直方图。(使用当前尺度空间 σ 值的 1.5 倍为方差的圆形高斯窗口和梯度级做权重)。直方图中的峰值为主方向参数,如果其他的任何柱子的高度高于峰值的80% 被认为是辅方向。这就会在相同的尺度空间相同的位置构建除具有不同方向的关键点。这对于匹配的稳定性会有所帮助。
第四,构建关键点的描述符
新的关键点描述符被创建了。选取与关键点周围一个 16x16 的邻域(这个领域不是直接在高斯金字塔中选取的,而是在讲其转到主方向后再选取的,为了保证旋转不变性)。把邻域 16 个 4x4 的小方块,为每个小方块创建一个具有 8 个 bin 的方向直方图(描述这个小方块的梯度方向的分布)。总共加起来有 128 个 bin。由此组成长为 128 的向量就构成了关键点描述符。
下一步就可以采用关键点特征向量的欧式距离来作为两幅图像中关键点的相似性判定度量。取第一个图的某个关键点,通过遍历找到第二幅图像中的距离最近的那个关键点。但有些情况下,第二个距离最近的关键点与第一个距离最近的关键点靠的太近。这可能是由于噪声等引起的。此时要计算最近距离与第二近距离的比值。如果比值大于 0.8,就忽略掉。这会去除 90% 的错误匹配,同时只去除 5% 的正确匹配。如文章所说。
这就是 SIFT 算法的摘要。非常推荐你阅读原始文献,这会加深你对算法的理解。请记住这个算法是受专利保护的。所以这个算法包含在 OpenCV 中的收费模块中。
代码
这里我用的摄像头哈
import cv2
import numpy as np
cap = cv2.VideoCapture(0)
while True:
_, frame = cap.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
sift = cv2.xfeatures2d.SIFT_create()
kp = sift.detect(gray, None)
frame = cv2.drawKeypoints(gray, kp, frame, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
cv2.imshow('frame', frame)
if cv2.waitKey(5) & 0xff == 27:
break
cv2.destroyAllWindows()
cap.release()
结果
那个小轴轴就是梯度方向!!神!奇!然后可以看到前景(我的头发等)中景(桌上的杂物)和远景(墙上的灯)都识别的很好。