【问题标题】:Python openCV matchTemplate on grayscale image with masking带有遮罩的灰度图像上的Python openCV matchTemplate
【发布时间】:2017-11-25 04:16:58
【问题描述】:

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

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

如您所见,除了 2 个非常接近的箭头外,它运行良好,因此另一个箭头位于模板的黑色区域中。

我正在尝试使用掩码,但似乎 cv2 根本没有应用我的掩码,即无论掩码数组具有什么值,匹配都是相同的。已经尝试了两天,但 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)

有些事情我认为可能是错误的,但不知道如何解决:

  1. mask/tmpl/img 应具有的通道数。我尝试了一个带有彩色 4 通道 png stackoverflow eg. 的示例,但不确定它如何转换为灰度或 3 通道 jpeg。
  2. 掩码数组的值。例如屏蔽掉的像素应该是 1 还是 255?

非常感谢任何帮助。

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

【问题讨论】:

  • 尝试使用包含0 和255 强度值的单通道掩码。
  • @ZdaR 我试过了,但没用。似乎即使使用零矩阵或 255 矩阵的掩码对匹配也没有影响。
  • 原来这成功了,我犯了一个小错误。我需要在 matchTemplate() 中使用参数 mask=mask。不过还有更多的并发症。我已经更新了这个问题。谢谢

标签: python opencv opencv3.0


【解决方案1】:

您已经弄清楚了第一个问题,但我将对其进行一些扩展:

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

现在开始新一期:

这与您使用的方法以及您使用jpgs 的事实有关。看看formulas for the normed methods。在图像完全为零的情况下,您将得到错误的结果,因为您将被零除。但这不是确切的问题——因为返回 nan 和 np.nan > value 总是返回 false,所以你永远不会从 nan 值中绘制正方形。

相反,问题出在边缘情况,你会得到一个非零值的提示;并且因为您使用的是jpg 图像,所以并非所有黑色值都为 0;事实上,很多都不是。请注意,您正在按平均值潜水的公式,当您的图像窗口中有 1、2、5 等值时,平均值将非常小,因此它会放大相关值。您应该改用TM_SQDIFF(因为它是唯一允许使用掩码的其他方法)。此外,由于您使用的是jpg,因此您的大多数掩码都是毫无价值的,因为任何非零值(甚至 1)都算作包含。您应该使用 pngs 作为掩码。只要模板具有适当的掩码,无论您使用 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

您可以看到,模板的每个方向基本上都非常匹配。

不管怎样,8 的门槛似乎已经确定了:

我在您的代码中修改的唯一内容是将所有图像更改为pngs,切换到TM_SQDIFF,确保loc 查找值小于阈值而不是大于比,并使用 8 的MATCH_THRESH。至少我认为这就是我改变的全部。看看以防万一:

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)

【讨论】:

  • @TonyZ 看起来您的评论还没有完成,请随时详细说明。 png 文件是未压缩的,而 jpgs 是。对于png,矩阵中的内容与写入文件的内容完全相同; jpg 已压缩,因此值与您编写的矩阵不同。
  • 嗨!谢谢你的回答!我认为使用 TM_SQDIFF 有效,但不知道使用 png 作为掩码。我必须使用 16 的阈值和 jpeg 掩码,但 8 更好。我知道匹配每个旋转角度有点笨拙,但我需要为我的程序恢复箭头的方向。再次,谢谢你!我现在对幕后发生的事情有了更好的了解。
  • 这不是那么笨重,这是非常清晰的代码(这是我回答得如此彻底的原因的一半,所以谢谢你)并且考虑到发生了什么,它不需要很长时间运行(部分原因是您的模板足够小)。
  • @AlexanderReynolds 这是一个金粉回答 - 谢谢 - png 提示是无价的
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-09-01
  • 2020-04-10
  • 2015-12-18
  • 1970-01-01
  • 1970-01-01
  • 2012-09-23
  • 2015-05-30
相关资源
最近更新 更多