SIFT特征
SIFT:尺度不变特征变换
你好! 这是我对于 SIFT原理 的学习理解,记录的是关键结论,有理解错误的地方麻烦指出。如果你想SIFT的原理推导, 可以学习下面这个视频。
学习参考自:鲁鹏计算机视觉bilibil视频
提出原因
- 目标:能在不同尺度下检测出同一个特征点,且不受亮度、对比度、旋转、仿射变换影响
- 起初LoG 高斯拉普拉斯算子,可以检测边缘且具有尺度不变性。简单理解是:用高斯的二阶导的卷积核,卷积之后在某个大小的方差 σ \sigma σ会取得极值 ,检测区域半径r= 2 σ \sqrt[]{2}\sigma 2σ;
- 由于LoG计算量比较大,提出用DoG高斯差分算子来近似替代;
- D o G ( σ ) = G ( x , y , k σ ) − G ( x , y , σ ) DoG(\sigma )=G(x,y,k\sigma )-G(x,y,\sigma ) DoG(σ)=G(x,y,kσ)−G(x,y,σ);
1. 尺度空间极值检测Scale-space Extrema Detection
为了用尽量少的计算得到连续尺度的卷积结果,建立高斯差分金字塔。
s:每组输出的层数
k:尺度因子 k =
2
1
s
2^{\frac{1}{s} }
2s1 (这样设置是为了最后输出的尺度连续)
高斯金字塔组内:
- 每组内有s+3层
- 每组得到高斯差分s+2层,因为对空间上相邻27个点非极大值抑制,得到s个尺度(每3层的中间层中取最大点)
- 由
σ
\sigma
σ层求
k
σ
k\sigma
kσ:
- 由于大高斯核可以由小高斯核叠加实现,且能减少计算量。
- 符合勾股定理:例如方差5的高斯卷积 = 方差3 * 方差 4。
- 用 k 2 − 1 σ \sqrt{k^2-1}\sigma k2−1σ的卷积核,在原本 σ \sigma σ上卷积可以得到 k σ k\sigma kσ。
高斯金字塔组间:
- 第二组的第一层,从第一组的上往下数第3层下采样得到,依此类推。(因为该层 k n = 2 s 1 s = 2 ) k^{n } = 2^{s\frac{1}{s}} = 2) kn=2ss1=2)。
- 因为更大的 σ \sigma σ,意味着更大的卷积核,更多的计算量。我们可以通过下采样缩小图片减少计算量,同时使用 σ \sigma σ的卷积,来近似 2 σ 2\sigma 2σ的效果。
2. 关键点定位Keypoint Localization
目的:剔除弱变化点、边缘点
- 极值点的强度小于阈值(论文中为0.03),则拒绝该极值点。
- 对改变量E进行泰勒展开(Harris角度的内容), E ( u , v ) = ( u v ) M ( u v ) E(u,v)=\begin{pmatrix}u & v \end{pmatrix} M \binom{u}{v} E(u,v)=(uv)M(vu), 再对M进行对角化,比较两个特征值大小,如果一个远大于另一个则为边缘,两个都大则为角点。这里比值大于10的点被筛去。
3. 定向分配Orientation Assignment
目的:方向归一化
- 对关键点周围正方形窗口(例如16x16)的每个像素点求梯度方向。
- 每10度为1个方向,共36个方向,建立梯度直方图(还要进行特征点尺度 σ \sigma σ的1.5倍的高斯加权),最高峰为主方向。
- 高于最高峰80%的作为辅方向
4. 关键点描述符Keypoint Descriptor
- 关键点周围有一个16x16的邻域。它被分成16个4x4大小的子块。对于每个子块,计算每个像素的梯度,统计成8维的(每个45度)直方图。因此,表示成一个4 x 4 x 8 = 128维向量。
5. 关键点匹配Keypoint Matching
- 比较时,用两个点对应的8维向量逐个计算欧式距离。
通过识别两幅图像之间的最近邻居来匹配关键点。但在某些情况下,第二个最接近的匹配可能非常接近第一个。它可能是由于噪音或其他原因发生的。在这种情况下,计算最近距离与次最近距离的比率。如果大于0.8,则拒绝。根据论文,它消除了大约90%的错误匹配,而只丢弃了5%的正确匹配。1
6. opencv实现sift特征提取
import cv2
import os
import numpy as np
##sift特征点提取
#图片所在目录
direct_images = r"C:\Users\huangq\Desktop\images"
#获取目录下所有文件
files = os.listdir(direct_images)
#过滤出所有jpg文件
jpg_files = [f for f in files if f.endswith('.JPG')]
#读取并处理每个jpg
# SIFT特征点检测器
sift = cv2.SIFT_create()
# SIFT处理代码
keypoints_and_descriptors = {}
for file in jpg_files:
#地址合并
file_path = os.path.join(direct_images, file)
image = cv2.imread(file_path)
#图上检测SIFT特征点
keypoints, descriptors = sift.detectAndCompute(image, None)
#存储
keypoints_and_descriptors[file] = {'keypoints': keypoints, 'descriptors': descriptors}
# #绘制
image_with_keypoints = np.copy(image)
cv2.drawKeypoints(image, keypoints, image_with_keypoints, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
# #显示图像
width = 1080 #image.shape[1]
height = int(image.shape[0] * (width / image.shape[1]))
resized_image = cv2.resize(image_with_keypoints, (width, height))
cv2.imshow("SIFT KeyPoints", resized_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
输出效果:
7. opencv实现sift特征匹配
##sift特征点匹配
bf = cv2.BFMatcher()
a = keypoints_and_descriptors[jpg_files[0]]['descriptors']
b = keypoints_and_descriptors[jpg_files[1]]['descriptors']
matches = bf.knnMatch(a,b,k=2)
good = []
for m,n in matches:
if m.distance < 0.75*n.distance:
good.append([m])
kp1 = keypoints_and_descriptors[jpg_files[0]]['keypoints']
kp2 = keypoints_and_descriptors[jpg_files[1]]['keypoints']
img1 = keypoints_and_descriptors[jpg_files[0]]['image']
img2 = keypoints_and_descriptors[jpg_files[1]]['image']
img3 = cv2.drawMatchesKnn(img1,kp1,img2,kp2,good,None,flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
# plt.imshow(img3),plt.show()
# cv2.imshow("SIFT KeyPoints", img3)
# cv2.waitKey(0)
cv2.namedWindow('image', cv2.WINDOW_NORMAL) # 创建可调整大小的窗口
cv2.imshow('image', img3) # 显示图像
cv2.waitKey(0)
cv2.destroyAllWindows()
输出效果:
https://docs.opencv.org/4.5.4/da/df5/tutorial_py_sift_intro.html ↩︎