OpenCV-Python Feature模块主要是实现一些经典的局部特征描述方法。
在上一篇博客特征匹配中,为了画出匹配,一会使用了cv.drawMatches(),一会使用了cv.drawMatchesKnn(),两者有什么区别吗?
为了了解区别,我们直接去查看一下C++的源码,这两个函数声明在features2d.hpp中:
CV_EXPORTS_W void drawMatches( InputArray img1, const std::vector<KeyPoint>& keypoints1,
InputArray img2, const std::vector<KeyPoint>& keypoints2,
const std::vector<DMatch>& matches1to2, InputOutputArray outImg,
const Scalar& matchColor=Scalar::all(-1), const Scalar& singlePointColor=Scalar::all(-1),
const std::vector<char>& matchesMask=std::vector<char>(), DrawMatchesFlags flags=DrawMatchesFlags::DEFAULT );
/** @overload */
CV_EXPORTS_AS(drawMatchesKnn) void drawMatches( InputArray img1, const std::vector<KeyPoint>& keypoints1,
InputArray img2, const std::vector<KeyPoint>& keypoints2,
const std::vector<std::vector<DMatch> >& matches1to2, InputOutputArray outImg,
const Scalar& matchColor=Scalar::all(-1), const Scalar& singlePointColor=Scalar::all(-1),
const std::vector<std::vector<char> >& matchesMask=std::vector<std::vector<char> >(), DrawMatchesFlags flags=DrawMatchesFlags::DEFAULT );
再看一下drawMatches()和drawMatchesKnn()函数参数:
def drawMatches(img1, keypoints1, img2, keypoints2, matches1to2, outImg, matchColor=None, singlePointColor=None, matchesMask=None, flags=None)
def drawMatchesKnn(img1, keypoints1, img2, keypoints2, matches1to2, outImg, matchColor=None, singlePointColor=None, matchesMask=None, flags=None)
这下我们就明白了,两个函数的参数不同之处在于matches1to2 和matchesMask,对于drawMatches(),matches1to2和matchesMask是一维数组;对于drawMatchesKnn(),matches1to2和matchesMask是二维数组。
其余参数完全相同,其中matchColor表示匹配连线颜色,singlePointColor表示特征点颜色,matchesMask表示画哪些匹配,flags=0表示画特征点和连线,flags=2表示不画特征点。这几个参数可选,可以使用字典传入。
再回到具体的例子,第一个例子是在暴力匹配match()方法中的代码片段:
# 创建BFMatcher对象
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck=True)
# 特征匹配,match方法
matches = bf.match(des1, des2)
# 将距离从小到大排序
matches = sorted(matches, key=lambda x: x.distance)
img3 = cv.drawMatches(img1, kp1, img2, kp2, matches, None, flags=2)
由于暴力匹配match()方法返回的是最佳匹配,那么matches自然就是一个一维列表,所以只能使用drawMatches(),传入matches参数。
第二个例子是暴力匹配knnMatch()方法+ratio test中:
# 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])
img3 = cv.drawMatchesKnn(img1, kp1, img2, kp2, good, None, flags=2)
由于knnMatch()方法返回的是2个最佳匹配,所以matches是二维列表。而在 good.append([m]) 语句中多了一个方括号[],所以good结果还是一个二维列表,并传入drawMatchesKnn()函数。
如果在这里例子中想使用drawMatches()函数,只要去掉[]就可以了:
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)
img3 = cv.drawMatches(img1, kp1, img2, kp2, good, None, flags=2)
效果是相同的。
第三个例子是FLANN匹配中:
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)
matches和matchesMask都是二维列表,所以使用drawMatchesKnn()。
如果想改用drawMatches()呢?可以像上一个例子一样定义一维列表good存放好的匹配:
good = []
for i, (m,n) in enumerate(matches):
if m.distance < 0.7*n.distance:
good.append(m)
draw_params = dict(matchColor = (0,255,0),
singlePointColor = (255,0,0),
matchesMask = None,
flags=0)
img3 = cv.drawMatches(img1, kp1, img2, kp2, good, None, **draw_params)
效果是相同的。
最后一个例子是FLANN匹配+单应性寻找目标物体中:
# 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 = cv.findHomography(src_pts, dst_pts, cv.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 = 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)
在这里,使用good一维列表存放好的匹配。findHomography()返回的mask是一个二维数组,所以通过 matchesMask = mask.ravel().tolist() 语句转化为一维列表。这样就可以使用drawMatches()了。
小结
可见,drawMatchesKnn()能用的地方都可以用drawMatches()替代,具体如何使用还看个人了。