在大图中找到小图的位置

11

我有一张图片,比如说small.png和一张更大的图片big.png。在更大的图片中,小图片出现了2次,我想找到它的位置。

我尝试使用numpy和Image库,但是出现了错误'JpegImageFile' object has no attribute '__getitem__'。我之前用的是png格式,但也出现了相同的错误。

是否有其他模块或方法可以完成这个任务。我对任何方法都持开放态度。

目前会抛出错误的代码如下:

import numpy
from PIL import Image
here = Image.open(r"/Users/vks/Desktop/heren.jpg")
big = Image.open(r"/Users/vks/Desktop/Settingsn.jpg")
print here,big
herear = numpy.asarray(here)
bigar = numpy.asarray(big)
herearx = herear.shape[0]
hereary = herear.shape[1]

bigarx = bigar.shape[0]
bigary = bigar.shape[1]

print herearx , hereary , bigarx, bigary

stopx = bigarx - herearx + 1
stopy = bigary - hereary + 1

for x in range(0, stopx):
    for y in range(0, stopy):
        x2 = x+ herearx
        y2 = y + hereary
        pic = big[y:y2, x:x2] ===> error thrown here
        test = pic = here
        if test.all():
            print x,y
else:
    print "None"

我在我的Mac上安装不了另一个cv2模块。当我执行pip install cv2时,它失败并显示未找到包。

输入图像描述

输入图像描述


1
OpenCV确实有一组用于此的函数:模板匹配(文档) - jedwards
我明白你想做什么,但我认为代码应该改成这样:pic = bigar[y:y2, x:x2]; test = (pic == herear)(注意,bighere都已经被更改为它们在numpy数组版本中的名称(这样你就可以使用索引和等式测试),并且下一行的第二个等号被替换为双等号表示等于测试而不是赋值)。 - jedwards
@Divakar,虽然问题本质上归结为查找匹配的子数组,但它似乎并不像重复的问题,特别是因为发帖者代码中的问题实际上并不仅限于这个。 - jedwards
@jedwards 嗯,对我来说,dup目标似乎正在做OP试图实现的事情。如果它不起作用,我会鼓励OP解释为什么不行。如果需要的话,那么它可以重新打开。希望这听起来合理。 - Divakar
1
@Divakar 不,我同意这将会有影响。我的观点是他的代码存在问题,超出了重复目标中所概述的问题。如果没有我的回答,仅仅参考重复目标将无法解决他的问题。我同意参考重复目标是有用的,但至于它是否是重复的,我并不同意。这就像说它是这个的重复。是一部分解决方案,但并不重复。然而,这一点已经不太重要了,因为在关闭之前,我认为它已经得到了令人满意的回答(以我个人的看法)。 - jedwards
显示剩余7条评论
2个回答

11

以下代码对我有效:

import numpy
from PIL import Image

here = Image.open(r"eye.png")
big  = Image.open(r"lenna.png")

herear = numpy.asarray(here)
bigar  = numpy.asarray(big)

hereary, herearx = herear.shape[:2]
bigary,  bigarx  = bigar.shape[:2]

stopx = bigarx - herearx + 1
stopy = bigary - hereary + 1

for x in range(0, stopx):
    for y in range(0, stopy):
        x2 = x + herearx
        y2 = y + hereary
        pic = bigar[y:y2, x:x2]
        test = (pic == herear)
        if test.all():
            print(x,y)

输出: 312 237

图形上:

enter image description here

使用的测试图片

lenna.png

Lenna

eye.png

Eye

注意:在创建较小的裁剪版本时,请使用无损图像格式(PNG是无损的,JPEG通常是有损的)。如果使用有损格式,您可能面临像素值接近但不完全相同的风险。以上代码基于您的代码将仅查找精确的像素级匹配。OpenCV模板匹配函数在这方面要灵活一些。这并不是说您不能修改您的代码以同样出色地工作,您可以。但就目前而言,此答案中的代码具有该限制。

更通用版本

这里,作为函数,它会收集所有匹配坐标并将它们作为(x,y)元组的列表返回:

import numpy as np
from PIL import Image

im_haystack = Image.open(r"lenna.png")
im_needle   = Image.open(r"eye.png")

def find_matches(haystack, needle):
    arr_h = np.asarray(haystack)
    arr_n = np.asarray(needle)

    y_h, x_h = arr_h.shape[:2]
    y_n, x_n = arr_n.shape[:2]

    xstop = x_h - x_n + 1
    ystop = y_h - y_n + 1

    matches = []
    for xmin in range(0, xstop):
        for ymin in range(0, ystop):
            xmax = xmin + x_n
            ymax = ymin + y_n

            arr_s = arr_h[ymin:ymax, xmin:xmax]     # Extract subimage
            arr_t = (arr_s == arr_n)                # Create test matrix
            if arr_t.all():                         # Only consider exact matches
                matches.append((xmin,ymin))

    return matches

print(find_matches(im_haystack, im_needle))

更新:

根据您提供的图像,您会注意到匹配方式只会匹配其中一个“here”,左上角的是你为针图片裁剪的那个,因此完全匹配。右下角的图像需要逐像素地完全匹配。使用此实现,即使在其中一个颜色通道中存在单个位差异,它也将被忽略。

事实证明,这两个“here”的差异相当大: enter image description here

测试版本:

  • Python 2.7.10,Numpy 1.11.1,PIL(Pillow)1.1.7
  • Python 3.5.0,Numpy 1.10.4,PIL(Pillow)1.1.7

1
好漂亮的图片!!!但是我得到了 AttributeError: 'bool' object has no attribute 'all' 的错误……然后在第一次比较时,test 的值为 false。然后就出现了这个错误……我正在使用 png - vks
你是否使用了我列出的完全相同的代码?你使用的是哪个版本的Python/PIL/numpy?我会尝试匹配它。 - jedwards
@vks,我已经修正了问题,现在示例代码可以处理您提供的两张图片。请注意,它只能检测到一个“here”。第二个“here”不是完全匹配的像素对像素。 - jedwards
我修正了我的代码,它运行得很好,但为什么第二个不一样呢?它看起来完全一样,不是吗? - vks
通常不匹配的原因是至少有一个像素不同。实际上,这个差异可能更多。 - jedwards
也许你可以通过检查最大差异来代替精确匹配,例如 arr_t = ((arr_s / arr_n) < 0.05)。免责声明,我没有测试过它。 - PhoneixS

3

这里是我在网上找到的使用OpenCV的代码。

import datetime

import numpy as np
from PIL import Image
import cv2

# sourceImagePath = ".\\COCImages\\ErrorPrompts\\AnyoneThere.png"
# needleImagePath = ".\\COCImages\\ErrorPrompts\\AnyoneThere_Search.png"
sourceImagePath = ".\\COCImages\\ErrorPrompts\\main.png"
needleImagePath = ".\\COCImages\\ErrorPrompts\\eye.png"


def start_findNeedle(sourceImagePath, needleImagePath):
    startTime = datetime.datetime.now()
    image = cv2.imread(sourceImagePath)
    # cv2.imshow('Rainforest', image)
    # cv2.waitKey(0)
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    template = cv2.imread(needleImagePath, 0)

    result = cv2.matchTemplate(gray, template, cv2.TM_SQDIFF)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
    print("min_val: ", min_val)
    print("min_loc: ", min_loc)

    height, width = template.shape[:2]

    top_left = max_loc
    bottom_right = (top_left[0] + width, top_left[1] + height)
    cv2.rectangle(image, top_left, bottom_right, (0, 0, 255), 5)

    # cv2.imshow('Rainforest', image)
    # cv2.waitKey(0)
    # cv2.destroyAllWindows()

    endTime = datetime.datetime.now()
    totalTimeNeeded = endTime - startTime
    print("totalTimeNeeded: ", totalTimeNeeded)

    return min_loc

start_findNeedle(sourceImagePath, needleImagePath)

这版代码比@jedwards提供的旧版运行得更快:

主图像(点击此处),眼睛图像(点击此处)

旧版本消耗了0:00:01.499806的时间。

我的版本仅消耗了0:00:00.009005的时间。

注意:会有一个小警告,内容是libpng warning: iCCP: known incorrect sRGB profile,因为该图像不是以sRGB格式呈现的。

希望这可以解决并提高时间效率。

附加:

您还可以使用以下两个图像来检查时间效率:

主图像 主图像

眼睛图像 眼睛图像

更新:

  1. cv2.TM_CCOEFF更改为cv2.TM_SQDIFF,这更准确。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接