SIFT算法详细原理与应用
1 SIFT算法由来
1.1 什么是 SIFT?
SIFT,全称为 Scale-Invariant Feature Transform(尺度不变特征变换),是一种用于图像特征检测和描述的经典算法。它通过提取图像中的局部关键点,并为每个关键点生成具有尺度和旋转不变性的描述子,使其能够在不同的图像中进行特征匹配。SIFT 算法尤其适合处理视角变化、尺度变换、部分遮挡和光照变化的问题,因此被广泛应用于计算机视觉领域。通过sift算法可生成图像的特征向量(128维),对图像特征向量可以理解为图像的细节纹理特征,它具有光照,角度,尺寸大小不变性,一个特征向量可能为某个人脸图像的局部一个特征纹理,比如为人脸图像中鼻子的特征,不同人脸图像的鼻子特征是不同的,故,通过收集人脸图像的所有特征向量,可以进行人脸识别,图像匹配等场景。
1.2 SIFT 的发展历程
SIFT 由计算机科学家 David G. Lowe 于 1999 年首次提出,并在 2004 年发表的论文《Distinctive Image Features from Scale-Invariant Keypoints》中进一步完善。其革命性的设计使得 SIFT 成为了特征提取领域的重要里程碑。
虽然 SIFT 曾因专利保护限制了开源使用,但随着专利过期(美国专利于 2020 年到期),SIFT 再次成为开源社区的重要工具,并在许多实际项目中被广泛应用。
此外,SIFT 的思想也启发了许多后续算法的诞生,例如 SURF(Speeded-Up Robust Features)和 ORB(Oriented FAST and Rotated BRIEF),进一步推动了特征提取技术的发展。
2 SIFT算法详细原理
-
根据高斯函数生成高斯金字塔
生成高斯金字塔的作用可以理解为 通过相机调整距离物体的距离拍摄的物理,实际上对于物体本身,不管相机距离物体的距离远近,总有一些特征是不变的,比如物体的边缘及轮廓等。
高斯金字塔底层的图像可以理解为相机近距离拍摄物体的图像,顶层可以理解为相机远距离拍摄物体的图像。故sift具有图像尺寸不变性及旋转不变性(再生成种子时会进行角度旋转变换及归一化处理),而角点检测算法Harris只具有旋转不变性
通过模拟不同距离的图像,尽可能的找到图像的所有特征点 -
根据高斯金字塔生成差分金字塔(DOG)
通过对高斯金字塔同一组中相邻的图像进行相减,得到DOG差分图像 -
根据DOG差分图像找到关键点(极值点)
为了寻找尺度空间的极值点,每一个采样点要和它所有的相邻点比较,看其是否比它的图像域和尺度域的相邻点大或者小。如图所示,中间的检测点和它同尺度的8个相邻点和上下相邻尺度对应的9×2个点共26个点比较,以确保在尺度空间和二维图像空间都检测到极值点。 一个点如果在DOG尺度空间本层以及上下两层的26个领域中是最大或最小值时,就认为该点是图像在该尺度下的一个特征点,如图所示。 -
关键点过滤
真正的关键点都是灰度值在,x,y方向都有明显变化的,即:一个定义不好的高斯差分算子的极值在横跨边缘的地方有较大的主曲率,而在垂直边缘的方向有较小的主曲率。 -
根据找到的关键点计算出对应的描述子
- 计算关键点周围领域的直方图(包括变化尺度,角度(角度可以分为8个方向)),得出当前关键点的主方向(变换尺度最大的为主方向)
通过对每个关键点领域做直方图计算,可以得到该关键点的主方向及大小,由此每个关键点有三个基本的属性:(关键点位置,主方向(与x轴的角度),主方向的大小) - 对关键点周围领域进行旋转,将主方向旋转成与x方向一致的方向,以确保旋转不变性
- 将关键点领域分为4x4的 16个子区域,每个子区域分别计算他们的直方图,然后生成由右图组成的16个种子,每个种子包含八个方向的变化尺度。
- 生成描述子
根据关键点生成的16个种子的八维向量,组成一个4x4x8-128维的向量。再将特征向量进行归一化(就是使的特征向量的值1,成为单位向量)。即每一个关键点就能得到一个128维的向量,也成为一个关键点的描述子。
- 计算关键点周围领域的直方图(包括变化尺度,角度(角度可以分为8个方向)),得出当前关键点的主方向(变换尺度最大的为主方向)
-
两个图像描述子的匹配
通过sift算法分别生成两个图像的各自的多个描述子,分别计算两个图像描述子向量之间的距离,计算两个向量的距离有多种
计算两个向量的距离有多种方式- 曼哈顿距离欧氏距离
- 欧氏距离
- 切比雪夫距离
- 闵可夫斯基距离
- 标准化欧式距离
- 余弦距离
- 汉明距离
- 杰卡德距离
- 马氏距离
具体详见:9个机器学习算法常见距离计算公式
详见:
经典算法研究(1):SIFT算法1
SIFT算法详解
SIFT 全面解析:原理、实现与应用
经典的图像匹配算法----SIFT
3 SIFT算法应用
详见:OpenCV入门学习笔记之Harris角点检测与SIFT特征匹配算法
3.1 cv2中sift算法的使用
""" SIFT,全称为 Scale-Invariant Feature Transform(尺度不变特征变换)"""
src_img = cv2.imread('resources/sift_2.jpg')img = copy.deepcopy(src_img)
gray1 = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)gray = copy.deepcopy(gray1)# SIFT检测器
sift = cv2.SIFT_create()# 找出图像中的关键点
kp = sift.detect(gray, None)# 在图中画出关键点
sift_src_img = cv2.drawKeypoints(img, kp, img, color=(255, 0, 0), flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)sift_gray_img = cv2.drawKeypoints(gray, kp, gray, color=(255, 0, 0),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)# 计算关键点对应的SIFT特征向量
# kp, des = sift.compute(gray, kp)# 调整子图间距
plt.subplot(1, 2, 1), plt.imshow(src_img), plt.title('Origin')
plt.subplot(1, 2, 2), plt.imshow(sift_src_img), plt.title('Sift_Origin')
# plt.subplot(2, 2, 3), plt.imshow(gray, cmap='gray'), plt.title('Gray')
# plt.subplot(2, 2, 4), plt.imshow(sift_gray_img, cmap='gray'), plt.title('Sift_Gray')
plt.show()
其中右图中的每个关键点是由位置,方向,方向大小组成,通过断点调试可以看到这些信息均包含在kp数组里
3.2 cv2中sift算法进行图像拼接
如下图有不同角度拍摄的书面,两个图片有共同部分,也有不同部分,现在要对这两个图片进行拼接,共同组成一个完整的图片。

拼接的过程:
1, 通过sift求出两个图片的特征向量
2,通过knn匹配算法找到两个图片的多对(四队以上)相互匹配的关键点坐标
3,通过四对关键点坐标对B图像进行投射变换
4,A图像与变换后的B图像进行拼接得到原图
下面是完整的
import copy
import time
from datetime import datetime
from typing import Anyimport cv2
import numpy as np
import matplotlib
import matplotlib.pyplot as plt# modify backend to TkAgg
matplotlib.use('TkAgg')# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei'] # 使用黑体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题# modify backend to TkAgg
matplotlib.use('TkAgg')# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei'] # 使用黑体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题class SIFT():def __init__(self):passdef sift_keypoints_draw(self):""" SIFT,全称为 Scale-Invariant Feature Transform(尺度不变特征变换)"""src_img = cv2.imread('resources/sift_2.jpg')img = copy.deepcopy(src_img)gray1 = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)gray = copy.deepcopy(gray1)# SIFT检测器sift = cv2.SIFT_create()# 找出图像中的关键点kp = sift.detect(gray, None)# 在图中画出关键点sift_src_img = cv2.drawKeypoints(img, kp, img, color=(255, 0, 0), flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)sift_gray_img = cv2.drawKeypoints(gray, kp, gray, color=(255, 0, 0),flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)# 计算关键点对应的SIFT特征向量# kp, des = sift.compute(gray, kp)# 调整子图间距plt.subplot(1, 2, 1), plt.imshow(src_img), plt.title('Origin')plt.subplot(1, 2, 2), plt.imshow(sift_src_img), plt.title('Sift_Origin')# plt.subplot(2, 2, 3), plt.imshow(gray, cmap='gray'), plt.title('Gray')# plt.subplot(2, 2, 4), plt.imshow(sift_gray_img, cmap='gray'), plt.title('Sift_Gray')plt.show()def image_sift(self, src_image_a, src_image_b):# 对两个图像通过sift 分别求出各自的特征向量,根据特征向量求出两个图像的所有相同的部分的点坐标信息,并返回""" SIFT,全称为 Scale-Invariant Feature Transform(尺度不变特征变换)"""gray_a = cv2.cvtColor(src_image_a, cv2.COLOR_BGR2GRAY)gray_b = cv2.cvtColor(src_image_b, cv2.COLOR_BGR2GRAY)# generate kps and feature vectorsift = cv2.SIFT_create()(kps_a, features_a) = sift.detectAndCompute(gray_a, None)(kps_b, features_b) = sift.detectAndCompute(gray_b, None)n_kps_a = np.float32([kp.pt for kp in kps_a])n_kps_b = np.float32([kp.pt for kp in kps_b])# feature vector matchratio = 0.95n = 2matcher = cv2.BFMatcher()# 获取两个图像之间所有的匹配点(通过knn算法 进行匹配 对于A图像的每个匹配点,都在B中找到两个最近的匹配点)raw_matches = matcher.knnMatch(features_a, features_b, n)good_matches = []# 通过第一匹配点与第二匹配点的距离差异大小,过滤掉可能为不真实的匹配点for match in raw_matches:if match[0].distance < ratio * match[1].distance:good_matches.append(match[0])if len(good_matches) < 4:print("numbers of match points is less than 4")# 求透视矩阵(用RANSAC方法)# 关于重投影阈值的取值,1:可以根据应用场景来取"""应用场景 推荐阈值范围 说明高精度匹配(如标定板、AR 标记) 1.0~3.0 适用于特征点位置非常准确的场景图像拼接(SIFT/SURF/ORB 匹配) 3.0~5.0 平衡噪声和精度动态场景或低质量图像 5.0~10.0 容忍更大的匹配误差"""src_pts = np.float32([n_kps_b[match.trainIdx] for match in good_matches])dst_pts = np.float32([n_kps_a[match.queryIdx] for match in good_matches])return src_pts, dst_ptsdef image_perspective_transformation(self, src_image_a, src_image_b, src_pts, dst_pts):# 图像透视变换reproj_threshold = 4.0H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, reproj_threshold)distance = 700# 对图像B进行透视变换,变换成与A图像相同角度的平面图像,以便于与图像A进行拼接pert_src_image_b = cv2.warpPerspective(src_image_b, H, (src_image_b.shape[1] + distance, src_image_b.shape[0]))# 图像变形和拼接h1, w1 = src_image_a.shape[:2]h2, w2 = src_image_b.shape[:2]# 计算拼接后图像的尺寸corners1 = np.float32([[0, 0], [0, h1], [w1, h1], [w1, 0]]).reshape(-1, 1, 2)corners2 = np.float32([[0, 0], [0, h2], [w2, h2], [w2, 0]]).reshape(-1, 1, 2)warped_corners2 = cv2.perspectiveTransform(corners2, H)all_corners = np.concatenate((corners1, warped_corners2), axis=0)[xmin, ymin] = np.int32(all_corners.min(axis=0).ravel() - 0.5)[xmax, ymax] = np.int32(all_corners.max(axis=0).ravel() + 0.5)# 计算平移变换translation_dist = [-xmin, -ymin]H_translation = np.array([[1, 0, translation_dist[0]],[0, 1, translation_dist[1]],[0, 0, 1]])# 应用变换,图像拼接result = cv2.warpPerspective(src_image_b, H_translation.dot(H), (xmax - xmin, ymax - ymin))result[translation_dist[1]:h1 + translation_dist[1],translation_dist[0]:w1 + translation_dist[0]] = src_image_areturn resultdef image_mosaicking(self):# 图像拼接src_image_a = cv2.imread('resources/sift_1.jpg')src_image_b = cv2.imread('resources/sift_2.jpg')src_pts, dst_pts = self.image_sift(src_image_a, src_image_b)dst_image_b = self.image_perspective_transformation(src_image_a, src_image_b, src_pts, dst_pts)plt.subplot(1, 3, 1), plt.imshow(src_image_a), plt.title('src_image_a')plt.subplot(1, 3, 2), plt.imshow(src_image_b), plt.title('src_image_b')plt.subplot(1, 3, 3), plt.imshow(dst_image_b), plt.title('Origin_perspective')plt.show()if __name__ == '__main__':sift = SIFT()sift.image_mosaicking()
下面是拼接后的效果图: