如何检测一个图像是否在另一个图像中?

问题描述

我试图检测一个图像是否与另一个图像 100% 匹配,然后将变量设置为 True(如果是)。但是,除了给出此代码一个特定线程之外,我所阅读的所有内容几乎没有结果。

import cv2

method = cv2.TM_SQDIFF_norMED

# Read the images from the file
small_image = cv2.imread('ran_away.png')
large_image = cv2.imread('pokemon_card.png')

result = cv2.matchTemplate(small_image,large_image,method)

# We want the minimum squared difference
mn,_,mnLoc,_ = cv2.minMaxLoc(result)

# Draw the rectangle:
# Extract the coordinates of our best match
MPx,MPy = mnLoc

# Step 2: Get the size of the template. This is the same size as the match.
trows,tcols = small_image.shape[:2]

# Step 3: Draw the rectangle on large_image
cv2.rectangle(large_image,(MPx,MPy),(MPx+tcols,MPy+trows),(0,255),2)

# display the original image with the rectangle around the match.
cv2.imshow('output',large_image)

# The image is only displayed if we call this
cv2.waitKey(0)

然而,这会打开一个输出,并做我不想做的事情。我想要做的就是检测图像是否在图像中,如果是,则将其打印到控制台。在我的特殊情况下,我试图检测这张图片

Ran_away.png

在这图片

Pokemon_card.png

如果是,则在控制台打印出宠物小精灵已经逃跑了。

解决方法

您的代码显示了基本的模板匹配。请阅读有关该主题的一些 tutorial 以及有关 cv2.matchTemplate 的文档,尤其是要了解不同的 template match modes

我只能想到以下解决方案来完成您的任务:不要使用 TM_SQDIFF_NORMED,而是使用 TM_SQDIFF,这样您就可以在 result 中获得绝对值而不是相对值:

  • 对于 TM_SQDIFF_NORMED,即使匹配不正确,最佳匹配始终是接近 0.0 的某个值。
  • 对于 TM_SQDIFF0.0 附近的某个值表示实际正确 匹配。

所以,现在,只需编写一个方法来进行模板匹配,并检测 result 的最小值是否低于 0.0 附近的某个阈值,例如 10e-6。如果是这样,打印出你想要的任何东西,如果不是,做其他事情:

import cv2


def is_template_in_image(img,templ):

    # Template matching using TM_SQDIFF: Perfect match => minimum value around 0.0
    result = cv2.matchTemplate(img,templ,cv2.TM_SQDIFF)

    # Get value of best match,i.e. the minimum value
    min_val = cv2.minMaxLoc(result)[0]

    # Set up threshold for a "sufficient" match
    thr = 10e-6

    return min_val <= thr


# Read template
template = cv2.imread('ran_away.png')

# Collect image file names
images = ['pokemon_card.png','some_other_image.png']

for image in images:
    if is_template_in_image(cv2.imread(image),template):
        print('{}: {}'.format(image,'Pokemon has ran away.'))
    else:
        print('{}: {}'.format(image,'Nothing to see here.'))

输出:

pokemon_card.png: Pokemon has ran away.
some_other_image.png: Nothing to see here.
----------------------------------------
System information
----------------------------------------
Platform:      Windows-10-10.0.19041-SP0
Python:        3.9.1
PyCharm:       2021.1.1
OpenCV:        4.5.2
----------------------------------------
,

我找到了一个使用相对较新的 NumPy 方法 sliding_window_view 的解决方案。

在给定窗口形状的数组中创建一个滑动窗口视图。

也称为滚动窗口或移动窗口,窗口在所有维度上滑动 数组并在所有窗口位置提取数组的子集。

1.20.0 版的新功能。

注意:由于兼容性问题,我已在新的虚拟环境中安装了最新的 NumPy 版本。

检查sliding_window_view如何工作的简单测试:

import numpy as np
from numpy.lib.stride_tricks import sliding_window_view

t = np.array([[ [0,0],[1,1,1]],[ [2,2,2],[3,3,3]]])

x = np.array([[ [0,1],[2,3]],[[10,10,10],[11,11,11],[12,12,12],[13,13,13]],[[20,20,20],[21,21,21],[22,22,22],[23,23,23]]])

x[1:3,1:3,:] = t  # Copy t to x - it looks like there is a problem along edges

v = sliding_window_view(x,(2,3))

print(v-t)

结果以:

[[[[[[ 0  0  0]
     [ 0  0  0]]

这意味着 t 按预期从 v 的所有“窗口”中减去。


添加以下命令用于测试 np.all

print(np.where((v == t).all(axis=(3,4,5))))

输出为:

(array([1],dtype=int64),array([1],array([0],dtype=int64))

all(axis=(3,5))True 如果沿轴 3、4 和 5 的所有元素都是 True
在上面的例子中,我们在索引 [1,1] 中找到了一个匹配项。


这是检测完美匹配的解决方案(使用 NumPy):

import cv2
import numpy as np
from numpy.lib.stride_tricks import sliding_window_view

# Read the images from the file
small_image = cv2.imread('ran_away.png')
#small_image = cv2.imread('icon.png');
large_image = cv2.imread('pokemon_card.png')

v = sliding_window_view(large_image,small_image.shape)

match_idx = np.where((v == small_image).all(axis=(3,5)))

if len(match_idx[0]) > 0:
    row = match_idx[0][0]
    col = match_idx[1][0]

    cv2.rectangle(large_image,(col,row),(col+small_image.shape[1],row+small_image.shape[1]),(0,255,0),2)

    cv2.imshow('large_image',large_image)
    cv2.waitKey()
    cv2.destroyAllWindows()

结果:
enter image description here