OpenCV-Python|Feature模块 — drawMatches()与drawMatchesKnn()分析

OpenCV-Python|Feature模块 — drawMatches与drawMatchesKnn分析

  • 前言
    • 分析

前言

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)

这下我们就明白了,两个函数的参数不同之处在于matches1to2matchesMask,对于drawMatches(),matches1to2matchesMask是一维数组;对于drawMatchesKnn(),matches1to2matchesMask是二维数组。
其余参数完全相同,其中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()替代,具体如何使用还看个人了。

你可能感兴趣的:(OpenCV-Python|Feature模块 — drawMatches()与drawMatchesKnn()分析)