模块对象没有属性'drawMatches',OpenCV Python。

45

我只是在OpenCV中进行特征检测的示例。下面是该示例。它给我以下错误:

module'对象没有'drawMatches'属性

我已经查看了OpenCV文档,但不确定为什么会出现此错误。有人知道原因吗?

import numpy as np
import cv2
import matplotlib.pyplot as plt

img1 = cv2.imread('box.png',0)          # queryImage
img2 = cv2.imread('box_in_scene.png',0) # trainImage

# Initiate SIFT detector
orb = cv2.ORB()

# find the keypoints and descriptors with SIFT
kp1, des1 = orb.detectAndCompute(img1,None)
kp2, des2 = orb.detectAndCompute(img2,None)

# create BFMatcher object
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

# Match descriptors.
matches = bf.match(des1,des2)

# Draw first 10 matches.
img3 = cv2.drawMatches(img1,kp1,img2,kp2,matches[:10], flags=2)

plt.imshow(img3),plt.show()

错误:

Traceback (most recent call last):
File "match.py", line 22, in <module>
img3 = cv2.drawMatches(img1,kp1,img2,kp2,matches[:10], flags=2)
AttributeError: 'module' object has no attribute 'drawMatches'

可能是如何在Python中使用OpenCV模块可视化描述符匹配的重复问题。 - Lokesh A. R.
哪些文档?并确保您使用的是正确的版本。 - aIKid
1
文档链接:https://opencv-python-tutroals.readthedocs.org/en/latest/py_tutorials/py_feature2d/py_matcher/py_matcher.html - Javed
这些不是官方的OpenCV文档!官方的OpenCV文档可以在以下网址找到:http://docs.opencv.org/ - Mailerdaimon
官方文档:不幸的是,我已经看过这个OpenCV 3.0.0-dev文档。 - Javed
你说得对,这些是官方文档,但那些是开发者文档,可能会有所更改。因此,在生产代码中使用它们时要小心!但如果你使用OpenCV 3.0.0,Python绑定应该已经存在,你的代码应该可以工作。 - Mailerdaimon
3个回答

78

我也有点晚了,但是我在Mac OS X上安装了OpenCV 2.4.9,而我的发行版中没有 drawMatches 函数。我也尝试了第二种方法,使用 find_obj,但对我也不起作用。因此,我决定自己编写实现它的代码,尽力模仿 drawMatches,以下是我的实现。

我提供了自己的图像,其中一个是相机人的图像,另一个是同一图像逆时针旋转55度后的图像。

我编写的基本原理是分配一个输出RGB图像,其中行数是两个图像中最大的那个,以容纳将两个图像放置在输出图像中,列则是两列之和。请注意,我假设两个图像都是灰度的。

我将每个图像放置在其对应的位置,然后运行所有匹配关键点的循环。我提取了两个图像之间匹配的关键点,然后提取它们的 (x,y) 坐标。我在检测到的每个位置绘制圆,并画一条连接这些圆的直线。

请记住,第二个图像中检测到的关键点是相对于它自己的坐标系的。如果您想将其放置在最终输出图像中,则需要通过第一个图像的列数来偏移列坐标,以便列坐标相对于输出图像的坐标系。

不多说了:

import numpy as np
import cv2

def drawMatches(img1, kp1, img2, kp2, matches):
    """
    My own implementation of cv2.drawMatches as OpenCV 2.4.9
    does not have this function available but it's supported in
    OpenCV 3.0.0

    This function takes in two images with their associated 
    keypoints, as well as a list of DMatch data structure (matches) 
    that contains which keypoints matched in which images.

    An image will be produced where a montage is shown with
    the first image followed by the second image beside it.

    Keypoints are delineated with circles, while lines are connected
    between matching keypoints.

    img1,img2 - Grayscale images
    kp1,kp2 - Detected list of keypoints through any of the OpenCV keypoint 
              detection algorithms
    matches - A list of matches of corresponding keypoints through any
              OpenCV keypoint matching algorithm
    """

    # Create a new output image that concatenates the two images together
    # (a.k.a) a montage
    rows1 = img1.shape[0]
    cols1 = img1.shape[1]
    rows2 = img2.shape[0]
    cols2 = img2.shape[1]

    # Create the output image
    # The rows of the output are the largest between the two images
    # and the columns are simply the sum of the two together
    # The intent is to make this a colour image, so make this 3 channels
    out = np.zeros((max([rows1,rows2]),cols1+cols2,3), dtype='uint8')

    # Place the first image to the left
    out[:rows1,:cols1] = np.dstack([img1, img1, img1])

    # Place the next image to the right of it
    out[:rows2,cols1:] = np.dstack([img2, img2, img2])

    # For each pair of points we have between both images
    # draw circles, then connect a line between them
    for mat in matches:

        # Get the matching keypoints for each of the images
        img1_idx = mat.queryIdx
        img2_idx = mat.trainIdx

        # x - columns
        # y - rows
        (x1,y1) = kp1[img1_idx].pt
        (x2,y2) = kp2[img2_idx].pt

        # Draw a small circle at both co-ordinates
        # radius 4
        # colour blue
        # thickness = 1
        cv2.circle(out, (int(x1),int(y1)), 4, (255, 0, 0), 1)   
        cv2.circle(out, (int(x2)+cols1,int(y2)), 4, (255, 0, 0), 1)

        # Draw a line in between the two points
        # thickness = 1
        # colour blue
        cv2.line(out, (int(x1),int(y1)), (int(x2)+cols1,int(y2)), (255,0,0), 1)


    # Show the image
    cv2.imshow('Matched Features', out)
    cv2.waitKey(0)
    cv2.destroyWindow('Matched Features')

    # Also return the image if you'd like a copy
    return out

为了说明这个方法的可行性,这里是我使用的两张图片:

摄像机图像

旋转后的摄像机图像

我使用了OpenCV的ORB检测器来检测关键点,并且使用标准化的汉明距离作为相似性的距离度量,因为这是一种二进制描述符。 因此:

import numpy as np
import cv2

img1 = cv2.imread('cameraman.png', 0) # Original image - ensure grayscale
img2 = cv2.imread('cameraman_rot55.png', 0) # Rotated image - ensure grayscale

# Create ORB detector with 1000 keypoints with a scaling pyramid factor
# of 1.2
orb = cv2.ORB(1000, 1.2)

# Detect keypoints of original image
(kp1,des1) = orb.detectAndCompute(img1, None)

# Detect keypoints of rotated image
(kp2,des2) = orb.detectAndCompute(img2, None)

# Create matcher
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

# Do matching
matches = bf.match(des1,des2)

# Sort the matches based on distance.  Least distance
# is better
matches = sorted(matches, key=lambda val: val.distance)

# Show only the top 10 matches - also save a copy for use later
out = drawMatches(img1, kp1, img2, kp2, matches[:10])

这是我得到的图像:

Matched Features


cv2.BFMatcher 中的 knnMatch 一起使用

我想指出上面的代码仅在假定匹配项出现在 1D 列表中时才起作用。但是,如果您决定使用来自 cv2.BFMatcherknnMatch 方法,那么返回的将是一个列表嵌套列表的形式。具体而言,在给定名为 des1img1 描述符和名为 des2img2 描述符的情况下,从 knnMatch 返回的列表中的每个元素都是另一个列表,其中包含了与 des1 中各描述符最相似的 k 个来自 des2 的匹配项。因此,knnMatch 输出的第一个元素是来自 des2k 个最接近 des1 中找到的第一个描述符的匹配项列表。第二个元素是来自 des2k 个最接近 des1 中找到的第二个描述符的匹配项列表,以此类推。

为了最大程度地利用 knnMatch 的优势,您必须将要匹配的邻居的总数限制为k=2。原因是因为您想使用至少两个匹配点来验证每个可用源点的质量,并且如果质量足够好,您将希望使用这些点来绘制匹配项并在屏幕上显示它们。您可以使用一个非常简单的比率测试(由David Lowe创作)来确保对于一个点,我们看到匹配到最佳点的距离/不相似性远小于匹配到第二佳点的距离/不相似性。我们可以通过计算最佳匹配点与次佳匹配点之间距离的比率来捕获这一点。这个比率应该很小,以说明点到其最佳匹配点是明确的。如果比率接近 1,则意味着两个匹配项同样“好”,因此是不确定的,因此我们不应将其包括在内。我们可以将此视为异常值拒绝技术。因此,要将从 knnMatch 返回的内容转换为我上面编写的代码所需的内容,请遍历匹配项,使用上述比率测试并检查是否通过。如果通过,请将第一个匹配的关键点添加到新列表中。

假设您像之前一样声明了所有变量以创建 BFMatcher 实例,则现在要做的是调整 knnMatch 方法以适用于使用 drawMatches

# Create matcher
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

# Perform KNN matching
matches = bf.knnMatch(des1, des2, k=2)

# Apply ratio test
good = []
for m,n in matches:
    if m.distance / n.distance < 0.75: # Or you can do m.distance < 0.75 * n.distance
       # Add the match for point m to the best 
       good.append(m)

# Or do a list comprehension
#good = [m for (m,n) in matches if m.distance < 0.75*n.distance]

# Now perform drawMatches
out = drawMatches(img1, kp1, img2, kp2, good)
当你遍历 matches 列表时,mn 应该是来自 des1 中某个点和其在 des2 中的最佳匹配(m)及次佳匹配(n)。如果发现比值很小,就会将这两个点之间的最佳匹配(m)添加到最终列表中。我所用的比值为0.75,这是需要调整的参数,如果结果不佳,请尝试调整该参数值。然而,0.7到0.8之间的值是一个好的起点。
以上修改要归功于用户 @ryanmeasel,并且这些修改的答案可以在他的帖子中找到:OpenCV Python : No drawMatchesknn function

3
谢谢这个……返回“out”非常有用,因为我正在运行一个小型嵌入式设备上,重新编译OpenCV需要耗费我10小时。这使我能够完成我的工作并上床睡觉 :) - wentbackward
@wentbackward - 你好!不客气!返回 out 的工作做得很好。我只是想展示实际的图像匹配,但返回 out 以便保存结果以后展示也很棒 :) 祝一切顺利! - rayryeng
@Aphire - 啊,谢谢 :) 很高兴这段代码对你有帮助! - rayryeng
我无法再编辑我的问题了,但问题是图像没有进行灰度处理 :) - primoz
@Primoz 是的,我的评论中说图像应该是灰度的。顺便说一句,如果你觉得这个答案有帮助,可以点个赞 :) - rayryeng
显示剩余8条评论

18
drawMatches 函数不是 Python 接口的一部分。正如您在 docs 中看到的,目前它仅为 C++ 定义。
来自文档的摘录:
 C++: void drawMatches(const Mat& img1, const vector<KeyPoint>& keypoints1, const Mat& img2, const vector<KeyPoint>& keypoints2, const vector<DMatch>& matches1to2, Mat& outImg, const Scalar& matchColor=Scalar::all(-1), const Scalar& singlePointColor=Scalar::all(-1), const vector<char>& matchesMask=vector<char>(), int flags=DrawMatchesFlags::DEFAULT )
 C++: void drawMatches(const Mat& img1, const vector<KeyPoint>& keypoints1, const Mat& img2, const vector<KeyPoint>& keypoints2, const vector<vector<DMatch>>& matches1to2, Mat& outImg, const Scalar& matchColor=Scalar::all(-1), const Scalar& singlePointColor=Scalar::all(-1), const vector<vector<char>>& matchesMask=vector<vector<char> >(), int flags=DrawMatchesFlags::DEFAULT )

如果该函数有Python界面,你会发现类似于这样的内容:
 Python: cv2.drawMatches(img1, keypoints1, [...]) 

编辑

实际上,5个月前就有一次commit引入了这个功能。然而,它尚未包含在官方文档中。
请确保您正在使用最新版本的OpenCV(2.4.7)。 为了完整起见,OpenCV 3.0.0的函数接口将如所示:

cv2.drawMatches(img1, keypoints1, img2, keypoints2, matches1to2[, outImg[, matchColor[, singlePointColor[, matchesMask[, flags]]]]]) → outImg

3
我在这里看到了它:https://opencv-python-tutroals.readthedocs.org/en/latest/py_tutorials/py_feature2d/py_matcher/py_matcher.html - Javed
请看我的编辑,它是5个月前添加的,但尚未包含在官方文档中。 - Mailerdaimon
如果我的回答有帮助,请考虑接受它。要将答案标记为已接受,请单击答案旁边的复选标记,将其从空心变为绿色。谢谢! - Mailerdaimon
1
如果已经提交了代码,那么这个函数不就应该可用了吗?但是我在我的py2.7.6_0安装中无法使用它,同时也安装了openCV 2.4.8_0。 - Paul Seeb
不,它被提交到主干并不意味着它必须进入每个标签(如2.4.8)。我不知道计划是什么,但我猜这些更改将集成在openCv 3.0中。然而,如果您真的需要此功能,您可以下载源代码并构建OpenCV和Python绑定。 - Mailerdaimon

16

我知道这个问题有一个被接受的正确答案,但如果你在使用OpenCV 2.4.8而不是3.0(-dev),一个解决方法可能是使用一些来自opencv\sources\samples\python2\find_obj中包含的示例函数。

import cv2
from find_obj import filter_matches,explore_match

img1 = cv2.imread('../c/box.png',0)          # queryImage
img2 = cv2.imread('../c/box_in_scene.png',0) # trainImage

# Initiate SIFT detector
orb = cv2.ORB()

# find the keypoints and descriptors with SIFT
kp1, des1 = orb.detectAndCompute(img1,None)
kp2, des2 = orb.detectAndCompute(img2,None)

# create BFMatcher object
bf = cv2.BFMatcher(cv2.NORM_HAMMING)#, crossCheck=True)

matches = bf.knnMatch(des1, trainDescriptors = des2, k = 2)
p1, p2, kp_pairs = filter_matches(kp1, kp2, matches)
explore_match('find_obj', img1,img2,kp_pairs)#cv2 shows image

cv2.waitKey()
cv2.destroyAllWindows()

这是输出图像:

输入图像描述


谢谢你的帮助,但我自己为所需的函数和其他函数添加了一个接口...还是感谢。 - Javed

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接