我有一个项目,我想在图像中找到一堆箭头,如下所示: 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)
我认为有些事情可能是错误的,但不确定如何解决:
任何帮助是极大的赞赏。
更新
我修复了代码中的一个小错误;在matchTemplate()的参数中必须使用mask = mask。这与使用255的掩码值相结合产生了不同。但是,现在我收到大量的误报,如下所示:
http://ibb.co/esfTnk 请注意,假阳性比真实阳性的相关性更高。
关于如何修复口罩以解决此问题的任何指示?现在,我只是在对模板进行黑白转换。
最佳答案
您已经找到了第一个问题,但是我将在这些问题上进行扩展:
对于二进制掩码,它应该是uint8
类型,其中值只是零或非零。具有零的位置将被忽略,并且如果它们不为零,则将其包括在掩码中。您可以传递float32
代替作为 mask ,在这种情况下,它可以使像素加权。因此将忽略值0,包含1和包含0.5,但仅赋予其权重是另一个像素的一半。请注意,TM_SQDIFF
和TM_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
您可以看到基本上模板的每个方向都紧密匹配。
无论如何,似乎将其设置为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)
关于带有 mask 的灰度图像上的Python openCV matchTemplate,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44690002/