1.1实验目的: 采集两幅图像,要求有视角和比例变化,完成两幅图像的SIFT特征检测,并对实验结果进行比较分析。
实验的问题是将同一个物体或事物在不同的时间、光照、分辨率、方向的情况下所对应的图像进行联系,从而达到识别目标的目的。
1.2实验背景:SIFT算法全称为Scale-invariant Feature transform,即“尺度不变特征转换”。是一种电脑视觉的算法用来侦测与描述影像中的局部性特征,它在空间尺度中寻找极值点,并提取出其位置、尺度、旋转不变量。传统的匹配算法对环境的适应性能力差,而SIFT算法的鲁棒性较强,能识别不同情况的目标。
在一定范围内,人肉眼可以识别分辨的物体计算机要识别是很难的,所以在一个不明确的场景里,我们可以把同一物体的不同大小尺度提供给计算机,建立同一副图像不同分辨率的模型。通过对图像景象平滑与下采样,得到一系列不同尺寸大小的图像,类似于一个金字塔。通过加入高斯滤波,加强在下采样后图像的连续性,形成多组多层的高斯金字塔。取特征点可以在这些层里面分析处理。
1.3 SIFT特点:相较与Harris这样的检测方式,SIFT是尺度不变的,当我们使用Harris时,对于旋转了的图像我们依然可以找到相应的拐角,但是当图像进行缩放时,则无法准确的识别出对应的拐角点。而SIFT算法进行关键点提取计算,从而进行图片分析处理,SIFT算法具有对图像的局部性特征处理,其拥有独特性,多量性,高速性,可扩展性,对噪声有稳定性。
SIFT算法的具体步骤有:(1).尺度空间极值监测。(2).关键点定位,即特征点定位。(3).特征方向赋值。(4).特征点描述。
我们可以使用python相关的库进行SIFT算法的实验与验证,首先我们需要用到import cv2,以及矩阵运算库import numpy as np。
import cv2
import numpy as np # 矩阵运算库
读取图片选择opencv cv2.imread(),将我们的图片读取后以多维数组的形式保存信息,前两维表示图片的像素坐标,最后以为表示图片的通道索引,具体图像的通道数由图片的格式来决定。
yuanp = cv2.imread('1.jpg') # 读取原图
xuanzhuanp = cv2.imread('2.jpg') # 读取旋转90°图
调用函数进行处理
cv2.xfeatures2d.SIFT_create()
调用函数detectAndComputer获取特征点以及SIFT特征向量
(kp1, des1) = wedo.detectAndCompute(yuanp, None)
(kp2, des2) = wedo.detectAndCompute(xuanzhuanp, None)
关键点包含的信息有角度、id、坐标、直径、方向等信息。
获取到特征点后可以使用des1.shape[0]以及des2.shape[0]来得知原图以及旋转之后的图所得到的特征点个数。同时可以使用kp1与kp2来显示某个特征点的坐标(.pt)、领域直径(.size)、方向(.angle)、所在金字塔的层数(,octave)等信息。
得到这些信息后就调用函数drawKeypoints()进行特征点绘制,这个函数含有四个参数,第一个为打开的原始图片,第二个为我们获取到的关键点信息,第三个为输出图片(我们选择原始图片),第四个参数为特征点参数的颜色选择,颜色color(a,b,c)三个参数分别为蓝色,绿色,红色通过调整这些参数可以改变颜色。
yuanp_make = cv2.drawKeypoints(yuanp, kp1, yuanp, color=(255, 0, 255))
xuanzhuanp_make = cv2.drawKeypoints(xuanzhuanp, kp2, xuanzhuanp, color=(255, 0, 255))
也可以加入参数DRAW_MATCHES_FLAGS_DARW_RICH_KEYPOINTS,将特征点带有方向线,更加清晰好看。
yuanp_make = cv2.drawKeypoints(yuanp, kp1, yuanp, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
xuanzhuanp_make = cv2.drawKeypoints(xuanzhuanp, kp2, xuanzhuanp,flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
之后通过调用函数将两图片合起来并保存在一张图片中,函数为np.hstack(原图,处理图)形式。
再记下来就是特征点匹配环节,用k最近邻算法求取空间距离中最近的k各个数据点,并将这些数据点归为一类。
先创建BFMatcher对象,再调用knn算法。des1为原图,des2为旋转图。Matches1就是用于原图和旋转图匹配的特征点数。
befion = cv2.BFMatcher()
matches1 = befion.knnMatch(des1, des2, k=2)
调整ratio1参数,若准确度较高匹配则为0.4,一般为0.5,匹配点数多的可用0.6。然后循环计算在这些匹配特征点数里的接近点数值,如果最接近的点和次接近点的比值大于一个既定的值,那么就保留这个最近的值。
for m1, n1 in matches1:
if m1.distance < ratio1 * n1.distance:
good1.append([m1])
在之后调用函数cv2.drawMatchesKnn()画出good前几个点对的连线,函数为:
match_result1 = cv2.drawMatchesKnn(yuanp, kp1, xuanzhuanp, kp2, good, None, flags=2)
其中yuanp为原图xuanzhuanp为旋转图,kp1、kp2为函数detectAndCompute的返回值,可以通过调试good来选择进行多少特征点对的连接,格式为good[:100],这个值前100对进行连接。
最后将结果图保存即可。
若是选择颜色各个提取点会相同大小:
特征点提取图2:
结果图:
这里为输出:
可以得到原图特征点有301个,旋转图的特征点有292个,单个提取数据点信息也可以看到,第一个数据点来自图像金字塔的3904991层。匹配到的特征点有175个。从结果图上来看越复杂,颜色变化越多的区域就会产生越多的特征点,拐角处也可以很好地匹配到,部分颜色单一的区域只有极少的一些特征点,大约有三分之二的特征点匹配成功,对提取的结果还是很不错的。
通过使用SIFT算法对图像的特征点处理匹配,可以感觉到SIFT算子对旋转、尺度缩放、亮度等具有很强的鲁棒性,拥有特别优秀稳定的局部特征,但是同时也发现SIFT算法也存在着不完美,它部分区域提取的特征点较少,就在本次实验图中,熊的脑袋这一块很单一,所以它只找到一个特征点,别的区域,比如边缘区域,也没有很多的特征点被提取出来,较为光滑的目标没有准确的特征点提取。本次实验从开始查资料到完成实验,发现了很多大大小小的问题,比如我们需要用到cv2库,但是在python新版本里没有这个库,因为在新版本中相关板块申请了专利,所以没有再出现,我们需要重装旧版本,opencv的版本得退回到3.4.2.16前。对python提供的函数等也要一个个解读,过程也是很有意义的。后续工作我想去了解如何解决特征点提取异常少、光滑区提取不明显、以及怎么提高特征点的匹配问题,这次实验三分之二的特征点相匹配,还有部分怎么样去提高匹配几率等。
from turtle import color
import cv2 # opencv版本需为3.4.2.16
import numpy as np # 矩阵运算库
import time # 时间库
yuanp = cv2.imread('1.jpg') # 读取原图
xuanzhuanp = cv2.imread('2.jpg') # 读取旋转90°图
##yuanp1 = cv2.imread('1222.jpg')
wedo = cv2.xfeatures2d.SIFT_create()
# 获取各个图像的特征点及sift特征向量
# 返回值kp包含sift特征的方向、位置、大小等信息;des的shape为(sift_num, 128), sift_num表示图像检测到的sift特征数量
(kp1, des1) = wedo.detectAndCompute(yuanp, None)
(kp2, des2) = wedo.detectAndCompute(xuanzhuanp, None)
# 特征点数目显示
print("=========================================")
print("=========================================")
print(' 原图 特征点数目:', des1.shape[0])
print(' 旋转图 特征点数目:', des2.shape[0])
print("=========================================")
print("=========================================")
# 举例说明kp中的参数信息
for i in range(1):
print("关键点", i)
print("关键点坐标:", kp1[i].pt)
print("邻域直径:", kp1[i].size)
print("方向:", kp1[i].angle)
print("所在的图像金字塔的层数:", kp1[i].octave)
print("=========================================")
print("=========================================")
"""
首先对原图和旋转图进行特征匹配,即图yuanp和图xuanzhuanp
"""
# 绘制特征点,并显示为红色圆圈
##we=cv2.drawKeypoints(yuanp,kp1,yuanp,color(255,0,255))
##cv2.imwrite('xxx.jpg',we)
yuanp_make = cv2.drawKeypoints(yuanp, kp1, yuanp, flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
xuanzhuanp_make = cv2.drawKeypoints(xuanzhuanp, kp2, xuanzhuanp,flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
hpingjie = np.hstack((yuanp_make, xuanzhuanp_make)) # 对提取特征点后的图像进行横向拼接
cv2.imwrite("sift_cat1.png", hpingjie)
print('原图与旋转图 特征点绘制图像已保存')
cv2.imshow("sift_point1", hpingjie)
cv2.waitKey()
# 特征点匹配
# K近邻算法求取在空间中距离最近的K个数据点,并将这些数据点归为一类
start = time.time() # 计算匹配点匹配时间
befion = cv2.BFMatcher()
matches1 = befion.knnMatch(des1, des2, k=2)
print('用于 原图和旋转图 图像匹配的所有特征点数目:', len(matches1))
# 调整ratio
# ratio=0.4:对于准确度要求高的匹配;
# ratio=0.6:对于匹配点数目要求比较多的匹配;
# ratio=0.5:一般情况下。
ratio1 = 0.5
good = []
for m1, n1 in matches1:
# 如果最接近和次接近的比值大于一个既定的值,那么我们保留这个最接近的值,认为它和其匹配的点为good_match
if m1.distance < ratio1 * n1.distance:
good.append([m1])
print("原图与旋转图匹配对的数目:", len(good))
end = time.time()
print("匹配点匹配运行时间:%.4f秒" % (end-start))
# 通过对good值进行索引,可以指定固定数目的特征点进行匹配,如good[:20]表示对前20个特征点进行匹配
match_result1 = cv2.drawMatchesKnn(yuanp, kp1, xuanzhuanp, kp2, good, None, flags=2)
cv2.imwrite("result1.png", match_result1)
print("=========================================")
print("=========================================")
cv2.imshow("原图和旋转图的匹配结果", match_result1)
cv2.waitKey()