使用屏蔽

时间:2017-06-22 04:11:57

标签: 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)

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

  1. mask / tmpl / img应具有的通道数。我尝试过使用彩色4声道png stackoverflow eg.的示例,但不确定它是如何转换为灰度或3声道jpeg的。
  2. 掩码数组的值。例如掩盖的像素应该是1还是255?
  3. 非常感谢任何帮助。

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

1 个答案:

答案 0 :(得分:4)

你已经找到了第一个问题,但我会对它们进行一些扩展:

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

现在谈到新问题:

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

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

Square difference image

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

无论如何,似乎是一个8的门槛:

Match locations with square differences and a threshold of 8

我在代码中修改的唯一内容是所有图片都更改为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)