【问题标题】:Determine if an image exists within a larger image, and if so, find it, using Python确定一个图像是否存在于更大的图像中,如果存在,则使用 Python 找到它
【发布时间】:2015-06-22 05:06:44
【问题描述】:

我需要一个我正在开发的 Python 程序,它能够拍摄一张小图像,确定它是否存在于一个更大的图像中,如果存在,报告它的位置。如果没有,请报告。 (在我的情况下,大图像将是一个屏幕截图,而小图像是一个可能在屏幕上也可能不在屏幕上的图像,在 HTML5 画布中。)在线查看,我发现了 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)

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

【问题讨论】:

  • 一个简单的问题:您能否假设您的small 图像将始终以其原始大小出现在large 图像中并与其原始值完全相同?或者您需要处理可能是interpolated 的可变大小small 图像并处理illumination 变化?我的意思是,你提到exact match,真的准确吗?
  • 您是否保证始终使用PNG 格式?我问是因为JPEGs 经历了量化和有损压缩,而表面上相同的事物在其内部表示上可能会有所不同。

标签: python opencv image-processing numpy graphics


【解决方案1】:

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

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")

【讨论】:

    【解决方案2】:

    这是对@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')
    

    【讨论】:

      【解决方案3】:

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

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

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

      通过使用智能integral images,可以非常快速地计算h x w 窗口内从每个像素开始的总和。一个积分图像是一个求和的面积表(cumulative summed array),可以用numpy快速计算为:

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

      而且它具有非常好的特性,例如仅用 4 次算术运算即可计算窗口内所有值的总和:

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

      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矢量化(因此非常快)。

      这将大大减少可能的窗口数量(在我的一项测试中减少到 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 示例:

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

      【讨论】:

      • 这正是我所需要的。它确实可以完美运行,而且速度非常快。非常感谢!
      • 但是在这种情况下,两个图像具有相同的来源。所以像素值将是相同的。如果我从不同的来源获得“tpl”中的图像怎么办?在那种情况下它也能工作吗?
      • @Neelesh 如果它来自不同的来源,问题就不同了。您正在寻找模板匹配并且存在其他解决方案(例如标准化互相关)。但是,可以稍微修改上面的代码以返回具有最小总和差异而不是精确总和的补丁。希望对您有所帮助!
      • np.logical_and(*[...]) - 您是否期望np.logical_and 将任意数量的东西与在一起? NumPy ufunc 不是这样工作的。您实际上将第三个列表元素指定为一个数组来放置输出,而不是另一个数组来 AND 在一起。 ufunc reduce 方法可能会有所帮助:np.logical_and.reduce([...])(没有 *)而不是 np.logical_and(*[...])
      • @Hat 你可以在不需要 cv2 的情况下使用填充:img = np.pad(img, ((1,0), (1,0), (0,0)), mode='constant') (sintax 可能有点偏离,我是通过电话来的,无法检查)。但是谢谢你让我知道!是一个快速而好的修复:)
      【解决方案4】:

      由于您对 OpenCV 感到满意,我建议您从已经完成的工作开始,并获得最佳匹配。一旦你有了最佳匹配的位置,你就可以检查它实际上是一个很好的匹配。

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

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

      【讨论】:

        猜你喜欢
        • 2023-03-26
        • 2017-04-24
        • 2015-07-31
        • 2012-06-09
        • 1970-01-01
        • 2019-10-20
        • 1970-01-01
        • 2016-08-30
        • 2012-03-05
        相关资源
        最近更新 更多