近期一直研究图像的拼接问题。图像拼接前,找到各个图像的特征点是个非常关键的步骤。这期专栏,我将介绍两种较常用的特征匹配方法(基于OpenCV),Brute-Force匹配和FLANN匹配。

1、BF匹配

cv2.BFMatch( normType, crossCheck=True/False)

其中normType是用来指定要使用的距离测试类型。默认值为cv2.Norm_L2,适用于SIFT,SURF方法,还有一个参数为cv2.Norm_L1。如果是ORB,BRIEF,BRISK算法等,要是用cv2.NORM_HAMMING,如果ORB算法的参数设置为VTA_K==3或4,normType就应该设置为cv2.NORM_HAMMING2.

第二个参数是crossCheck,默认值是False。如果设置为True,匹配条件会更加严格。举例来说,如果A图像中的i点和B图像中的j点距离最近,并且B中的j点到A中的i点距离也最近,相互匹配,这个匹配结果才会返回。

import cv2 as cv
from matplotlib import pyplot as plt
#读取需要特征匹配的两张照片,格式为灰度图。
img1=cv.imread("image/work1.jpg",0)
img2=cv.imread("image/work2.jpg",0)
orb=cv.ORB_create()#建立orb特征检测器
kp1,des1=orb.detectAndCompute(img1,None)#计算img1中的特征点和描述符
kp2,des2=orb.detectAndCompute(img2,None) #计算img2中的
bf = cv.BFMatcher(cv.NORM_HAMMING,crossCheck=True) #建立匹配关系
mathces=bf.match(des1,des2) #匹配描述符
mathces=sorted(mathces,key=lambda x:x.distance) #据距离来排序img3= cv.drawMatches(img1,kp1,img2,kp2,mathces[:40],is=2) #画出匹配关系
plt.imshow(img3),plt.show() #matplotlib描绘出来图1 BF匹配结果
2、FLANN匹配
import cv2 as cv
from matplotlib import pyplot as plt
queryImage=cv.imread("image/work1.jpg",0)
trainingImage=cv.imread("image/work2.jpg",0)#读取要匹配的灰度照片
sift=cv.xfeatures2d.SIFT_create()#创建sift检测器
kp1, des1 = sift.detectAndCompute(queryImage,None)
kp2, des2 = sift.detectAndCompute(trainingImage,None)
#设置Flannde参数
FLANN_INDEX_KDTREE=0
indexParams=dict(algorithm=FLANN_INDEX_KDTREE,trees=5)
searchParams= dict(checks=50)
flann=cv.FlannBasedMatcher(indexParams,searchParams)
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]
drawParams=dict(matchColor=(0,0,255),singlePointColor=(255,0,0),matchesMask=matchesMask,flags=0) #给特征点和匹配的线定义颜色
resultimage=cv.drawMatchesKnn(queryImage,kp1,trainingImage,kp2,matches,None,**drawParams) #画出匹配的结果
plt.imshow(resultimage,),plt.show()

FLANN 匹配器有两个参数,一个是indexParams,另一个是searchParams,以字典的形式进行参数传递。为了计算匹配,FLANN内部会决定如何处理索引和搜索对象。

索引方法可以选择LinearIndex,KTreeIndex,KMeansIndex等,其中KTreeIndex配置索引简单,只需制定待处理核密度的数量即可,推荐值为1~16。SearchParams只包含一个字段,checks,表示制定索引树要被遍历的次数。经验值推荐,5 kd—trees,50 checks 可以取得较好的匹配精度,并且可以在较短的时间内完成。

在匹配结果中我们根据Lowe的论文,删除匹配结果距离大于0.7的。通过这种过滤,可以避免几乎90%的错误匹配。原文“Distinctive Image Feature from scale Scale-Invariant Keypoints”

匹配结果如下:图2 FLANN匹配结果

更新一下。

3、FLANN单应性匹配

两幅不在一个平面角度的照片,通过其中一幅照片(小图像)的特征点,与第二幅(大图像)中的特征点可以确定这部分位置在第二幅中的位置。我们通过cv2.findHomography()函数,计算这对图像的透视变化矩阵,然后通过cv2.perspectiveTransform()找到在第二图像(大图像)中的第一幅图像的位置。

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
MIN_MATCH_COUNT=10 #设置最低匹配数量为10
img1=cv.imread("image/pen.jpg",0) #读取第一个图像(小图像)
img2=cv.imread("image/work2.jpg",0) #读取第二个图像(大图像)
sift=cv.xfeatures2d.SIFT_create() #创建sift检测器
kp1,des1=sift.detectAndCompute(img1,None)
kp2,des2=sift.detectAndCompute(img2,None)
#创建设置FLAAN匹配
FLANN_INDEX_KDTREE=0
index_params=dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params=dict(checks=50)
flann=cv.FlannBasedMatcher(index_params,search_params)
mathces=flann.knnMatch(des1,des2,k=2)
good=[]
#过滤不合格的匹配结果,大于0.7的都舍弃
for m,n in mathces:
if m.distance<0.7*n.distance:
good.append(m)
#如果匹配结果大于10,则获取关键点的坐标,用于计算变换矩阵
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,10.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=cv.perspectiveTransform(pts,M)
cv.polylines(img2,[np.int32(dst)],True,0,5,cv.LINE_AA)
else:
print(" Not Enough matches are found")
matchesMask=None
#画出特征匹配线
draw_params=dict(matchColor=(0,255,0),singlePointColor=None,
matchesMask=matchesMask,flags=2)
#plt展示最终的结果
img3=cv.drawMatches(img1,kp1,img2,kp2,good,None,**draw_params)
plt.imshow(img3),plt.show()

展示下最终的结果,其中白色和黑色的边框,就是通过变换矩阵计算得到的。原始照片(大图像)原始照片(小图像)FLANN单应性匹配原始图像(大图像)原始图像(小图像)FLANN单应性匹配