内容来自OpenCV-Python Tutorials 自己翻译整理
目标:
我们将结合特征点匹配和寻找单应性的方法,使用calib3d模块在复杂的图像当中寻找已知目标。
基础:
这里简单说一下什么事单应变换,如果有说的不对,还请各位看官斧正。
一般来讲,二维的图像变换可以分成这几类
综上,简答的来理解单应变换,就是相当于你用一个照相机,正对着一个物体照一张照片。然后你再换个角度,换个距离,换个一个高度对着这个物体再拍一张。那么,你可以用单应变换找到这两张图片对应关系。
上节课我们使用了一个查询图像找到了它的一些特征点,我们又使用另外一个训练图像,同样找到了一些特征点,并且找出他们之间的最佳匹配。
简单的说,我们在另外一组图片当中,找到了一个目标图片的部分位置。这个信息足够在训练图片中精确的找到目标图片当中的对象了。
为了达到上面的目的,我们使用一个calib3d的模块,其中有cv2.findHomography()函数。如果我们传入一组两个图像对应的点集,它会自动找到对应透视变换的目标对象。然后我们可以使用cv2.perspectiveTransform() 函数来找到对应目标。参数至少需要四个正确的对应点来找到这个变换。
我们会看到在匹配的过程中,可能出现一些误差,这些误差可能会影响结果。为了解决这个问题,算法使用了RANSAC(随机抽样一致算法)或者LEAST_MEDIAN(最小中位数平方法?),这两个方法可以通过设定参数来决定使用哪一个。所以一个优秀的匹配点会提供一个正确的估计结果,这个结果叫做内点(inliers),剩下的点被称为外点。cv2.findHomography()返回一个蒙版,可以指定内点和外点。
代码:
首先使用SIFT算法找到特征点,然后使用比值测试找到最佳匹配。
import numpy as np
import cv2
from matplotlib import pyplot as plt
MIN_MATCH_COUNT = 10
img1 = cv2.imread('1.jpg',0)
img2 = cv2.imread('26.jpg',0)
sift = cv2.xfeatures2d.SIFT_create()
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
FLANN_INDEX_KDTREE = 0#kd树
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50) # or pass empty dictionary
flann = cv2.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)
现在我们要设定一个条件,至少要找到十个匹配才能开始寻找目标(设定MIN_MATCH_COUNT参数)。否则显示信息没有足够的匹配点。
如果找到了足够的匹配点,我们可以在两幅图像中,提取匹配到的关键点的位置信息。将他们当做参数传递给要寻找对应透视变换的算法。一旦我们得到的了这个3×3的变换矩阵,我们就可以使用它来变换查询图片的角点信息来对应到训练图片的角点信息。然后把它们画出来。
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
最后画出我们的“内点”(如果成功找到目标)或者匹配点(失败)
原图:
import numpy as np
import cv2
from matplotlib import pyplot as plt
MIN_MATCH_COUNT = 10
img1 = cv2.imread('a.jpg',0)
img2 = cv2.imread('b.jpg',0)
sift = cv2.xfeatures2d.SIFT_create()
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
FLANN_INDEX_KDTREE = 0#kd树
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50) # or pass empty dictionary
flann = cv2.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)
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()