我有一个项目,我希望在图像中找到一堆箭头,如下所示: 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)
我认为有些事情可能是错的但不确定如何解决:
非常感谢任何帮助。
更新 我在我的代码中修复了一个微不足道的错误;必须在matchTemplate()的参数中使用mask = mask。这与使用255的掩码值相结合产生了差异。但是,现在我得到了大量的误报: http://ibb.co/esfTnk 请注意,误报与真实正面相关性更强。 有关如何修复我的面具以解决此问题的任何指示?现在我只是使用模板的黑白转换。
答案 0 :(得分:4)
你已经找到了第一个问题,但我会对它们进行一些扩展:
对于二进制掩码,它应该是uint8
类型,其中值只是零或非零。忽略零的位置,如果它们不为零,则包含在掩码中。您可以将float32
作为掩码传递,在这种情况下,它可以让您权衡像素;所以0的值是忽略的,1是include,而.5是include,但只给它一半的权重和另一个像素。请注意,只有TM_SQDIFF
和TM_CCORR_NORMED
支持掩码,但这很好,因为您正在使用后者。 matchTemplate
的掩码仅限单个频道。正如您所发现的那样,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
,所以大多数掩码都毫无价值,因为任何非零值(甚至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
您可以看到,模板的每个方向基本上都是匹配的。
无论如何,似乎是一个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)