当前位置: 首页 > 工具软件 > FLANN > 使用案例 >

cv2-特征点匹配(bf、FLANN)

常元章
2023-12-01

cv2-特征点匹配(bf、KNN、FLANN)

1. 暴力匹配法(bf)

(又称作:交叉匹配)交叉过滤的思想很简单,再进行一次匹配,反过来使用被匹配到的点进行匹配,如果匹配到的仍然是第一次匹配的点的话,就认为这是一个正确的匹配。

eg:假如第一次特征点A使用暴力匹配的方法,匹配到的特征点是特征点B;反过来,使用特征点B进行匹配,如果匹配到的仍然是特征点A,则就认为这是一个正确的匹配,否则就是一个错误的匹配。

cv2.BFMatcher() 创建一个 BFMatcher 对象。它有两个可选参数。

  • 第一个是normType。它是用来指定要使用的距离测试类型。默认值为cv2.Norm_L2。这很适合SIFT和SURF等(c2.NORM_L1 也可以)。对于使用二进制描述符的 ORB,BRIEF,BRISK算法等,要使用 cv2.NORM_HAMMING,这样就会返回两个测试对象之间的汉明距离。如果 ORB 算法的参数设置为 V T A_K==3 或 4,normType就应该设置成 cv2.NORM_HAMMING2。

  • 第二个参数是布尔变量 crossCheck,默认值为False。如果设置为True,匹配条件就会更加严格,只有到 A 中的第 i 个特征点与 B 中的第 j 个特征点距离最近,并且 B 中的第 j 个特征点到 A 中的第 i 个特征点也是最近(A 中没有其他点到 j 的距离更近)时才会返回最佳匹配(i,j)。也就是这两个特征点要互相匹配才行。这样就能提供统一的结果,这可以用来替代 D.Lowe在 SIFT 文章中提出的比值测试方法。

而创建的BFMatcher 对象具有两个方法,BFMatcher.match() 和BFMatcher.knnMatch()。

  • 第一个方法会返回最佳匹配。
  • 第二个方法为每个关键点返回 k 个最佳匹配(降序排列之后取前 k 个),其中 k 是由用户设定的。如果除了匹配之外还要做其他事情的话可能会用上(比如进行比值测试)。

就像使用 cv2.drawKeypoints() 绘制关键点一样,我们可以使用cv2.drawMatches() 来绘制匹配的点。它会将这两幅图像先水平排列,然后在最佳匹配的点之间绘制直线(从原图像到目标图像)。

  • 如果前面使用的是 BFMatcher.knnMatch(),现在我们可以使用函数 cv2.drawMatchsKnn为每个关键点和它的 k 个最佳匹配点绘制匹配线。
  • 如果 k 等于 2,就会为每个关键点绘制两条最佳匹配直线。如果我们要选择性绘制话就要给函数传入一个掩模。

注意,

  • cv2.drawMatches()使用的点对集keypoint是一维的,(N,);
    matchesMask是计算转换H矩阵时生成的掩模,inliers的点,最后只画出符合条件的inliers点对。
  • cv2.drawMatchesKnn()使用的点对集keypoint是一维的,(N,1);
    画出keypoin中前几个点对连线。

1.1 bf.match()

matches = bf.match(des1, des2) 返回值是一个 DMatch 对象列表。这个DMatch 对象具有下列属性:

  • DMatch.distance:描述符之间的距离。越小越好。
  • DMatch.trainIdx : 目标图像中描述符的索引。
  • DMatch.queryIdx :查询图像中描述符的索引。
  • DMatch.imgIdx :目标图像的索引。
detector = cv2.ORB_create()  # Oriented FAST and Rotated BRIEF

kp1 = detector.detect(img1, None)
kp2 = detector.detect(img2, None)
kp1, des1 = detector.compute(img1, kp1) # keypoint 是关键点的列表,desc 检测到的特征的局部图的列表
kp2, des2 = detector.compute(img2, kp2)
# 获得一个暴力匹配器的对象
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

# 利用匹配器 匹配两个描述符的相近程度
'''1. 暴力法'''
matches = bf.match(des1, des2)
# 按照相近程度 进行排序
matches = sorted(matches, key=lambda x: x.distance)
# 画出匹配项
img31 = cv2.drawMatches(img1, kp1, img2, kp2, matches[: 50], img2, flags=2)

"""2. knn法"""
matches = bf.knnMatch(des1, des2, k=1)  # knn 匹配可以返回k个最佳的匹配项、bf返回所有的匹配项
img32 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, matches[: 50], img2, flags=2)
  • 优化:对 matches 匹配点进行排序,提取匹配效果最优的50个点。过滤掉相似性低的特征点。

1.2 bf.knnMatch()

返回对象,同bf.Match()。

K近邻匹配,在匹配的时候选择K个和特征点最相似的点,如果这K个点之间的区别足够大,则选择最相似的那个点作为匹配点,通常选择K = 2,也就是最近邻匹配。对每个匹配返回两个最近邻的匹配,如果第一匹配和第二匹配距离比率足够大(向量距离足够远),则认为这是一个正确的匹配,比率的阈值通常在2左右。

knn匹配过程中很可能发生错误的匹配,错误的匹配主要有两种:

  • 匹配的特征点事错误的
  • 图像上的特征点无法匹配。

KNNMatch():暴力法的基础上添加比率测试。

  • 1)交叉过滤 (暴力法)
    如果第一幅图像的一个特征点和第二幅图像的一个特征点相匹配,则进行一个相反的检查,即将第二幅图像上的特征点与第一幅图像上相应特征点进行匹配,如果匹配成功,则认为这对匹配是正确的。
     
    OpenCV中的BFMatcher已经包含了这种过滤 BFMatcher matcher(NORM_L2,true),在构造BFMatcher是将第二个参数设置为true。

  • 2)比率测试 (knn)
    KNNMatch,可设置K = 2 ,即对每个匹配返回两个最近邻描述符,仅当第一个匹配与第二个匹配之间的距离足够小时,才认为这是一个匹配。

3. FLANN匹配法

FLANN是快速最近邻搜索算法寻找的简称(用快速的第三方库近似最近邻搜索算法)。它包含一组算法,这些算法针对大型数据集中的快速最近邻搜索和高维特征进行了优化。对于大型数据集,它的运行速度比BFMatcher快。

使用 FLANN 匹配,我们需要传入两个字典作为参数。这两个用来确定要使用的算法和其他相关参数等。

  • 第一个是 IndexParams。各种不同算法的信息可以在 FLANN 文档中找到。这里我们总结一下,对于 SIFT 和 SURF 等,我们可以传入的参数是:
    FLANN_INDEX_KDTREE = 1index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)。

  • 第二个字典是 SearchParams。用它来指定递归遍历的次数。值越高结果越准确,但是消耗的时间也越多。如果你想修改这个值,传入参数:searchparams = dict(checks = 100)。

# 创建sift检测器
sift = cv2.xfeatures2d.SIFT_create()
# 查找监测点和匹配符
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
"""
keypoint是检测到的特征点的列表
descriptor是检测到特征的局部图像的列表
"""

# 获取flann匹配器
FLANN_INDEX_KDTREE = 0
# 参数1:indexParams
#    对于SIFT和SURF,可以传入参数index_params=dict(algorithm=FLANN_INDEX_KDTREE, trees=5)。
#    对于ORB,可以传入参数index_params=dict(algorithm=FLANN_INDEX_LSH, table_number=6, key_size=12)。
indexParams = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)

# 参数2:searchParams 指定递归遍历的次数,值越高结果越准确,但是消耗的时间也越多。
searchParams = dict(checks=50)

# 使用FlannBasedMatcher 寻找最近邻近似匹配
flann = cv2.FlannBasedMatcher(indexParams, searchParams)
# 使用knnMatch匹配处理,并返回匹配matches
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: # 通过0.7系数来决定匹配的有效关键点数量
        matchesMask[i] = [1, 0]

drawPrams = dict(matchColor=(0, 255, 0),
                 singlePointColor=(255, 0, 0),
                 matchesMask=matchesMask,
                 flags=0)
# 匹配结果图片
img33 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, matches, None, **drawPrams)

4. 总结

特征匹配:FLANN效果更好一些。

 类似资料: