基于ORB的特征检测和特征匹配
ORB是用来代替SIFT和SURF的,具有更快的速度,发布于2011年。ORB首次提出于论文ORB: an efficient alternative to SIFT or SURF。
ORB将基于FAST关键点检测的技术和基于BRIEF描述符发技术结合,故先介绍FAST和BRIEF,然后再讨论Brute-Force匹配(其中的一种特征匹配算法),并展示一个特征匹配的例子。
-
FAST
FAST(Features from Accelerated Segment Test)算法会在像素周围绘制一个圆,该圆包括16个像素,然后将每个像素与加上一个阈值的圆心像素值进行比较,若有连续、比加上一个阈值的圆心的像素值还亮或暗的像素,则可认为圆心是角点。
FAST方法与阈值紧密相关,要求开发人员输入参数(SIFT不需要这样的输入)。 -
BRIEF
BRIEF(Binary Robust Independent Elementary Features)并不是一个特征检测算法,它只是一个描述符。
在使用SIFT和SURF分析图像时,核心函数是detectAndCompute函数,包括检测和计算。如果把这两步的结果以元组形式输出,detectAndCompute函数会返回两种不同的结果。检测结果是关键点,计算结果是描述符。这意味着OpenCV的SIFT类和SURF类既是检测器,又是描述符。
关键点描述符是图像的一种表示,因为可以比较两个图像的关键点描述符,并找到它们的共同之处,所以描述符可以作为特征匹配的一种方法(gateway)。 -
暴力匹配
暴力(Brute-Force)匹配方法是一种描述符匹配方法,该方法会比较两个描述符,并产生匹配结果的列表。该算法基本上不涉及优化,第一个描述符所有特征都用来和第二个描述符的特征进行比较。每次比较给出一个匹配值,最好的一个匹配结果称为一个匹配。
OpenCV专门提供了BFMatcher对象来实现暴力匹配。
ORB特征匹配
在ORB的论文中,作者得到了如下结果:
- 向FAST增加一个快速、准确的方向向量(component)
- 能高效计算带方向的BRIEF特征
- 基于带方向的BRIEF特征的方差分析和相关性分析
- -在旋转不变性的条件下学习一种不相关的BRIEF特征,这会在最邻近的应用中得到较好的性能
ORB旨在优化和加快操作速度,包括:以旋转感知(rotation-aware)的方式使用BRIEF,这样即使在训练图像与查询图像之间旋转差别很大的情况下也能够提高匹配效果。
下面代码表示乐队logo与乐队专辑进行匹配
代码
# -*- coding: utf-8 -*-
"""
Created on Sun Jan 3 21:25:48 2021
@author: gkm0120
"""
import numpy as np
import cv2
from matplotlib import pyplot as plt
# 查询和测试图像
img1 = cv2.imread('../images/manowar_logo.png',0)
img2 = cv2.imread('../images/manowar_single.jpg',0)
# 创建ORB特征检测器和描述符
orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(img1,None)
kp2, des2 = orb.detectAndCompute(img2,None)
# 暴力匹配
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1,des2)
# 按距离排序
matches = sorted(matches, key = lambda x:x.distance)
img3 = cv2.drawMatches(img1,kp1,img2,kp2, matches[:25], img2,flags=2)
plt.imshow(img3),plt.show()
输入
|
|
图例
注:使用cv2.drawMatches绘制这些匹配。
K-最邻近匹配
K-最近邻(KNN)也是一个匹配检测算法。若要在脚本中使用KNN,需做两方面修改:1. 用KNN代替暴力匹配;2. 对绘制匹配的地方进行修改,具体如下
代码
# -*- coding: utf-8 -*-
"""
Created on Thu Jan 7 16:42:08 2021
@author: gkm0120
"""
import numpy as np
import cv2
from matplotlib import pyplot as plt
# query and test images
img1 = cv2.imread('../images/manowar_logo.png',0)
img2 = cv2.imread('../images/manowar_single.jpg',0)
# create the ORB detector
orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(img1,None)
kp2, des2 = orb.detectAndCompute(img2,None)
# brute force matching
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)
matches = bf.knnMatch(des1,des2,k=2)
# Sort by distance.
img3 = cv2.drawMatchesKnn(img1,kp1,img2,kp2, matches, img2,flags=2)
plt.imshow(img3),plt.show()
图例
注:1. match函数与knnMatch函数区别:match函数返回最佳匹配,KNN函数返回看k个匹配。
2.若报出如下错误
cv2.error: /opt/local/var/macports/build/_opt_local_var_macports_sources_rsync.macports.org_release_tarballs_ports_graphics_opencv/opencv/work/opencv-3.0.0/modules/core/src/stat.cpp:3682: error: (-215) K == 1 && update == 0 && mask.empty() in function batchDistance
可将crossCheck = True
设为crossCheck = False
,可参考knnMatch does not work with K != 1。
FLANN匹配
近似最邻近的快速库(Fast Library for Approximate Nearest Neighbors,FLANN)具有一种内部机制,该机制可以根据本身选择合适的方法处理数据集。Github地址
下面介绍一个基于FLANN进行特征匹配的例子。
代码
# -*- coding: utf-8 -*-
"""
Created on Thu Jan 7 19:36:28 2021
@author: gkm0120
"""
import numpy as np
import cv2
from matplotlib import pyplot as plt
queryImage = cv2.imread('../images/bathory_album.jpg',0)
trainingImage = cv2.imread('../images/bathory_vinyls.jpg',0)
# 创建SIFT并检测/计算
sift = cv2.xfeatures2d.SIFT_create()
kp1, des1 = sift.detectAndCompute(queryImage,None)
kp2, des2 = sift.detectAndCompute(trainingImage,None)
# FLANN匹配参数
FLANN_INDEX_KDTREE = 0
indexParams = dict(algorithm = 0, trees = 5)
searchParams = dict(checks=50) # 或通过空字典
flann = cv2.FlannBasedMatcher(indexParams,searchParams)
matches = flann.knnMatch(des1,des2,k=2)
# 准备一个空的掩模来画出好比赛
matchesMask = [[0,0] for i in range(len(matches))]
# David G. Lowe的比率测试,填充掩模
for i,(m,n) in enumerate(matches):
if m.distance < 0.7*n.distance:
matchesMask[i]=[1,0]
drawParams = dict(matchColor = (0,255,0),
singlePointColor = (255,0,0),
matchesMask = matchesMask,
flags = 0)
resultImage = cv2.drawMatchesKnn(queryImage,kp1,trainingImage,kp2,matches,None,**drawParams)
plt.imshow(resultImage,),plt.show()
图例
注:FLANN匹配器有两个参数:indexParams和searchParams。这两个参数在Python中以字典形式进行参数传递(在C++中以结构体形式进行参数传递)。其中丢弃任何距离大于0.7的值,可以避免几乎90%的错误匹配,但得到的 “好” 匹配也会减少(来源:Distinctive Image Feature from Scale-Invariant Keupoints)。
FLANN的单适应匹配
简单来说,单适应是一个条件,该条件表明当两幅图片中的一幅出现投影畸变(perspective distortion)时,它们还能彼此匹配。
下面代表表示:在左边有一幅图像,该图像正确识别了右侧的图像,画出了关键点之间的匹配线段,而且还画了一个白色方框,用来展示目标在右侧发生投影畸变的效果:
代码
# -*- coding: utf-8 -*-
"""
Created on Thu Jan 7 19:58:37 2021
@author: gkm0120
"""
import numpy as np
import cv2
from matplotlib import pyplot as plt
MIN_MATCH_COUNT = 10 # 确保至少有一定数目的良好匹配(计算单适应至少要4个匹配)
img1 = cv2.imread('../images/tattoo_seed.jpg',0)
img2 = cv2.imread('../images/hush.jpg',0)
# 启动SIFT检测器
sift = cv2.xfeatures2d.SIFT_create()
# 使用SIFT查找关键点和描述符
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
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)
# 根据Lowe的比率测试存储所有符合条件的匹配项。
good = []
for m,n in matches:
if m.distance < 0.7*n.distance:
good.append(m)
# 在原始图像和训练图像中发现关键点
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 = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC,5.0)
matchesMask = mask.ravel().tolist()
#对第二张图片计算相对原始目标的投影畸变,并可以绘制边框
h,w = img1.shape
pts = np.float32([ [0,0],[0,h-1],[w-1,h-1],[w-1,0] ]).reshape(-1,1,2)
dst = cv2.perspectiveTransform(pts,M)
img2 = cv2.polylines(img2,[np.int32(dst)],True,255,3, cv2.LINE_AA)
else:
print ("Not enough matches are found - %d/%d" % (len(good),MIN_MATCH_COUNT))
matchesMask = None
draw_params = dict(matchColor = (0,255,0), # draw matches in green color
singlePointColor = None,
matchesMask = matchesMask, # draw only inliers
flags = 2)
# 绘制单应性图像中关键点的匹配线
img3 = cv2.drawMatches(img1,kp1,img2,kp2,good,None,**draw_params)
plt.imshow(img3, 'gray'),plt.show()
输入
|
|