确定图像是否存在于更大的图像中,如果存在,则使用 Python 找到它

Determine if an image exists within a larger image, and if so, find it, using Python

我需要一个我正在开发的 Python 程序来拍摄小图像,确定它是否存在于大图像中,如果存在,报告它的位置。如果没有,请报告。 (在我的例子中,大图是屏幕截图,小图是可能在屏幕上或不在屏幕上的图像,在 HTML5 canvas 中。)在线查看,我发现关于 OpenCV 中的模板匹配,它确实具有出色的 Python 绑定。我尝试了以下,基于我在网上找到的非常相似的代码,也使用了 numpy:

import cv2
import numpy as np
image = cv2.imread("screenshot.png")
template = cv2.imread("button.png")
result = cv2.matchTemplate(image,template,cv2.TM_CCOEFF_NORMED)
StartButtonLocation = np.unravel_index(result.argmax(),result.shape)

这没有完成我需要它做的事情,因为它总是 returns 较大图像中的一个点;匹配最接近的点,无论它有多糟糕。我想要在较大图像中找到较小图像的精确像素匹配的东西,如果 none 存在,则引发异常,或 returns False,或类似的东西.而且,它需要相当快。有没有人知道如何做到这一点?

既然你对 OpenCV 很满意,我建议你从你已经做过的开始,得到最好的匹配。获得最佳匹配的位置后,您可以检查它是否真的是一个很好的匹配。

检查匹配是否良好应该与提取匹配图像并将其与模板进行比较一样简单。要提取图像,您可能需要使用 cv2.minMaxLoc(result) 并处理输出。提取方法似乎取决于用于比较图像的方法,并通过示例完成 here

提取图像后,您应该可以使用 numpy.allclose 或其他方法比较它们。

如果您正在寻找 exact match 大小和图像值,我会提出一个快速完美的答案。

想法是在更大的 H x W 图像中计算所需 h x w 模板 的强力搜索。蛮力方法包括查看图像上所有可能的 h x w windows 并检查模板中逐个像素的对应关系。然而,这在计算上非常昂贵,但可以加速。

im = np.atleast_3d(im)
H, W, D = im.shape[:3]
h, w = tpl.shape[:2]

通过使用智能 integral images 可以非常快速地计算出 h x w window 中从每个像素开始的总和。积分图像是求和区域 table(累积求和数组),可以用 numpy 非常快地计算为:

sat = im.cumsum(1).cumsum(0)

而且它有很好的特性,比如计算一个window中所有值的总和只需要4次算术运算:

因此,通过计算模板的总和并将其与积分图像上的h x w windows的总和进行匹配,很容易找到"possible windows"的列表,其中内部值的总和与模板中值的总和相同(快速近似)。

iA, iB, iC, iD = sat[:-h, :-w], sat[:-h, w:], sat[h:, :-w], sat[h:, w:]
lookup = iD - iB - iC + iA

以上是图像中显示的所有可能 h x w 矩形操作的 numpy 向量化(因此非常快)。

这将大大减少 windows 的可能数量(在我的一个测试中减少到 2 个)。最后一步,将检查与模板的精确匹配:

posible_match = np.where(np.logical_and.reduce([lookup[..., i] == tplsum[i] for i in range(D)]))
for y, x in zip(*posible_match):
    if np.all(im[y+1:y+h+1, x+1:x+w+1] == tpl):
        return (y+1, x+1)

注意这里yx坐标对应的是图片中的A点,也就是模板的前一行和前一列。

放在一起:

def find_image(im, tpl):
    im = np.atleast_3d(im)
    tpl = np.atleast_3d(tpl)
    H, W, D = im.shape[:3]
    h, w = tpl.shape[:2]

    # Integral image and template sum per channel
    sat = im.cumsum(1).cumsum(0)
    tplsum = np.array([tpl[:, :, i].sum() for i in range(D)])

    # Calculate lookup table for all the possible windows
    iA, iB, iC, iD = sat[:-h, :-w], sat[:-h, w:], sat[h:, :-w], sat[h:, w:] 
    lookup = iD - iB - iC + iA
    # Possible matches
    possible_match = np.where(np.logical_and.reduce([lookup[..., i] == tplsum[i] for i in range(D)]))

    # Find exact match
    for y, x in zip(*possible_match):
        if np.all(im[y+1:y+h+1, x+1:x+w+1] == tpl):
            return (y+1, x+1)

    raise Exception("Image not found")

它适用于灰度和彩色图像,并在 7ms 中运行 303x384 带有 50x50 模板的彩色图像。

一个实际例子:

>>> from skimage import data
>>> im = gray2rgb(data.coins())
>>> tpl = im[170:220, 75:130].copy()

>>> y, x = find_image(im, tpl)
>>> y, x
(170, 75)

并说明结果:

左边是原图,右边是模板。这里是完全匹配:

>>> fig, ax = plt.subplots()
>>> imshow(im)
>>> rect = Rectangle((x, y), tpl.shape[1], tpl.shape[0], edgecolor='r', facecolor='none')
>>> ax.add_patch(rect)

最后,只是一个 possible_matches 测试的例子:

图像中两个windows的和是相同的,但是函数的最后一步过滤掉了与模板不完全匹配的那个。

这是对@Imanol Luengo 功能的改进。为了减少计算量,我们首先过滤与模板左上顶点相同的像素。然后我们只检查由这些像素引起的矩形。

def find_image(pic1, pic2): # pic1 is the original, while pic2 is the embedding

    dim1_ori = pic1.shape[0]
    dim2_ori = pic1.shape[1]

    dim1_emb = pic2.shape[0]
    dim2_emb = pic2.shape[1]

    v1_emb = pic2[0, 0]
    v2_emb = pic2[0, dim2_emb - 1]
    v3_emb = pic2[dim1_emb - 1, dim2_emb - 1]
    v4_emb = pic2[dim1_emb - 1, 0]

    mask = (pic1 == v1_emb).all(-1)
    found = 0

    if np.sum(mask) > 0: # Check if a pixel identical to v1_emb
        result = np.argwhere(mask)
        mask = (result[:, 0] <= dim1_ori - dim1_emb) & (result[:, 1] <= dim2_ori - dim2_emb)

        if np.sum(mask) > 0: # Check if the pixel induce a rectangle
            result = result[mask] + [0, dim2_emb - 1]
            mask = [(pic1[tuple(coor)] == v2_emb).all(-1) for coor in result]

            if np.sum(mask) > 0: # Check if a pixel identical to v2_emb
                result = result[mask] + [dim1_emb-1, 0]
                mask = [(pic1[tuple(coor)] == v3_emb).all(-1) for coor in result]

                if np.sum(mask) > 0: # Check if a pixel identical to v3_emb
                    result = result[mask] - [0, dim2_emb - 1]
                    mask = [(pic1[tuple(coor)] == v4_emb).all(-1) for coor in result]

                    if np.sum(mask) > 0: # Check if a pixel identical to v4_emb
                        result = result[mask]
                        result[:, 0] = result[:, 0] - (dim1_emb - 1)
                        result = np.c_[result, result[:, 0] + dim1_emb, result[:, 1] + dim2_emb]

                        for coor in result: # Check if the induced rectangle is indentical to the embedding
                            induced_rectangle = pic1[coor[0]:coor[2], coor[1]:coor[3]]
                            if np.array_equal(induced_rectangle, pic2):
                                found = 1
                                break
    if found == 0:
        return('No image found')
    else:
        return('Image found')

我尝试使用最后一个脚本来查找嵌入在目录中的图像,但这不起作用,这是我的工作:

import cv2
import numpy as np
import os
import glob

pic2 = "/home/tse/Images/pictures/20/redu.png"
path = "/home/tse/Images/pictures/20/*.png"
for pic1 in glob.glob(path):
    def find_image(pic1, pic2):
        dim1_ori = pic1.shape[0]
        dim2_ori = pic1.shape[1]
        dim1_emb = pic2.shape[0]
        dim2_emb = pic2.shape[1]

        v1_emb = pic2[0, 0]
        v2_emb = pic2[0, dim2_emb - 1]
        v3_emb = pic2[dim1_emb - 1, dim2_emb - 1]
        v4_emb = pic2[dim1_emb - 1, 0]

        mask = (pic1 == v1_emb).all(-1)
        found = 0

        if np.sum(mask) > 0: # Check if a pixel identical to v1_emb
            result = np.argwhere(mask)
            mask = (result[:, 0] <= dim1_ori - dim1_emb) & (result[:, 1] <= dim2_ori - dim2_emb)

            if np.sum(mask) > 0: # Check if the pixel induce a rectangl
                result = result[mask] + [0, dim2_emb - 1]
                mask = [(pic1[tuple(coor)] == v2_emb).all(-1) for coor in result]

                if np.sum(mask) > 0: # Check if a pixel identical to v2_emb
                    result = result[mask] + [dim1_emb-1, 0]
                    mask = [(pic1[tuple(coor)] == v3_emb).all(-1) for coor in result]

                    if np.sum(mask) > 0: # Check if a pixel identical to v3_emb
                        result = result[mask] - [0, dim2_emb - 1]
                        mask = [(pic1[tuple(coor)] == v4_emb).all(-1) for coor in result]

                        if np.sum(mask) > 0: # Check if a pixel identical to v4_emb
                            result = result[mask]
                            result[:, 0] = result[:, 0] - (dim1_emb - 1)
                            result = np.c_[result, result[:, 0] + dim1_emb, result[:, 1] + dim2_emb]

                            for coor in result: # Check if the induced rectangle is indentical to the embedding
                                induced_rectangle = pic1[coor[0]:coor[2], coor[1]:coor[3]]
                                if np.array_equal(induced_rectangle, pic2):
                                    found = 1
                                    break
        if found == 0:
            return('No image found')
            print("Not found")
        else:
            return('Image found')
            print("Found")