我有一个项目,我想在图像中找到一堆箭头,如下所示: ibb.co/dSCAYQ
使用以下模板: ibb.co/jpRUtQ

我在Python中使用cv2的模板匹配功能。我的算法是将模板旋转360度并为每次旋转匹配。我得到以下结果: ibb.co/kDFB7k

如您所见,除了2个箭头非常接近以外,其他所有箭头都位于模板的黑色区域之外,它的效果很好。

我正在尝试使用 mask ,但是cv2似乎根本没有应用我的 mask ,即无论 mask 数组具有什么值,匹配都是相同的。已经尝试了两天,但是cv2的有限文档没有帮助。

这是我的代码:

import numpy as np
import cv2
import os
from scipy import misc, ndimage

STRIPPED_DIR = #Image dir
TMPL_DIR = #Template dir
MATCH_THRESH = 0.9
MATCH_RES = 1  #specifies degree-interval at which to match

def make_templates():
    base = misc.imread(os.path.join(TMPL_DIR,'base.jpg')) # The templ that I rotate to make 360 templates
    for deg in range(360):
        print('making template: ' + str(deg))
        tmpl = ndimage.rotate(base, deg)
        misc.imsave(os.path.join(TMPL_DIR, 'tmp' + str(deg) + '.jpg'), tmpl)

def make_masks():
    for deg in range(360):
        tmpl = cv2.imread(os.path.join(TMPL_DIR, 'tmp' + str(deg) + '.jpg'), 0)
        ret2, mask = cv2.threshold(tmpl, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
        cv2.imwrite(os.path.join(TMPL_DIR, 'mask' + str(deg) + '.jpg'), mask)

def match(img_name):
    img_rgb = cv2.imread(os.path.join(STRIPPED_DIR, img_name))
    img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)

    for deg in range(0, 360, MATCH_RES):
        tmpl = cv2.imread(os.path.join(TMPL_DIR, 'tmp' + str(deg) + '.jpg'), 0)
        mask = cv2.imread(os.path.join(TMPL_DIR, 'mask' + str(deg) + '.jpg'), 0)
        w, h = tmpl.shape[::-1]
        res = cv2.matchTemplate(img_gray, tmpl, cv2.TM_CCORR_NORMED, mask=mask)
        loc = np.where( res >= MATCH_THRESH)

        for pt in zip(*loc[::-1]):
            cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)
        cv2.imwrite('res.png',img_rgb)

我认为有些事情可能是错误的,但不确定如何解决:
  • mask/tmpl/img应该具有的 channel 数。我尝试了一个带有彩色4 channel png的stackoverflow eg.的示例,但不确定如何将其转换为灰度或3 channel jpeg。
  • 掩码数组的值。例如 mask 的像素应为1还是255?

  • 任何帮助是极大的赞赏。

    更新
    我修复了代码中的一个小错误;在matchTemplate()的参数中必须使用mask = mask。这与使用255的掩码值相结合产生了不同。但是,现在我收到大量的误报,如下所示:
    http://ibb.co/esfTnk 请注意,假阳性比真实阳性的相关性更高。
    关于如何修复口罩以解决此问题的任何指示?现在,我只是在对模板进行黑白转换。

    最佳答案

    您已经找到了第一个问题,但是我将在这些问题上进行扩展:

    对于二进制掩码,它应该是uint8类型,其中值只是零或非零。具有零的位置将被忽略,并且如果它们不为零,则将其包括在掩码中。您可以传递float32代替作为 mask ,在这种情况下,它可以使像素加权。因此将忽略值0,包含1和包含0.5,但仅赋予其权重是另一个像素的一半。请注意,TM_SQDIFFTM_CCORR_NORMED仅支持掩码,但这很好,因为您正在使用后者。 matchTemplate的掩码仅是单个 channel 。正如您所发现的,mask不是位置参数,因此必须使用参数mask=your_mask中的键来调用它。所有这些在this page on the OpenCV docs中都是非常明确的。

    现在开始新的问题:

    这与您使用的方法以及您使用jpg的事实有关。看看formulas for the normed methods。如果图像完全为零,则将得到错误的结果,因为您将被零除。但这不是确切的问题-因为返回nan,而np.nan > value始终返回false,所以您永远不会从nan值中画出一个正方形。

    相反,问题出在提示您非零值的边缘情况。由于您使用的是jpg图片,因此并非所有的黑色值都完全为0;实际上,很多不是。请注意,从公式中可以得出平均值,当图像窗口中的值有1、2、5等时,平均值将非常小,因此会炸毁相关值。您应该改用TM_SQDIFF(因为这是唯一允许使用掩码的其他方法)。另外,由于您使用的是jpg,因此大多数掩码都是毫无值(value)的,因为任何非零值(甚至1)都将被计为包含。您应该使用png作为掩码。只要模板具有适当的掩码,对于模板使用jpg还是png都无关紧要。

    使用TM_SQDIFF,而不是寻找最大值,而是寻找最小值-您希望模板和图像补丁之间的差异最小。您知道差异应该很小-像素完美匹配的精确度为0,您可能不会获得。您可以稍微增加一些阈值。请注意,每次旋转总是会获得非常接近的值,因为模板的性质-小箭头栏几乎不会添加那么多正值,并且不一定要保证一度离散化完全正确(除非您以这种方式制作图像)。但是,即使箭头指向完全错误的方向,由于存在很多重叠,箭头仍将非常接近。而朝向正确方向的箭头将实际上接近具有正确方向的值。

    在运行代码时,预览平方差的结果是什么:

    res = cv2.matchTemplate(img_gray, tmpl, cv2.TM_SQDIFF, mask=mask)
    cv2.imshow("result", res.astype(np.uint8))
    if cv2.waitKey(0) & 0xFF == ord('q'):
        break
    

    带有 mask 的灰度图像上的Python openCV matchTemplate-LMLPHP

    您可以看到基本上模板的每个方向都紧密匹配。

    无论如何,似乎将其设置为8的阈值:

    带有 mask 的灰度图像上的Python openCV matchTemplate-LMLPHP

    我在代码中修改的唯一一件事是将所有图像都更改为png,切换为TM_SQDIFF,确保loc查找的值小于阈值而不是大于阈值,并使用MATCH_THRESH为8。至少我认为这就是我的全部改变了。看看以防万一:
    import numpy as np
    import cv2
    import os
    from scipy import misc, ndimage
    
    STRIPPED_DIR = ...
    TMPL_DIR = ...
    MATCH_THRESH = 8
    MATCH_RES = 1  #specifies degree-interval at which to match
    
    def make_templates():
        base = misc.imread(os.path.join(TMPL_DIR,'base.jpg')) # The templ that I rotate to make 360 templates
        for deg in range(360):
            print('making template: ' + str(deg))
            tmpl = ndimage.rotate(base, deg)
            misc.imsave(os.path.join(TMPL_DIR, 'tmp' + str(deg) + '.png'), tmpl)
    
    def make_masks():
        for deg in range(360):
            tmpl = cv2.imread(os.path.join(TMPL_DIR, 'tmp' + str(deg) + '.png'), 0)
            ret2, mask = cv2.threshold(tmpl, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
            cv2.imwrite(os.path.join(TMPL_DIR, 'mask' + str(deg) + '.png'), mask)
    
    def match(img_name):
        img_rgb = cv2.imread(os.path.join(STRIPPED_DIR, img_name))
        img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
    
        for deg in range(0, 360, MATCH_RES):
            tmpl = cv2.imread(os.path.join(TMPL_DIR, 'tmp' + str(deg) + '.png'), 0)
            mask = cv2.imread(os.path.join(TMPL_DIR, 'mask' + str(deg) + '.png'), 0)
            w, h = tmpl.shape[::-1]
            res = cv2.matchTemplate(img_gray, tmpl, cv2.TM_SQDIFF, mask=mask)
    
            loc = np.where(res < MATCH_THRESH)
            for pt in zip(*loc[::-1]):
                cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)
            cv2.imwrite('res.png',img_rgb)
    

    关于带有 mask 的灰度图像上的Python openCV matchTemplate,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44690002/

    10-13 06:15