K最近邻(K-Nearest Neighbors,简称KNN)和比率检验(Ratio Test)是在计算机视觉中用于特征匹配的常见技术。它们通常与特征描述子(例如SIFT、SURF、ORB等)一起使用,以在图像中找到相似的特征点。
下面是使用K最近邻和比率检验进行特征匹配的一般步骤:
提取特征描述子:
使用适当的特征提取算法(如SIFT、SURF、ORB等)从图像中提取特征点,并计算每个特征点的描述子。
特征点匹配:
对于两幅图像,分别计算每个特征点的描述子,并使用KNN算法来找到在第二幅图像中与每个特征点最接近的K个特征点。
比率检验:
对于每个特征点,计算最接近的两个特征点的距离比率(通常称为比率检验)。如果最近的邻居距离远离次近邻居距离,则说明匹配比较可靠。您可以使用一个阈值来过滤匹配。
过滤匹配:
应用比率检验,将那些距离比率低于阈值的匹配点过滤掉。这可以帮助排除掉不太可靠的匹配。
绘制匹配结果:
将保留下来的匹配点绘制在两幅图像上,以便观察和分析。
想象一下,一大群知名哲学家邀请你评判他们关于对生命、宇宙
和一切事物都很重要的一个问题的辩论。在每位哲学家轮流发言时,你都会认真听。最后,在所有哲学家都发表完他们所有的论点时,你浏览笔记,会发现以下两件事:
根据最初的观察,你推断最多只有一位哲学家的观点是正确的,
但事实上,也有可能所有哲学家的观点都是错误的。然后,根据第二次观察,即使其中一位哲学家的观点是正确的,你也会开始担心可能会选择一个观点错误的哲学家。不管你怎么看,这些人都会让你的晚宴迟到。你称其为平局,并说辩论中仍有最重要的问题尚未解决。我们可以对判断哲学家辩论的假想问题与过滤糟糕关键点匹配的实际问题进行比较。
首先,假设查询图像中的每个关键点在场景中最多有一个正确的
匹配。也就是说,如果查询图像是NASA标识,那么就假定另一幅图像、(场景)最多包含一个NASA标识。假设一个查询关键点最多有一个正确或者良好的匹配,那么在考虑所有可能的匹配时,我们主要观察糟糕的匹配。
因此,蛮力匹配器计算每个可能匹配的距离分值,可以提供大量的对糟糕匹配的距离分值的观察。与无数糟糕的匹配相比,我期望良好的匹配会有明显更好(更低)的距离分值,因此糟糕的匹配分值可以帮助我们为针对良好的匹配选择一个阈值。这样的阈值不一定能很好地推广到不同的查询关键点或者不同的场景,但是至少在具体案例上会有所帮助。
现在,我们来考虑修改后的蛮力匹配算法的实现,该算法以上述
方式自适应地选择距离阈值。在上一节的示例代码中,我们使用
cv2.BFMatcher类的match方法来获得包含每个查询关键点的单个最佳匹配(最小距离)的列表。这样的实现丢弃了有关所有可能的糟糕匹配的距离分值的信息,而这类信息是自适应方法所需要的。幸运的是,cv2.BFMatcher还提供了knnMatch方法
,该方法接受一个参数k,可以指定希望为每个查询关键点保留的最佳(最短距离)匹配的最大数量。(在某些情况下,得到的匹配数可能比指定的数量最大值更少。)KNN表示K最近邻(K-Nearest Neighbor)。
我们会使用knnMatch方法为每个查询关键点请求两个最佳匹配的
列表。基于每个查询关键点至多有一个正确匹配的假设,我们确信次优匹配是错误的。次优匹配的距离分值乘以一个小于1的值,就可以获得阈值。
然后,只有当距离分值小于阈值时,才将最佳匹配视为良好的匹
配。这种方法被称为比率检验(ratio test),最先是由David
Lowe(SIFT算法的作者)提出来的。他在论文“Distinctive Image
Features from Scale-Invariant Keypoints”(网址为
https://www.cs.ubc.cs/~lowe/papers/ijcv04.pdf)中描述了比率检
验。具体来说,在“Application to object recognition”部分,他
声明如下:
一个匹配正确的概率可以根据最近邻到第2近邻的距离比例来确
定。
我们可以用与前面代码示例相同的方式加载图像、检测关键点,
并计算ORB描述符。然后,使用下面两行代码执行蛮力KNN匹配:
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck= False)
knn_matches = bf.knnMatch(descriptors1, descriptors2, k=2)
knnMatch返回列表的列表,每个内部列表至少包含一个匹配项,
且不超过k个匹配项,各匹配项从最佳(最短距离)到最差依次排序。
下列代码行根据最佳匹配的距离分值对外部列表进行排序:
# 根据匹配的质量,对匹配进行排序,显示前n个排序
knn_matches = sorted(knn_matches, key=lambda x: x[0].distance)
总体代码如下:
import cv2
def orb_test():
# 加载图片 灰色
img1 = cv2.imread('images\\quexiao\\2-1.png')
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
img2 = cv2.imread('images\\quexiao\\2.png')
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
image1 = gray1.copy()
image2 = gray2.copy()
'''
1.使用ORB算法检测特征点、描述符
'''
orb = cv2.ORB_create(128)
keypoints1, descriptors1 = orb.detectAndCompute(image1, None)
keypoints2, descriptors2 = orb.detectAndCompute(image2, None)
# BFMatcher解决匹配
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck= False)
knn_matches = bf.knnMatch(descriptors1, descriptors2, k=2)
# matches = bf.match(descriptors1, descriptors2)
# 根据匹配的质量,对匹配进行排序,显示前n个排序
knn_matches = sorted(knn_matches, key=lambda x: x[0].distance)
# 绘制前10个最佳匹配
img_matches = cv2.drawMatchesKnn(img1, keypoints1, img2, keypoints2, knn_matches[:10], img2,flags=cv2.DRAW_MATCHES_FLAGS_NOT_DRAW_SINGLE_POINTS)
cv2.imshow('matche', img_matches)
cv2.waitKey(0)
cv2.destroyAllWindows()
if __name__ == '__main__':
orb_test()
运行效果:
将此输出图像与上一节中的输出图像进行比较,可以看到使用KNN
和比率检验可以过滤掉很多糟糕的匹配项。剩余的匹配项并不完美,但是几乎所有的匹配项都指向了正确的区域。
实验原图在上一节中,上一节 opencv 进阶16-基于FAST特征和BRIEF描述符的ORB(图像匹配)