第二十四章: 图像特征匹配:暴力特征匹配算法、FLANN特征匹配算法
一、特征匹配
提取特征点及特征点的描述子后,就可以进行特征匹配了。opencv给我们提供的特征匹配方法有2种:
1、BF暴力特征匹配方法
BF,Brute-Force,暴力特征匹配方法,就是通过枚举的方式进行特征匹配,很笨的方法,但准确率很高。
-
匹配原理
图像特征匹配,首先要有两张图像才能进行匹配,其次是先分别计算每张图像的特征点和描述子,然后使用第一组中的每个特征的描述子与第二组中的所有特征描述子进行匹配
匹配的时候是计算两个描述子之间的相似度,然后返回相似度最高的一对儿匹配点,也就是最终的匹配结果。 -
计算相似度
opencv提供了好几种计算方法,比如NORM_L1, NORM_L2, HAMMING1, HAMMING2等方法。其中,NORM_L1, NORM_L2主要用于SIFT、SURF的描述子的计算;HAMMING1, HAMMING2是专门用ORB、BRIEF算法获取的描述子的计算。
HAMMING方法是通过二进制位来判断两个二进制串在第几位出现差异,出现差异的前面的位数越多,HANNING值就越高,就越相似。 -
BF匹配步骤
(1)创建匹配器:bf = cv2.BFMatcher(normType[, corssCheck])
参数normType表示相似度计算的方法,默认值是NORM_L2;
参数crossCheck表示交叉检查,意思就是用第一组中的每个描述子和第二组的所有描述子进行匹配完后,再用第二组中的每个描述子和第一组的所有描述子进行匹配,这两步做完后,两步同时找到的相同的匹配对儿就是有效的匹配。但这个参数默认值是False,就是不开启这个功能,如果开启计算量就会增大。(2)进行特征匹配:
方法一:用匹配器的match方法,进行特征匹配:match = bf.match(des1, des2),就是对两幅图的描述子进行计算,返回匹配的结果。
方法二:调用knnMatch方法进行匹配:match = bf.knnMatch(des1, des2, k)
参数des1,des2是描述子,就是通过SIFT\SURF\ORB等特征提取算法计算出来的描述子;参数k表示取欧式距离最近的前k个关键点,就是计算第一组每个描述子和第二组所有描述子之间的欧式距离,然后取距离最小的前k对儿。当k=1就和match方法的结果一样。
返回结果是一个match对象,这个对象包含了:distance, 描述子之间的距离,值越低越好,越低表示近似度越高,或者说匹配度越高;queryIdx,表示第一个图像的描述子的索引值,query就是查找,就是用哪副图去查找;trainIdx,表示第二幅图像的描述子的索引值。(3)绘制匹配点:
方法一对应的绘制方法:img_match = cv2.drawMatches(img1, kp1, img2, kp2, match, outImg),就是将匹配的点用线连接到一起,这样通过人眼就能观察到哪些点进行匹配了。
参数img1,kp1是指第一组搜索的图和其特征点,就是我们提供的、要搜索的图;参数img2,kp2是指匹配的图及其特征点,就是比如搜索引擎从图片库中拿出来的图;参数match就是匹配器的匹配结果;outImg是图像的输出,这里不用输出,设置位None,我们用img_match这个对象来接受返回值。
方法二对应的绘制方法:img_match = cv2.drawMatchesKnn(img1,kp1, img2, kp2, match) -
#例24.1 调用BF算法匹配图像 import cv2 import numpy as np import matplotlib.pyplot as plt img_template = cv2.imread(r'C:\Users\25584\Desktop\builing-0.png') #模板 (120, 70, 3) img_template_gray = cv2.cvtColor(img_template, cv2.COLOR_BGR2GRAY) img_orig = cv2.imread(r'C:\Users\25584\Desktop\building.jpg') #原图 (600, 868, 3) img_orig_gray = cv2.cvtColor(img_orig, cv2.COLOR_BGR2GRAY) sift = cv2.xfeatures2d.SIFT_create() #创建SIFT对象 kp_template, des_template = sift.detectAndCompute(img_template_gray, None) #进行检测 kp_orig, des_orig = sift.detectAndCompute(img_orig_gray, None) bf = cv2.BFMatcher(cv2.NORM_L1) #创建匹配器 match1 = bf.match(des_template, des_orig) #调用match方法进行匹配 img_match1 = cv2.drawMatches(img_template, kp_template, img_orig, kp_orig, match1, None) #绘制匹配结果 (600, 938, 3) match2 = bf.knnMatch(des_template, des_orig, 1) #调用knnMatch方法进行匹配 img_match2 = cv2.drawMatchesKnn(img_template, kp_template, img_orig, kp_orig, match2, None) #---------------可视化---------------------------------- Fig=plt.figure(figsize=(16,14)) Grid=plt.GridSpec(3,7) axes1=Fig.add_subplot(Grid[1,0]), plt.imshow(img_template[:,:,::-1]), plt.box(), plt.xticks([]), plt.yticks([]), plt.title('template') axes2=Fig.add_subplot(Grid[0:4,1:3]), plt.imshow(img_orig[:,:,::-1]), plt.box(), plt.xticks([]), plt.yticks([]), plt.title('orig img') axes3=Fig.add_subplot(Grid[0:4,3:5]), plt.imshow(img_match1[:,:,::-1]), plt.box(), plt.xticks([]), plt.yticks([]), plt.title('match img1') axes4=Fig.add_subplot(Grid[0:4,5:7]), plt.imshow(img_match2[:,:,::-1]), plt.box(), plt.xticks([]), plt.yticks([]), plt.title('match img2')
说明:len(kp_template),len(match1),len(match2)都返回59, len(kp_orig)返回4566
模板是我从原图上截取的一块,截取的就是第三层楼,从右往左数的第二个窗户。所以匹配的效果还是非常不错的,不过也是有几个点匹配错了。这就是特征匹配,通过这种方法就可以以图搜图了。或者比如直播间的实时视频,可以通过视频匹配到主播的手势等,比如ok手势匹配到了,就说明主播说了一句OK。都是一些小的应用。
二、FLANN特征匹配
FLANN,最快邻近区特征匹配方法,是一种快速匹配方法,在进行批量特征匹配时,FLANN速度更快。但是FLANN使用的是邻近近似值,所以精度较差。如果我们想图像的精确匹配就用BF匹配方法,如果我们想要速度就用FLANN匹配方法。
- FLANN匹配步骤:
(1)创建FLANN匹配器:flann = cv2.FlannBasedMatcher(index_params[, search_params])
参数index_params是一个字典,我们主要是传入要匹配的算法,有KDTREE和LSH两种算法,通常如果我们使用的是SIFT或者是SURF,就选择KDTREE匹配算法;如果使用的是ORB就选择LSH算法。如果搭配错误会报错。
如果我们使用的匹配算法是KDTREE,就需要传入第二个参数search_params, 这个参数也是一个字典,用来指定KDTREE算法中遍历树的次数,一般情况下经验是:KDTREE的层级设置为5,搜索值设置为50,就是10倍,这样计算量相对比较少,速度比较快,准确率也相对比较高。比如:index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5), search_params = dict(checks=50),其中FLANN_INDEX_KDTREE的默认值是1,即参数填:dict(algorithm=1, trees=5), dict(checks=50)即可。
(2)进行特征匹配:
方法一:调用match方法进行匹配:Dmatch = flann.match(des1, des2)
方法二:调用knnMatch方法进行匹配:Dmatch = flann.knnMatch(des1, des2, k)
(3)绘制匹配点:
方法一对应的绘制方法:cv2.drawMatches(img1, kp1, img2, kp2, Dmatch, outImg)
方法二对应的绘制方法:cv2.drawMatchesKnn(img1,kp1, img2, kp2, Dmatch)
#例24.2 调用FLANN算法匹配图像
import cv2
import numpy as np
import matplotlib.pyplot as plt
img_template = cv2.imread(r'C:\Users\25584\Desktop\builing-0.png') #模板 (120, 70, 3)
img_template_gray = cv2.cvtColor(img_template, cv2.COLOR_BGR2GRAY)
img_orig = cv2.imread(r'C:\Users\25584\Desktop\building.jpg') #原图 (600, 868, 3)
img_orig_gray = cv2.cvtColor(img_orig, cv2.COLOR_BGR2GRAY)
sift = cv2.xfeatures2d.SIFT_create() #创建特征检测器SIFT
kp_template, des_template = sift.detectAndCompute(img_template_gray, None) #进行检测
kp_orig, des_orig = sift.detectAndCompute(img_orig_gray, None)
flann = cv2.FlannBasedMatcher(dict(algorithm=1, trees=5), dict(checks=50)) #创建匹配器flann
match1 = flann.match(des_template, des_orig) #调用match方法进行匹配
img_match1 = cv2.drawMatches(img_template, kp_template, img_orig, kp_orig, match1, None) #绘制匹配结果
match2 = flann.knnMatch(des_template, des_orig, 2) #调用knnMatch方法进行匹配,这里参数k设为2,
good = [] #对匹配的点进行一个过滤
for i, (m,n) in enumerate(match2):
if m.distance < 0.7 * n.distance:
good.append(m)
img_match2 = cv2.drawMatchesKnn(img_template, kp_template, img_orig, kp_orig, [good], None) #[good]变成43个点了
#---------------可视化----------------------------------
Fig=plt.figure(figsize=(16,14))
Grid=plt.GridSpec(3,7)
axes1=Fig.add_subplot(Grid[1,0]), plt.imshow(img_template[:,:,::-1]), plt.box(), plt.xticks([]), plt.yticks([]), plt.title('template')
axes2=Fig.add_subplot(Grid[0:4,1:3]), plt.imshow(img_orig[:,:,::-1]), plt.box(), plt.xticks([]), plt.yticks([]), plt.title('orig img')
axes3=Fig.add_subplot(Grid[0:4,3:5]), plt.imshow(img_match1[:,:,::-1]), plt.box(), plt.xticks([]), plt.yticks([]), plt.title('match img1')
axes4=Fig.add_subplot(Grid[0:4,5:7]), plt.imshow(img_match2[:,:,::-1]), plt.box(), plt.xticks([]), plt.yticks([]), plt.title('match img2')