OpenCV-Python|Feature模块 — 特征匹配

前言

OpenCV-Python|Feature 模块 — 特征匹配。
学习暴力匹配和FLANN匹配,利用特征匹配与单应性寻找物体。

暴力匹配

暴力匹配较简单。它选取第一个集合的特征,通过距离计算与第二个集合的所有特征进行匹配,返回距离最近的那个。
我们通过cv.BFMatcher() 创建一个暴力匹配器对象。它有两个参数,第一个参数normType表示距离度量方法,默认是cv.NORM_L2,对于SIFT,SURF较好;对于ORB,BRIEF,BRISK等,使用cv.NORM_HAMMING较好。
第二个参数crossCheck是布尔型变量,默认false。如果为true,表示进行双向匹配,这样就可以增强匹配鲁棒性,是一种替代D.Lowe SIFT论文里比率测试的方法。
BFMatcher提供了两种匹配方法:BFMatcher.match()BFMatcher.knnMatch()。第一种方法返回最佳匹配。第二种方法返回k个最佳匹配,k自定义,通常为2。
最后,可以使用cv.drawMatches()或cv.drawMatchesKnn()画出匹配。

BFMatcher.match()

现在来看一个使用BFMatcher.match()方法进行特征匹配的例子。我们把第一幅图像称为trainImage,把第二幅图像称为trainImage。

import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt

img1 = cv.imread('box.png', 0)			# queryImage
img2 = cv.imread('box_in_scene.png', 0)	# trainImage

# 创建ORB检测器
orb = cv.ORB_create()

# ORB特征检测
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)

# 创建BFMatcher对象
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck=True)

# 特征匹配,match方法
matches = bf.match(des1, des2)
print(len(kp1), des1.shape, len(kp2), des2.shape, len(matches))

# 将距离从小到大排序
matches = sorted(matches, key=lambda x: x.distance)

img3 = cv.drawMatches(img1, kp1, img2, kp2, matches, None, flags=2)

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

首先使用ORB检测特征。接着创建BFMatcher对象,由于使用的是ORB,所以normType=cv.NORM_HAMMING。然后使用BFMatcher.match()方法匹配特征,将距离升序排列,距离越小匹配越好。
运行结果:

kp1、des1、kp2、des2、matches的数量:
453 (453, 32) 500 (500, 32) 226

在这里插入图片描述
Matcher对象
matches = bf.match(des1, des2)返回的是一个DMatch对象列表。DMatch对象有以下属性:

  • DMatch.distance - 两个描述子的距离,值越小匹配越好。
  • DMatch.trainIdx - train描述子中描述子的索引。
  • DMatch.queryIdx - query描述子中描述子的索引。
  • DMatch.imgIdx - train image的索引。

BFMatcher.knnMatch()与比率测试

现在来看一个使用BFMatcher.knnMatch()方法进行特征匹配的例子。设置k=2这样就可以应用D.Lowe论文中的比率测试。

import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt

img1 = cv.imread('box.png', 0)			# queryImage
img2 = cv.imread('box_in_scene.png', 0)	# trainImage

# 创建SIFT检测器
sift = cv.SIFT_create()

# SIFT特征检测
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)

# BFMatcher特征匹配,knnMatch方法
bf = cv.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)

# 比率测试
good = []
for m,n in matches:
    if m.distance < 0.75*n.distance:
        good.append([m])
print(len(kp1), des1.shape, len(kp2), des2.shape, len(matches), len(good))

img3 = cv.drawMatchesKnn(img1, kp1, img2, kp2, good, None, flags=2)

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

运行结果:

kp1、des1、kp2、des2、matches、good的数量:
603 (603, 128) 969 (969, 128) 603 79

在这里插入图片描述

FLANN匹配

FLANN表示近似最近邻的快速库(Fast Library for Approximate Nearest Neighbors),包含了一系列对大型数据和高维特征的快速最近邻搜索算法,比BFMatcher快。
创建FLANN匹配器,需要传递两个字典参数表示算法。第一个参数是IndexParams,对于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, # 12
                   key_size = 12,     # 20
                   multi_probe_level = 1) #2

第二个参数是SearchParams,表示搜索次数,值越高精度越好,但耗时也越多。

import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt

img1 = cv.imread('box.png', 0)			# queryImage
img2 = cv.imread('box_in_scene.png', 0)	# trainImage

# 创建SIFT检测器
sift = cv.SIFT_create()

# SIFT特征检测
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)

# FLANN参数
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)

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

# 只画好的匹配,所以创建一个mask
matchesMask = [[0,0] for i in range(len(matches))]

# ratio test as per Lowe's paper
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 = cv.drawMatchesKnn(img1, kp1, img2, kp2, matches, None, **draw_params)

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

运行结果:

在这里插入图片描述

利用特征匹配与单应性寻找物体

假设我们有一幅queryImage和一幅trainImage,并且求得了两幅图像之间的特征匹配,那我们就可以用通过匹配点求得两幅图像的空间位置映射关系,从而将queryImage作为查询图像,求得其在trainImage上的对应目标。
通过匹配点求得两幅图像的空间位置投影变换,即单应性,可以使用calib3d模块的cv.findHomography() 函数。然后使用cv.perspectiveTransform() 可以找到queryImage的四个顶点在trainImage中对应的顶点,从而画出物体边框。
cv.findHomography() 可以应用RANSAC方法滤除误匹配点,返回单应性矩阵及一个区分内外点的mask。

import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt

MIN_MATCH_COUNT = 10

img1 = cv.imread('box.png', 0)			# queryImage
img2 = cv.imread('box_in_scene.png', 0)	# trainImage

# 创建SIFT检测器
sift = cv.SIFT_create()

# SIFT特征检测
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)

# FLANN参数
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)

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

# store all the good matches as per Lowe's ratio test.
good = []
for m,n in matches:
    if m.distance < 0.7*n.distance:
        good.append(m)
print(len(kp1), des1.shape, len(kp2), des2.shape, len(matches), len(good))

if len(good) > MIN_MATCH_COUNT:
    src_pts = np.float32([ kp1[m.queryIdx].pt for m in good ]).reshape(-1,1,2)
    dst_pts = np.float32([ kp2[m.trainIdx].pt for m in good ]).reshape(-1,1,2)

    M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC, 5.0)
    matchesMask = mask.ravel().tolist()
    print(len(np.nonzero(matchesMask)[0]))

    h,w = img1.shape
    pts = np.float32([ [0,0], [0,h-1], [w-1,h-1], [w-1, 0] ]).reshape(-1,1,2)
    dst = cv.perspectiveTransform(pts, M)

    img2 = cv.polylines(img2, [np.int32(dst)], True, 255, 3, cv.LINE_AA)

else:
    print("Not enough matches are found - {}/{}".format(len(good), MIN_MATCH_COUNT))
    matchesMask = None

draw_params = dict(matchColor = (0,255,0),	# 画出绿色匹配线
                   singlePointColor = None,
                   matchesMask = matchesMask,	#只画内点
                   flags = 2)

img3 = cv.drawMatches(img1, kp1, img2, kp2, good, None, **draw_params)

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

运行结果:

kp1、des1、kp2、des2、matches、good的数量:
603 (603, 128) 969 (969, 128) 603 73
内点数量:
71

在这里插入图片描述

参考

https://docs.opencv.org/4.4.0/dc/dc3/tutorial_py_matcher.html.
https://docs.opencv.org/4.4.0/d1/de0/tutorial_py_feature_homography.html.

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值