(四)OpenCV中的特征检测之特征匹配
阅读原文时间:2021年04月21日阅读:1

注释:本文翻译自OpenCV3.0.0 document->OpenCV-Python Tutorials,包括对原文档种错误代码的纠正

1.概述

我们知道很多关于特征检测器和描述符。 现在是学习如何匹配不同描述符的时候了。 OpenCV提供了两种技术,Brute-Force匹配器和基于FLANN的匹配器。

2.目标

  • 我们将看到如何将一副图像中的特征与其它图像特征匹配
  • 我们将在OpenCV中使用Brute-Force匹配器和FLANN Matcher

3.Brute-Force匹配器基础(Basics of Brute-Force Matcher)

    蛮力匹配器很简单。它采用第一组的一个特征的描述符并且使用一些距离计算与第二组中的所有其它特征匹配。将最近的一个返回。 

    对于BF匹配器,首先我们必须使用cv2,BFMatcher()创建BFMatcher对象。它需要两个可选参数,首先是normType。它指定要使用的距离测量。默认情况下,它是cv2.NORM_L2,这对SIFT、SURF等是有好处的(cv2.NORM_L1也在那里)。对于像ORB,BRIEF、BRISK等基于二进制字符串的描述符,应该使用cv2.NORM_HAMMING,它使用汉明距离作为度量。如果ORB使用WTA_K==3或4,则应使用cv2.NORM_HAMMING2.

    第二个参数是布尔变量,crossCheck默认为false。如果确实如此,则匹配器仅返回具有值(i,j)的匹配,使得集合A中的第i个描述符具有集合B中的第j个描述符作为最佳匹配,反之亦然。也就是说,两组中的两个特征应该相互匹配。它提供了一致的结果,对于D.Lowe在SIFT论文中提出的比率测试来说是一个很好的选择。

   一旦创建,两个重要的方法是BFMatcher.match()和BFMatcher.knnMatch()。首先返回最佳匹配。第二种方法返回k个最佳匹配,其中k由用户指定。当我们需要为此做额外的工作时,它可能会有用。

    就像我们使用cv2.drawKeypoints()来绘制关键点,cv2.drawMatches()帮助我们绘制匹配。它将两个图像水平堆叠,并从第一个图像到第二个图像绘制线条,以显示最佳匹配。还有cv2.drawMatchesKnn可以绘制所有k个最佳匹配。如果k = 2,它将为每个关键点绘制两条匹配线。所以如果我们想要有选择地绘制它,我们必须传递一个掩码。

我们来看SURF和ORB的每个例子(都使用不同的距离测量)。

4.用ORB描述符进行Brute-Force匹配(Brute-Force Matching with ORB Descripotrs)

在这里,我们将看到一个关于如何匹配两幅图像之间特征的简单示例。在这种情况下,我有一个queryImage和一个trainImage。 我们将尝试使用特征匹配在trainImage中查找queryImage。 (图像是/samples/c/box.png和/samples/c/box_in_scene.png)。

我们使用SIFT描述符来匹配特征。那么让我们开始加载图片,找到描述符等。

接下来我们创建一个距离测量值为cv2.NORM_HAMMING的BFMatcher对象(因为我们使用的是ORB),并且为了获得更好的结果,crossCheck被打开。 然后我们使用Matcher.match()方法来获取两幅图像中的最佳匹配。 我们按照距离的升序对它们进行排序,以便最佳匹配(低距离)出现在前面。然后我们只画出前10个匹配(只是为了能见度,你可以随意增加)。

代码如下:

# -*- coding: utf-8 -*-
'''
特征匹配(Feature Matching):
1.了解了很多特征检测器和描述符,现在是学习如何匹配不同描述符的时候了。
2.OpenCV提供了两种技术:Brute-Force匹配器和基于FLANN的匹配器
3.学会如何将一幅图像中的特征与其它图像匹配

特征匹配器1:Brute-Force(basicis of Brute-Force Matcher)蛮力匹配器
          它采用第一组中的一个特征的描述符并且使用一些距离计算与第二组中的所有其它特征匹配。将最近的一个返回。
          1.首先使用cv2.BFMatcher(normType,bool)创建BFMatcher对象.
          2.BFMatcher.match()和BFMatcher().knnMatch()。前一个方法返回最佳匹配,第二个方法返回k个最佳匹配。
          3.使用cv2.drawMatches()帮助我们绘制匹配。还有cv2.drawMatchesknn()绘制k个最佳匹配。需要传递一个掩膜

下面的栗子:用ORB描述符进行BF匹配
'''

import cv2
import numpy as np
from matplotlib import pyplot as plt

img1 = cv2.imread('1.jpg', 0)  # 查询图片  queryImage
img2 = cv2.imread('2.jpg', 0)  # 训练图片  trainImage

# 初始化SIFT探测器
orb = cv2.ORB_create()

# 用SIFT找到关键点和描述符
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)

'''
接下来创建一个距离测量值为cv2.NORM_HAMMING的BFMatcher对象
用Matcher.match()来获取两幅图像中的最佳匹配;
我们按照距离的升序对它们进行排序
'''
# 创建BFMatcher对象
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

# 匹配器描述符:结果是DMatch对象的列表,该DMatch对象具有以下属性:
 # 1.DMatch.distance:描述符之间的距离。越低,它就越好。
 # 2.DMatch.trainIndex:训练描述符中描述符的索引
 # 3.DMatch.queryIndex:查询描述符中描述符的索引
 # 4.DMatch.imgIndex:训练图像的索引
matches = bf.match(des1, des2)

# 根据距离排序,第六个参数是outImg
img3 = cv2.drawMatches(img1, kp1, img2, kp2, matches[:10], None, flags=2)

plt.imshow(img3), plt.show()

结果:

5.这个匹配对象是什么?

matches=bf.match(des1,des2)行的结果是DMatch对象的列表。该DMatch对象具有以下属性:

  • DMatch.distance - 描述符之间的距离。 越低,它就越好。
  • DMatch.trainIdx – 训练描述符中描述符的索引
  • DMatch.queryIdx - 查询描述符中描述符的索引
  • DMatch.imgIdx – 训练图像的索引

6.使用SIFT描述符和比率测试的Brute-Force匹配(强力匹配)

这一次,我们将使用BFMatcher.knnMatch()来获得k个最佳匹配。 在这个例子中,我们将采取k = 2,以便我们可以应用D.Lowe在他的论文中解释的比率测试。

# -*- coding: utf-8 -*-
'''
下面的栗子:使用SIFT描述符和比率测试的Brute-Force匹配
'''

import numpy as np
import cv2
from matplotlib import pyplot as plt

img1 = cv2.imread('1.jpg', 0)
img2 = cv2.imread('2.jpg', 0)

# 初始化SIFT检测器
sift = cv2.xfeatures2d.SIFT_create()

# 用SIFT找到关键点和描述符
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)

# 默认参数的BFMatcher
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)

# 应用比率测试(ratio test)
good = []
for m, n in matches:
    if m.distance < 0.75 * n.distance:
        good.append([m])

img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, good, None, flags=2)
plt.imshow(img3), plt.show()

查看下面的结果:

7.基于FLANN的匹配器(FLANN based Matcher)

FLANN表近似最近邻居的快速库。它包含一组经过优化的算法,用于大数据集中的快速最近邻搜索以及高维特征。对于大型数据集,它的工作速度比BFMatcher快。我们将看到基于FLANN的匹配器的第二个例子。

对于基于FLANN的匹配器,我们需要传递两个字典来指定要使用的算法及其相关参数等。首先是IndexParams。 对于各种算法,要传递的信息在FLANN文档中进行了解释。总而言之,对于SIFT,SURF等算法,您可以通过以下方法:

index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)

在使用ORB时,您可以传递以下参数。评论值是根据文档推荐的,但在某些情况下它没有提供所需的结果。其他值工作良好:

index_params= dict(algorithm = FLANN_INDEX_LSH,

                   table_number = 6, # 12

                   key_size = 12,     # 20

                   multi_probe_level = 1) #2

第二个字典是SearchParams。它指定了索引中的树应递归遍历的次数。值越高,精度越高,但也需要更多时间。如果你想改变这个值,传入search_params = dict(checks = 100)。

有了这些信息,我们就很好去匹配:

# -*- coding: utf-8 -*-
'''
基于FLANN的匹配器(FLANN based Matcher)
1.FLANN代表近似最近邻居的快速库。它代表一组经过优化的算法,用于大数据集中的快速最近邻搜索以及高维特征。
2.对于大型数据集,它的工作速度比BFMatcher快。
3.需要传递两个字典来指定要使用的算法及其相关参数等
对于SIFT或SURF等算法,可以用以下方法:
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
对于ORB,可以使用以下参数:
index_params= dict(algorithm = FLANN_INDEX_LSH,
                   table_number = 6, # 12   这个参数是searchParam,指定了索引中的树应该递归遍历的次数。值越高精度越高
                   key_size = 12,     # 20
                   multi_probe_level = 1) #2

'''
import cv2
import numpy as np
from matplotlib import pyplot as plt

img1 = cv2.imread('1.jpg', 0)
img2 = cv2.imread('2.jpg', 0)

# 初始化SIFT检测器
sift = cv2.xfeatures2d.SIFT_create()

# 用SIFT找到关键点和描述符
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)

# FLANN参数
FLANN_INDEX_KDTREE = 0
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)  # 或者传递空字典

flann = cv2.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)

# 只需要绘制好的匹配,所以创建一个掩膜
matchesMask = [[0, 0] for i in range(len(matches))]

# 按照Lowe的论文进行比率测试
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=0)

img3 = cv2.drawMatchesKnn(img1, kp1, img2, kp2, matches, None, **draw_params)

plt.imshow(img3), plt.show()

看结果: