目标
在本章中
- 我们将学习如何将一个图像中的特征与其他图像中的特征进行匹配。
- 我们将使用 OpenCV 中的 Brute-Force 匹配器和 FLANN 匹配器
Brute-Force 匹配器基础
Brute-Force 匹配器很简单。它获取第一组中一个特征的描述符,并使用某种距离计算将其与第二组中的所有其他特征进行匹配。并返回最接近的一个。
对于 BF 匹配器,首先我们必须使用 cv.BFMatcher() 创建 BFMatcher 对象。它接受两个可选参数。第一个是 normType。它指定要使用的距离测量方法。默认情况下,它是 cv.NORM_L2。它适用于 SIFT、SURF 等(cv.NORM_L1 也是可用的)。对于基于二进制字符串的描述符,如 ORB、BRIEF、BRISK 等,应使用 cv.NORM_HAMMING,它使用汉明距离作为度量。如果 ORB 使用 WTA_K == 3 或 4,则应使用 cv.NORM_HAMMING2。
第二个参数是布尔变量 crossCheck,默认为 false。如果为 true,则 Matcher 仅返回那些值为 (i,j) 的匹配项,使得集合 A 中的第 i 个描述符将集合 B 中的第 j 个描述符作为最佳匹配,反之亦然。也就是说,两组中的两个特征应该相互匹配。它提供了 consistent 的结果,并且是 D.Lowe 在 SIFT 论文中提出的比率测试的一个很好的替代方法。
一旦创建,两个重要的方法是 BFMatcher.match() 和 BFMatcher.knnMatch()。第一个返回最佳匹配。第二个方法返回 k 个最佳匹配,其中 k 由用户指定。当我们需要在其上进行额外工作时,它可能很有用。
就像我们使用 cv.drawKeypoints() 绘制关键点一样,cv.drawMatches() 帮助我们绘制匹配项。它水平堆叠两个图像,并从第一个图像到第二个图像绘制线,显示最佳匹配。还有 cv.drawMatchesKnn,它绘制所有 k 个最佳匹配。如果 k=2,它将为每个关键点绘制两条匹配线。因此,如果我们想选择性地绘制它,我们必须传递一个掩码。
让我们看看 SIFT 和 ORB 的每个示例(两者都使用不同的距离测量)。
使用 ORB 描述符进行 Brute-Force 匹配
在这里,我们将看到一个简单的示例,说明如何在两个图像之间匹配特征。在这种情况下,我有一个 queryImage 和一个 trainImage。我们将尝试使用特征匹配在 trainImage 中找到 queryImage。(图像是 /samples/data/box.png 和 /samples/data/box_in_scene.png)
我们使用 ORB 描述符来匹配特征。因此,让我们从加载图像、查找描述符等开始。
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
img1 =
cv.imread(
'box.png',cv.IMREAD_GRAYSCALE)
img2 =
cv.imread(
'box_in_scene.png',cv.IMREAD_GRAYSCALE)
orb = cv.ORB_create()
kp1, des1 = orb.detectAndCompute(img1,None)
kp2, des2 = orb.detectAndCompute(img2,None)
CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
从文件加载图像。
接下来,我们创建一个 BFMatcher 对象,其中距离测量为 cv.NORM_HAMMING(因为我们正在使用 ORB),并且启用了 crossCheck 以获得更好的结果。然后,我们使用 Matcher.match() 方法来获得两个图像中的最佳匹配。我们按距离升序对它们进行排序,以便最佳匹配(距离较短)位于前面。然后,我们仅绘制前 10 个匹配项(仅为了可见性。您可以根据需要增加它)
matches = bf.match(des1,des2)
matches = sorted(matches, key = lambda x:x.distance)
img3 =
cv.drawMatches(img1,kp1,img2,kp2,matches[:10],
None,flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
plt.imshow(img3),plt.show()
暴力描述符匹配器。
定义 features2d.hpp:1247
void drawMatches(InputArray img1, const std::vector< KeyPoint > &keypoints1, InputArray img2, const std::vector< KeyPoint > &keypoints2, const std::vector< DMatch > &matches1to2, InputOutputArray outImg, const Scalar &matchColor=Scalar::all(-1), const Scalar &singlePointColor=Scalar::all(-1), const std::vector< char > &matchesMask=std::vector< char >(), DrawMatchesFlags flags=DrawMatchesFlags::DEFAULT)
绘制两幅图像中找到的关键点匹配。
以下是我得到的结果
image
这个 Matcher 对象是什么?
matches = bf.match(des1,des2) 行的结果是一个 DMatch 对象列表。此 DMatch 对象具有以下属性
- DMatch.distance - 描述符之间的距离。越低越好。
- DMatch.trainIdx - 训练描述符中描述符的索引
- DMatch.queryIdx - 查询描述符中描述符的索引
- DMatch.imgIdx - 训练图像的索引。
使用 SIFT 描述符和比率测试进行 Brute-Force 匹配
这一次,我们将使用 BFMatcher.knnMatch() 来获得 k 个最佳匹配。在此示例中,我们将采用 k=2,以便我们可以应用 D.Lowe 在他的论文中解释的比率测试。
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
img1 =
cv.imread(
'box.png',cv.IMREAD_GRAYSCALE)
img2 =
cv.imread(
'box_in_scene.png',cv.IMREAD_GRAYSCALE)
sift = cv.SIFT_create()
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
matches = bf.knnMatch(des1,des2,k=2)
good = []
for m,n in matches
if m.distance < 0.75*n.distance
good.append([m])
img3 = cv.drawMatchesKnn(img1,kp1,img2,kp2,good,None,flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
plt.imshow(img3),plt.show()
请参阅下面的结果
image
基于 FLANN 的匹配器
FLANN 代表快速近似最近邻库。它包含一组算法,这些算法经过优化,可在大型数据集和高维特征中快速进行最近邻搜索。对于大型数据集,它的速度比 BFMatcher 快。我们将看到基于 FLANN 匹配器的第二个示例。
对于基于 FLANN 的匹配器,我们需要传递两个字典,它们指定要使用的算法,其相关参数等。第一个是 IndexParams。对于各种算法,要在 FLANN 文档中传递的信息进行了说明。总而言之,对于 SIFT、SURF 等算法,您可以传递以下内容
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
使用 ORB 时,您可以传递以下内容。注释值是根据文档推荐的,但是在某些情况下,它没有提供所需的结果。其他值工作正常。
FLANN_INDEX_LSH = 6
index_params= dict(algorithm = FLANN_INDEX_LSH,
table_number = 6,
key_size = 12,
multi_probe_level = 1)
第二个字典是 SearchParams。它指定应递归遍历索引中树的次数。较高的值可提供更高的精度,但也会花费更多时间。如果要更改值,请传递 search_params = dict(checks=100)。
有了这些信息,我们就可以开始了。
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
img1 =
cv.imread(
'box.png',cv.IMREAD_GRAYSCALE)
img2 =
cv.imread(
'box_in_scene.png',cv.IMREAD_GRAYSCALE)
sift = cv.SIFT_create()
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50)
matches = flann.knnMatch(des1,des2,k=2)
matchesMask = [[0,0] for i in range(len(matches))]
for i,(m,n) in enumerate(matches)
if m.distance < 0.7*n.distance
matchesMask[i]=[1,0]
draw_params = dict(matchColor = (0,255,0),
singlePointColor = (255,0,0),
matchesMask = matchesMask,
flags = cv.DrawMatchesFlags_DEFAULT)
img3 = cv.drawMatchesKnn(img1,kp1,img2,kp2,matches,None,**draw_params)
plt.imshow(img3,),plt.show()
基于 Flann 的描述符匹配器。
定义 features2d.hpp:1294
请参阅下面的结果
image