导读
在之前的文章图像处理中常用的相似度评估指标中,我们介绍了通过MSE
、PSNR
、SSIM
以及UQI
等指标来计算图像之间的相似度。但是,在使用这些算法计算图像相似的时候两张图像的size
必须一致,而且这些算法对于图像的旋转
、缩放
、平移
、仿射变换
以及光照强度
等都是不鲁棒的。
这篇文章我们来介绍几个更加鲁棒的图像相似度计算的算法,SIFT
、SURF
以及ORB
三种算法,它们都是基于特征点的提取来计算图像之间的相似度。
注意
:因为需要用到SHIFT
、SURF
以及ORB
算法,所以需要安装 opencv-python==3.4.2.16
和opencv-contrib-python==3.4.2.16
,如果是高本版opencv
由于license的问题可能会无法使用到这些算法
环境
- Python:3.7
- opencv:3.4.2.16
- opencv-contrib:3.4.2.16
SIFT
SIFT
(Scale-Invariant Feature Transform):尺度不变特征变换,是用于图像处理中的一种描述。这种描述具有尺度不变性,可在图像中检测出关键点,是一种局部特征的描述子。在图像的特征匹配和特征提取中,经常使用到。
特点
- 不变性:SIFT特征是提取图像的局部特征,它对于
旋转
、尺度缩放
、亮度变换
保持不变性,对于噪声
、视角变换
、仿射变换
也能保持一定的不变性 - 差异性:提取的特征信息量丰富,适合在海量数据中快速找到目标
- 实时性:优化版的SIFT算法提取特征的速度可以达到实时
- 扩展性:提取的特征向量可以和其他的特征向量进行融合
提取特征的步骤
- 尺度空间的极值检测:通过高斯微分函数来寻找图像在尺度变换和旋转不变的关键点
- 关键点定位:在每个候选关键点上,通过拟合精细的模型来确定尺度和位置,最终的选择依据它们的稳定程度
- 方向的确定:基于图像局部的梯度方向,分配给每个关键点一个或多个方向
- 关键点描述:在关键点的领域内,选定的尺度上测量图像的局部梯度,这些梯度被变换成一种表示,这种表示允许比较大的局部变形和光照的变化
代码实现
下面我们用python来提取图像的SIFT特征
- 导包
import cv2
import numpy as np
from matplotlib import pyplot as plt
- 提取图像的SIFT关键点
def extract_sift_feature(img):
#创建SIFT检测器
sift = cv2.xfeatures2d.SIFT_create()
#提取图像的特征点和描述子信息
keypoints, descriptors = sift.detectAndCompute(img, None)
return keypoints,descriptors
def show_img_sift_dot(img):
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#提取图像的SIFT特征点信息
keypoints, descriptors = extract_sift_feature(img)
#在图像上绘制特征点的位置
img1 = cv2.drawKeypoints(rgb_img, keypoints, img)
plt.imshow(img1)
plt.show()
图像上面的绘制出来的圆圈就是使用SIFT
算法提取的特征点
- 图像的关键点匹配
def draw_match_image(img1,img2):
rgb_img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)
rgb_img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)
#提取图像的SIFT关键点信息
keypoints1, descriptors1 = extract_sift_feature(img1)
keypoints2, descriptors2 = extract_sift_feature(img2)
#创建一个关键点匹配器,采用L1距离
bf = cv2.BFMatcher(cv2.NORM_L1, crossCheck=True)
#关键点匹配,通过计算两张图像提取的描述子之间的距离
matches = bf.match(descriptors1, descriptors2)
#根据描述子之间的距离进行排序
matches = sorted(matches, key=lambda x: x.distance)
#只匹配前50个关键点
img3 = cv2.drawMatches(rgb_img1, keypoints1, rgb_img2, keypoints2, matches[:50], rgb_img2, flags=2)
plt.imshow(img3)
plt.show()
上面是两张图片,左图是原图,右图是在原图的基础上增加了高斯噪声,直线连接了两张图像的相似的关键点
- 通过匹配的关键点个数来计算相似度
通过计算两张图之间SIFT关键点的匹配个数占总匹配数量的比例来计算相似度,这个算法计算出来的上面的两张图片的相似度只有28%,比较低
def cal_SIFT_sim(img1,img2):
#将图片转换为灰度图
img1 = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
img2 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
#提取图片的SIFT特征
keypoints1,descriptors1 = extract_sift_feature(img1)
keypoints2,descriptors2 = extract_sift_feature(img2)
#创建一个匹配器
bf = cv2.BFMatcher()
#记录图1和图2的匹配的关键点
matches1 = bf.knnMatch(descriptors1,descriptors2,k=2)
top_results1 = []
for m,n in matches1:
if m.distance < 0.7 * n.distance:
top_results1.append(m)
#记录图2和图1匹配的关键点
matches2 = bf.knnMatch(descriptors2,descriptors1,k=2)
top_results2 = []
for m,n in matches2:
if m.distance < 0.7 * n.distance:
top_results2.append(m)
#从匹配的关键点中选择出有效的匹配
#确保匹配的关键点信息在图1和图2以及图2和图1是一致的
top_results = []
for m1 in top_results1:
m1_query_idx = m1.queryIdx
m1_train_idx = m1.trainIdx
for m2 in top_results2:
m2_query_idx = m2.queryIdx
m2_train_idx = m2.trainIdx
if m1_query_idx == m2_train_idx and m1_train_idx == m2_query_idx:
top_results.append(m1)
#计算图像之间的相似度
#通过计算两张图片之间的匹配的关键点的个数来计算相似度
image_sim = len(top_results) / min(len(keypoints1),len(keypoints2))
return image_sim
- 提取图片SIFT特征的向量计算相似度
将图片中的每个SIFT特征点转换称为一个128维的特征向量,通过控制提取特征的个数来控制特征向量的长度,理论上来说组合特征向量用到的特征越多计算出来的图片相似度越高。
def extract_SIFT_vector(img,vector_size):
gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
sift = cv2.xfeatures2d.SIFT_create()
keypoints = sift.detect(gray_img, None)
# 根据关键点的返回值进行排序,越大越好
keypoints = sorted(keypoints, key=lambda x: -x.response)
img_kps = keypoints[:vector_size]
# 根据关键点来计算描述子向量
kps, des = sift.compute(gray_img, img_kps)
# 将向量展开为1维的
vector = des.flatten()
# 检查提取特征的长度,SIFT特征一个关键点的描述子是128维
vector_len = vector_size * 128
# 对于提取图片特征向量长度不够的使用0进行补充
if vector.size < vector_len:
vector = np.concatenate(vector, np.zeros(vector_len - vector.size))
return vector
import scipy.spatial as T
#设置提取特征向量特征的个数
vector_size = 30
img_path1 = "demo.png"
img_path2 = "demo_gauss_noise.png"
img1 = cv2.imread(img_path1)
img2 = cv2.imread(img_path2)
#提取图片的特征向量
img1_vector = extract_SIFT_vector(img1,vector_size).reshape(-1,128*vector_size)
img2_vector = extract_SIFT_vector(img2,vector_size).reshape(-1,128*vector_size)
#计算图片之间的相似度
sim = T.distance.cdist(img1_vector, img2_vector, 'cosine')
print(sim)#0.411
总结
这篇文章我们主要介绍了如何使用opencv来提取图片的SIFT特征,以及如何来使用SIFT特征点来进行图像匹配和图像的相似度计算
参考
- https://medium.com/@shehan.a.perera/a-comparison-of-sift-surf-and-orb-333d64bcaaea
- https://docs.opencv.org/4.5.5/df/dd2/tutorial_py_surf_intro.html
- https://github.com/ShehanPerera/Research
- https://stackoverflow.com/questions/64525121/sift-surf-set-opencv-enable-nonfree-cmake-solution-opencv-3-opencv-4
- https://docs.opencv.org/4.x/dc/dc3/tutorial_py_matcher.html