计算机视觉中,描述符是一种描述关键点的方法,它完全依赖于用来提取描述符的特定算法,并且与关键点(在KeyPoint类中定义)不同,除了每一个描述符表示一个关键点这一点之外,描述符没有共同的结构。
我们可以使用detect函数来检测图像中的一组关键点。类似地,可以使用compute函数从关键点中提取描述符。由于OpenCV中特征检测器与描述提取符组织方法的缘故,将它们联合使用是一件极其容易的事情,即detectAndCompute函数,可以同时进行关键点检测及特征提取,并且比单独调用两个函数更快。
一、蛮力匹配
蛮力匹配器是一个描述符匹配器,它比较两组关键点描述符并生成匹配列表。之所以称为蛮力匹配,是因为在该算法中几乎不涉及优化。对于第一个集合中的每个关键点描述符,匹配器将之与第二个集合中的每个关键点描述符进行比较。每次比较产生一个距离值,并基于最小距离选择最佳匹配。
OpenCV提供了一个cv2.BFMatcher类,支持几种蛮力特征匹配的方法。
下面,我们使用ORB进行特征匹配,使用 cv2.BFMatcher类的 match 方法:
import numpy as np
import cv2
from matplotlib import pyplot as plt
# 读入图像
imgname1 = 'E:/qi.png'
imgname2 = 'E:/qiqiqi.png'
img1 = cv2.imread(imgname1)
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) # 灰度处理图像
img2 = cv2.imread(imgname2)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) # 灰度处理图像
# 创建ORB特征检测器和描述符
orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(img1, None) # des是描述子
kp2, des2 = orb.detectAndCompute(img2, None) # des是描述子
# BFMatcher解决匹配
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
# 根据匹配的质量,对匹配进行排序,显示前n个排序
matches = sorted(matches, key=lambda x: x.distance)
# 绘制前25个最佳匹配
img_matches = cv2.drawMatches(img1, kp1, img2, kp2, matches[:25], img2,
flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS)
cv2.imshow("BFmatch", img_matches)
cv2.waitKey(0)
cv2.destroyAllWindows()
(我们可以看到,很多匹配都是假匹配,这也很典型。为了改善匹配结果,我们需要应用其他技术来过滤糟糕的匹配)
二、蛮力KNN和比率检验过滤匹配
现在,我们来考虑修改后的蛮力匹配算法的实现,该算法以上述方式自适应地选择距离阈值。
上一步,我们使用cv2.BFMatcher类的match方法来获得包含每个查询关键点的单个最佳匹配(最小距离)的列表。这样的实现丢弃了有关所有可能的糟糕匹配的距离分值的信息,而这类信息是自适应方法所需要的。
幸运的是,cv2.BFMatcher还提供了 knnMatch 方法,该方法接受一个参数k,可以指定希望为每个查询关键点保留的最佳(最短距离)匹配的最大数量。
我们会使用knnMatch方法为每个查询关键点请求两个最佳匹配的列表。基于每个查询关键点至多有一个正确匹配的假设,我们确信次优匹配是错误的。次优匹配的距离分值乘以一个小于1的值,就可以获得阈值。然后,只有当距离分值小于阈值时,才将最佳匹配视为良好的匹配。这种方法被称为比率检验。
下面,我们使用ORB进行特征匹配,使用 cv2.BFMatcher类的 knnMatch 方法:
将上一步的部分代码改为:
# BFMatcher解决匹配
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)
pairs_of_matches = bf.knnMatch(des1, des2, k=2)
matches = sorted(pairs_of_matches, key=lambda x: x[0].distance)
同时必须使用drawMatchesKnn 函数
img_matches = cv2.drawMatchesKnn(img1,kp1,img2,kp2,pairs_of_matches[:25],
img2,flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS)
结果:
到目前为止,我们并没有过滤掉所有糟糕的匹配,实际上,还故意包含了我们认为是糟糕的次优匹配。
现在,我们应用比率检验,将阈值设置为次优匹配距离分值的0.7倍(自行设置),如果KnnMatch无法提供次优匹配,那么就拒绝最佳匹配,因为无法对其应用检验。
matches = [x[0] for x in pairs_of_matches
if len(x) > 1 and x[0].distance < 0.6 * x[1].distance]
应用比率检验后,使用drawMatches 进行绘制。
完整的蛮力knn和比率检验匹配代码:
import numpy as np
import cv2
from matplotlib import pyplot as plt
# 读入图像
imgname1 = 'E:/qiqiqi.png'
imgname2 = 'E:/qi.png'
img1 = cv2.imread(imgname1)
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) # 灰度处理图像
img2 = cv2.imread(imgname2)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) # 灰度处理图像
# 创建ORB特征检测器和描述符
orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(img1, None) # des是描述子
kp2, des2 = orb.detectAndCompute(img2, None) # des是描述子
# BFMatcher解决匹配
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)
pairs_of_matches = bf.knnMatch(des1, des2, k=2)
matches = [x[0] for x in pairs_of_matches
if len(x) > 1 and x[0].distance < 0.7 * x[1].distance]
img_matches = cv2.drawMatches(img1, kp1, img2, kp2, matches[:25], img2,
flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS)
cv2.imshow("KNNmatch", img_matches)
cv2.waitKey(0)
cv2.destroyAllWindows()
接下来,将使用名为FLANN的更快的匹配器来代替蛮力匹配器。
【参考】:OpenCV 4计算机视觉 Python语言实现(原书第三版) 作者:Joseph Howse