在实验二当中
特征匹配主要是基于两种相似度较高的图片,通过Opencv里面提供的特征匹配方法来进行特征点之间的匹配和映射
特征点由关键点和描述子两部分组成。例如:在一张图像中计算SIFT特征点时,是指提取SIFT关键点,并计算SIFT描述子两件事。关键点是指特征点在图像里的位置,有些特征点还具有方向、大小等信息。描述子是指一个向量,描述该关键点周围像素的信息,按照“外观相似特征应该有相似的描述子”的原则设计
SIFT(尺度不变特征)做为最经典的特征提取算法,充分考虑图像变换过程中出现的光照、尺度、旋转等变化,但计算量较大,实时性不好。
FAST关键点(没有描述子)考虑适当降低精度和鲁棒性,以提升计算的速度,属于特别快的一种特征点。
ORB特征目前看来非常具有代表性的实时图像特征,它改进了FAST检测子不具有方向性问题,并采用速度极快的二进制描述子BRIEF;因此,ORB在保持特征子具有旋转、尺度不变性的同时,在速度方面也有了很大的提升。
本次实验主要展示如何通过SIFT进行特征匹配
SIFT,FAST和ORB的简单介绍-CSDN博客
下面让我们先了解一下重要的几个函数方法:
1.计算检测关键点并计算描述符
detectAndCompute(InputArray img, bool useProvidedKeypoints = false)
img:为cv.imread()读入的图像矩阵
useProvidedKeypoints:决定当前是探测关键点还是计算描述符,useProvidedKeypoints为true时,执行compute功能,计算描述符;为false时,执行detect功能,探测关键点
示例代码:
import cv2
img_m=cv2.imread('learn_test3.jpg',0)
img_m = cv2.resize(img_m,(500,700))
sift=cv2.SIFT_create()#定义一个SIFT特征计算对象
kp2,des2=sift.detectAndCompute(img_m,None)#,useProvidedKeypoints=False
res_r = cv2.drawKeypoints(img_m, kp2, img_m, color=(0, 255, 0), flags=0)#关键点描绘
cv2.imshow('picture2',res_r)#图片展示
cv2.waitKey(0)
探测关键点展示 :
Print(des2)
[[11. 2. 1. ... 0. 0. 0.]
[ 0. 1. 0. ... 28. 0. 6.]
[ 0. 0. 6. ... 0. 0. 3.]
...
[ 5. 3. 7. ... 0. 0. 0.]
[ 0. 0. 0. ... 0. 0. 0.]
[ 2. 4. 5. ... 0. 0. 0.]]
#des2,kp2均有值
useProvidedKeypoints=True展示 :
Print(des2)
None
#des2,kp2均为空
2.特征匹配方法:
暴力匹配(Brute-Force Matcher):cv::BFMatcher
暴力匹配很简单。首先在模板特征点描述符的集合当中找到第一个特征点,然后匹配目标图片的特征点描述符集合当中的所有特征点,匹配方式使用“距离”来衡量,返回“距离”最近的那个。该方法包含两个可选参数:normType、crossCheck。
normType指定了要使用的“距离”测量方法。缺省条件下,的参数是cv2.NORM_L2。
在使用SIFT方法和SURF方法等等进行匹配时,这种“距离”测量方法效果很好(cv2.NORM_L1也一样)。在使用基于二进制字符串的描述符,像ORB,BRIEF,BRISK等等方法时,应该使用cv2.NORM_HAMMING,这种方法使用汉明距离来测量。如果ORB算法的参数设置为WAT_K==3或者4,那么应该使用cv2.NORM_HAMMING2。
crossCheck参数是boolean类型,缺省的情况下是false。如果crossCheck是true,那么匹配器返回只返回一个最佳的匹配(i,j),其中i在特征点描述符集合A当中,j在特征点描述符集合B当中,反之亦然。也就是两个集合当中的特征点都彼此配对。这种方法返回了一致的结果,并且可以很好的用来替代SIFT算法论文当中测试。暴力匹配器有两个重要的方法,分别是BFMatcher.match() 和BFMatcher.knnMatch()。第一个返回最佳匹配,第二个返回前k个最佳的匹配,k值由用户指定。
cv::match方法
match匹配的返回结果是DMatch类型。
DMatch数据结构包含三个非常重要的数据分别是queryIdx,trainIdx,distance;
queryIdx:某一特征点在本帧图像的索引;
trainIdx:trainIdx是该特征点在另一张图像中相匹配的特征点的索引;
distance:代表这一对匹配的特征点描述符的欧式距离,数值越小也就说明俩个特征点越相近
matches = bf.match(des1,des2)
matches = sorted(matches,key=lambda x: x.distance)
通过对返回来的匹配数组,利用x.distance作为大小来进行排序,从而达到距离以小到大递增,则特征从大到小递减。
示例代码:
import cv2
img_f=cv2.imread('learn_test4.jpg')
img_m=cv2.imread('learn_test3.jpg')
sift=cv2.SIFT_create()
kp1,des1=sift.detectAndCompute(img_f,None,useProvidedKeypoints=False)#
kp2,des2=sift.detectAndCompute(img_m,None,useProvidedKeypoints=False)#
res_l = cv2.drawKeypoints(img_f, kp1, img_f, color=(0, 255, 0), flags=0)
res_r = cv2.drawKeypoints(img_m, kp2, img_m, color=(0, 255, 0), flags=0)
bf=cv2.BFMatcher(crossCheck=True)#用来比较a对b的路径是最短,同时检验b对a的路径是否也是最短的
matches=bf.match(des1,des2)#暴力匹配
matches=sorted(matches,key=lambda x:x.distance)#排序匹配得分,由小到大,通过两点的距离排序
img3=cv2.drawMatches(img_f,kp1,img_m,kp2,matches[:10],None,flags=2)#选取距离最小的十个点进行描绘,合并两个图片
img3=cv2.pyrDown(img3)#因为匹配之后会通过金字塔膨胀图片,所以要通过金字塔压缩一次图片
cv2.imshow('picture',img3)
cv2.waitKey(0)
效果展示:
cv::knn_Match方法
knnMatch匹配的返回结果是一个元组,说明结果不能改变;
对元组内元素进行类型查询: 所以Knnmatch与match的返回值类型一样,只不过一组返回的2个DMatch类型。
matches=bf.knnMatch(des1,des2,k=2)
good=[]
for m,n in matches:
if m.distance<0.75*n.distance:
good.append([m])
当 k=2 时,knnMatch 方法会返回每个查询特征点的两个最近邻匹配。每个匹配对象都包含两个最近邻的特征点,即有两个元素的列表。 例如,如果 matches 是 [(m1, n1), (m2, n2), ..., (mk, nk)],那么对于第 i 个查询特征点,它的两个最近邻匹配分别是 (mi, ni)。 以下是对每个匹配对象中元素的解释:
m.distance: 第一个最近邻的相似性度量值。距离值越小表示越相似。
n.distance: 第二个最近邻的相似性度量值。
所以判断如果m的距离小于n的距离*0.75,则判断m点特征点较为匹配,也可以取小于0.75的值
示例代码:
import cv2
img_f=cv2.imread('learn_test4.jpg')
img_m=cv2.imread('learn_test3.jpg')
sift=cv2.SIFT_create()
kp1,des1=sift.detectAndCompute(img_f,None,useProvidedKeypoints=False)#
kp2,des2=sift.detectAndCompute(img_m,None,useProvidedKeypoints=False)#
res_l = cv2.drawKeypoints(img_f, kp1, img_f, color=(0, 255, 0), flags=0)
res_r = cv2.drawKeypoints(img_m, kp2, img_m, color=(0, 255, 0), flags=0)
bf=cv2.BFMatcher()
matches=bf.knnMatch(des1,des2,k=3)
good=[]
for m,n,p in matches:
if m.distance<0.75*n.distance:
good.append([m])
img3=cv2.drawMatchesKnn(img_f,kp1,img_m,kp2,good,None,flags=2)
img3=cv2.pyrDown(img3)
cv2.imshow('pic1',img3)
cv2.waitKey(0)
结果演示:
快速近似最近邻匹配(FLANN)
flann = cv2.FlannBasedMatcher(index_params, search_params)
index_params:字典类型,在设置中用数字表示
search_params:字典类型,默认dict(checks=32, eps=0,sorted=True)
名称 | 值 | 备注 |
FLANN_INDEX_LINEAR | 0 | 线性暴力搜索 |
FLANN_INDEX_KDTREE | 1 | 随机kd树,平行搜索,默认trees=4 |
FLANN_INDEX_KMEANS | 2 | 层次k均值树 |
FLANN_INDEX_COMPOSITE | 3 | 随机kd树和层次k均值树构建索引 |
FLANN_INDEX_KDTREE_SINGLE | 4 | |
FLANN_INDEX_HIERARCHICAL | 5 | |
FLANN_INDEX_LSH | 6 | |
FLANN_INDEX_SAVED | 254 | |
FLANN_INDEX_AUTOTUNED | 255 |
FLANN的意思是快速最近邻搜索库。它包含一个对大数据集合和高维特征实现最近邻搜索的算法集合,而且这些算法是优化固偶读。面对大数据集时,效果要比暴力搜索好。
FLANN要传递两个字典作为参数,第一个参数是使用的搜索算法;第二个参数是搜索次数,次数越多,结果越精确,但是速度也越慢。
示例代码:
import cv2
img_f=cv2.imread('learn_test4.jpg')
img_m=cv2.imread('learn_test3.jpg')
sift=cv2.SIFT_create()
kp1,des1=sift.detectAndCompute(img_f,None,useProvidedKeypoints=False)
kp2,des2=sift.detectAndCompute(img_m,None,useProvidedKeypoints=False)
res_l = cv2.drawKeypoints(img_f, kp1, img_f, color=(0, 255, 0), flags=0)
res_r = cv2.drawKeypoints(img_m, kp2, img_m, color=(0, 255, 0), flags=0)
algorithm_param = dict(algorithm = 2, tree = 5)#FlannBasedMatcher的参数之一,使用哪种匹配算法进行匹配
check_param = dict(check = 5)#FlannBasedMatcher的参数之一,设置匹配次数,次数越多越准确但越慢
bf = cv2.FlannBasedMatcher(algorithm_param,check_param)
matches = bf.match(des1,des2)
matches = sorted(matches,key=lambda x: x.distance)
img3 = cv2.drawMatches(img_f,kp1,img_m,kp2,matches,None,flags=2)
img3 = cv2.pyrDown(img3)
cv2.imshow('pic',img3)
cv2.waitKey(0)
结果展示:
本次实验展示了特征检测和描述中的图片修复功能,官方文档请看OpenCV: OpenCV Tutorials
如有错误或遗漏,希望小伙伴批评指正!!!!
希望这篇博客对你有帮助!!!!
实验一: 计算机视觉Opencv实验合集——实验一:图像修复-CSDN博客
实验三:Opencv实验合集——实验三:背景减除-CSDN博客