带有遮罩的灰度图像上的Python openCV matchTemplate

Ton*_*nyZ 5 python opencv opencv3.0

我有一个项目,我想在看起来像这样的图像中找到一堆箭头: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)
Run Code Online (Sandbox Code Playgroud)

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

  1. 掩码/ tmpl / img应具有的通道数。我已经尝试过例如彩色4通道png stackoverflow的示例,但不确定如何将其转换为灰度或3通道jpeg。
  2. 掩码数组的值。例如,被掩盖的像素应该是1还是255?

任何帮助是极大的赞赏。

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

alk*_*asm 5

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

对于二进制掩码,它应该是uint8其中值只是零或非零的类型。具有零的位置将被忽略,如果它们不为零,则将其包括在掩码中。您可以float32改用a 作为遮罩,在这种情况下,它可以使像素加权。因此将忽略值0,包含1和包含0.5,但仅赋予其权重是另一个像素的一半。请注意,TM_SQDIFF和仅支持掩码TM_CCORR_NORMED,但这很好,因为您正在使用后者。的掩码matchTemplate仅是单通道。如您所知,mask它不是位置参数,因此必须使用参数中的键来调用它mask=your_mask。所有这些在OpenCV文档的此页面中都非常明确

现在开始新的问题:

它与您使用的方法以及您使用jpgs 的事实有关。看一下范数法公式。如果图像完全为零,则将得到错误的结果,因为您将被零除。但这不是确切的问题-因为返回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
Run Code Online (Sandbox Code Playgroud)

平方差图像

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

无论如何,似乎设置了8个阈值:

匹配具有方差和阈值8的位置

我在代码中修改的唯一一件事是将png所有图像都更改为s,切换为TM_SQDIFF,确保loc查找的值小于阈值而不是大于阈值,并使用MATCH_THRESH8的a 。至少我认为这就是我所做的全部更改。看看以防万一:

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)
Run Code Online (Sandbox Code Playgroud)