模板匹配:为 minMaxLoc 创建掩码的有效方法?

Posted

技术标签:

【中文标题】模板匹配:为 minMaxLoc 创建掩码的有效方法?【英文标题】:Template Matching: efficient way to create mask for minMaxLoc? 【发布时间】:2018-05-31 00:11:37 【问题描述】:

OpenCV 中的模板匹配很棒。并且您可以将掩码传递给 cv2.minMaxLoc 以便您仅在图像的一部分中搜索(某种程度)您想要的模板。您也可以在 matchTemplate 操作中使用掩码,但这只会掩码模板。

我想找到一个模板,并且我想确保这个模板在我图像的其他区域内。

计算 minMaxLoc 的掩码似乎有点繁重。也就是说,计算一个准确蒙版感觉很重。如果你用简单的方法计算掩码,它会忽略模板的大小。

示例按顺序排列。我的输入图像如下所示。他们有点做作。我想找到糖果条,但前提是它完全在钟面的白色圆圈内。

时钟1

时钟2

模板

在clock1中,糖果条位于圆形钟面内,它是“PASS”。但在clock2 中,糖果条只部分位于脸部内部,我希望它是“失败”。这是一个简单的代码示例。我使用 cv.HoughCircles 来查找钟面。

import numpy as np
import cv2

img = cv2.imread('clock1.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

template = cv2.imread('template.png')
t_h, t_w = template.shape[0:2]  # template height and width

# find circle in gray image using Hough transform
circles = cv2.HoughCircles(gray, method = cv2.HOUGH_GRADIENT, dp = 1, 
                           minDist  = 150, param1 = 50, param2 = 70,
                           minRadius = 131, maxRadius = 200)
i = circles[0,0]
x0 = i[0]
y0 = i[1]
r  = i[2] 

# display circle on color image
cv2.circle(img,(x0, y0), r,(0,255,0),2)

# do the template match
result = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)

# finally, here is the part that gets tricky. we want to find highest
# rated match inside circle and we'd like to use minMaxLoc

# make mask by drawing circle on zero array
mask = np.zeros(result.shape, dtype = np.uint8)  # minMaxLoc will throw
                                                 # error w/o np.uint8
cv2.circle(mask, (x0, y0), r, color = 1, thickness = -1)

# call minMaxLoc
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result, mask = mask)

# draw found rectangle on img
if max_val > 0.4:  # use 0.4 as threshold for finding candy bar
    cv2.rectangle(img, max_loc, (max_loc[0]+t_w, max_loc[1]+t_h), (0,255,0), 4)

cv2.imwrite('output.jpg', img)

使用clock1输出

使用clock2输出 甚至找到糖果棒 虽然它的一部分在圈外

所以为了正确制作掩码,我使用了一堆 NumPy 操作。我制作了四个单独的蒙版(模板边界框的每个角一个),然后将它们组合在一起。我不知道 OpenCV 中有任何方便的功能可以为我做掩码。我有点担心所有阵列操作都会很昂贵。有没有更好的方法来做到这一点?

h, w = result.shape[0:2]

# make arrays that hold x,y coords 
grid = np.indices((h, w))
x = grid[1]
y = grid[0]

top_left_mask  = np.hypot(x - x0, y - y0) - r < 0
top_right_mask = np.hypot(x + t_w - x0, y - y0) - r < 0
bot_left_mask  = np.hypot(x - x0, y + t_h - y0) - r < 0
bot_right_mask = np.hypot(x + t_w - x0, y + t_h - y0) - r < 0

mask = np.logical_and.reduce((top_left_mask, top_right_mask, 
                              bot_left_mask, bot_right_mask))
mask = mask.astype(np.uint8)
cv2.imwrite('mask.png', mask*255)

这是“花式”面具的样子:

似乎是对的。由于模板形状,它不能是圆形的。如果我用这个面具运行clock2.jpg,我会得到:

它有效。没有发现糖果棒。但我希望我可以用更少的代码行来做到这一点......

编辑: 我做了一些分析。我运行了 100 个循环的“简单”方式和“准确”方式并计算每秒帧数 (fps):

简单的方法:12.7 fps 精确方式:7.8 fps

所以用 NumPy 制作面具需要付出一些代价。这些测试是在一个相对强大的工作站上完成的。在更普通的硬件上它可能会变得更丑......

【问题讨论】:

速度对你来说真的是个问题吗?数组上的按位操作并不是特别昂贵——就像大多数事情一样,数组大小是线性的。对于典型的(例如不是非常大的 HDF-5 类型)图像,这样的东西可以很容易地以 30fps 的速度实时运行。到目前为止,对您的速度最大的打击是模板匹配本身,即 O(n*m),其中 n 是图像大小,m 是模板大小。掩蔽操作只有 O(n),minMaxLoc() 也是如此。由于通过遮罩大大减小了图像大小,因此您已经切出了最大的一块拼图。 哦,等等——您的代码没有使用mask 参数进行模板匹配步骤。您对使用归一化互相关或平方差进行模板匹配而不是相关系数有什么特别的反感吗?如果您使用其中一种方法,则可以将mask 参数传递给matchTemplate(),这将比其他方法更快。它只会在掩码中查找匹配项。更多here. @Alexander Reynolds:我发现 TM_SQDIFF 和 TM_CCORR_NORMED 都不适用于我的应用程序——我没有得到足够的区分来清除错误匹配。但是你给了我另一个想法:也许将预先屏蔽的图像传递给 cv2.matchTemplate 会更便宜。 NumPy 的方法是创建一个掩码并与原始图像进行 np.multiply (逐元素乘法)。 那无济于事。它仍然会遍历整个图像。仅仅因为元素是黑色/零并不意味着“不匹配”——当然,您可能正试图找到一块黑色!不过,我仍然认为我的第一个问题很重要——您已经分析并发现这件事实际上很慢吗?因为 numpy 数组操作非常快。 【参考方案1】:

方法一:cv2.matchTemplate之前的'mask'图片

只是为了好玩,我尝试制作自己的图像蒙版,并传递给cv2.matchTemplate,看看我能达到什么样的性能。需要明确的是,这不是一个合适的蒙版——我将所有像素设置为一种颜色(黑色或白色)以忽略。这是为了解决只有 TM_SQDIFF 和 TM_CORR_NORMED 支持正确掩码的事实。

@Alexander Reynolds 在 cmets 中提出了一个很好的观点,即如果模板图像(我们试图找到的东西)有很多黑色或很多白色,则必须小心。对于很多问题,我们会先验知道模板是什么样子,我们可以指定白色背景或黑色背景。

我用cv2.multiply,好像比numpy.multiply快。 cv2.multiply 的另一个优点是它会自动将结果裁剪到 0 到 255 的范围内。

import numpy as np
import cv2
import time

img = cv2.imread('clock1.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

template = cv2.imread('target.jpg')
t_h, t_w = template.shape[0:2]  # template height and width

mask_background = 'WHITE'

start_time = time.time()

for i in range(100):  # do 100 cycles for timing
    # find circle in gray image using Hough transform
    circles = cv2.HoughCircles(gray, method = cv2.HOUGH_GRADIENT, dp = 1, 
                               minDist  = 150, param1 = 50, param2 = 70,
                               minRadius = 131, maxRadius = 200)
    i = circles[0,0]
    x0 = i[0]
    y0 = i[1]
    r  = i[2] 

    # display circle on color image
    cv2.circle(img,(x0, y0), r,(0,255,0),2)

    if mask_background == 'BLACK':  # black = 0, white = 255 on grayscale
        mask = np.zeros(img.shape, dtype = np.uint8)

    elif mask_background == 'WHITE':
        mask = 255*np.ones(img.shape, dtype = np.uint8)

    cv2.circle(mask, (x0, y0), r, color = (1,1,1), thickness = -1)
    img2 = cv2.multiply(img, mask)  # element wise multiplication
                                    # values > 255 are truncated at 255
    # do the template match
    result = cv2.matchTemplate(img2, template, cv2.TM_CCOEFF_NORMED)

    # call minMaxLoc
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)

    # draw found rectangle on img
    if max_val > 0.4:
        cv2.rectangle(img, max_loc, (max_loc[0]+t_w, max_loc[1]+t_h), (0,255,0), 4)

fps = 100/(time.time()-start_time)
print('fps ', fps)

cv2.imwrite('output.jpg', img)

分析结果:

黑色背景 12.3 fps 白色背景 12.1 fps

相对于原始问题中的 12.7 fps,使用此方法对性能的影响很小。但是,它的缺点是它仍然会找到仍然有点超出边缘的模板。根据问题的确切性质,这在许多应用程序中可能是可以接受的。

方法2:使用cv2.boxFilter为minMaxLoc创建掩码

在这种技术中,我们从一个圆形掩码开始(如在 OP 中),然后使用 cv2.boxFilter 对其进行修改。我们将anchor 从内核的默认中心更改为左上角 (0, 0)
import numpy as np
import cv2
import time

img = cv2.imread('clock1.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

template = cv2.imread('target.jpg')
t_h, t_w = template.shape[0:2]  # template height and width
print('t_h, t_w ', t_h, ' ', t_w)

start_time = time.time()

for i in range(100):
    # find circle in gray image using Hough transform
    circles = cv2.HoughCircles(gray, method = cv2.HOUGH_GRADIENT, dp = 1, 
                               minDist  = 150, param1 = 50, param2 = 70,
                               minRadius = 131, maxRadius = 200)
    i = circles[0,0]
    x0 = i[0]
    y0 = i[1]
    r  = i[2] 

    # display circle on color image
    cv2.circle(img,(x0, y0), r,(0,255,0),2)

    # do the template match
    result = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)

    # finally, here is the part that gets tricky. we want to find highest
    # rated match inside circle and we'd like to use minMaxLoc

    # start to make mask by drawing circle on zero array
    mask = np.zeros(result.shape, dtype = np.float)  
    cv2.circle(mask, (x0, y0), r, color = 1, thickness = -1)

    mask = cv2.boxFilter(mask, 
                         ddepth = -1, 
                         ksize = (t_w, t_h), 
                         anchor = (0,0),
                         normalize = True,
                         borderType = cv2.BORDER_ISOLATED)
    # mask now contains values from zero to 1. we want to make anything
    # less than 1 equal to zero
    _, mask = cv2.threshold(mask, thresh = 0.9999, 
                        maxval = 1.0, type = cv2.THRESH_BINARY)
    mask = mask.astype(np.uint8)

    # call minMaxLoc
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result, mask = mask)

    # draw found rectangle on img
    if max_val > 0.4:
        cv2.rectangle(img, max_loc, (max_loc[0]+t_w, max_loc[1]+t_h), (0,255,0), 4)

fps = 100/(time.time()-start_time)
print('fps ', fps)

cv2.imwrite('output.jpg', img)

此代码提供与 OP 相同的掩码,但速度为 11.89 fps。与 方法 1 相比,这种技术为我们提供了更高的准确度和稍高的性能损失。

【讨论】:

以上是关于模板匹配:为 minMaxLoc 创建掩码的有效方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何在模板匹配中使用 cv2.minMaxLoc()

openflow流表项中有关ip掩码的匹配的问题(控制器为ryu)

如何使用带有 alpha 掩码的模板缓冲区

匹配给定 IP 范围/掩码的 IPv4 地址?

OpenCV模板匹配

实时模板匹配 - OpenCV、C++