11

So i have an image lets say small.png and a bigger image big.png .The small image occurs 2 times in the bigger image and i wish to find its location.

I tried using numpy and Image but got error

'JpegImageFile' object has no attribute '__getitem__'.I had png format earlier and it gave the same error.

Is there any other module or way to get this done.I am open to any .

The code as of now which throws error is

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"

There was another cv2 module but it just doesnt get installed on my mac. pip install cv2 fails saying on package found.

enter image description here

enter image description here

vks
  • 67,027
  • 10
  • 91
  • 124
  • 1
    OpenCV indeed has a set of functions for this: [Template Matching (docs)](http://docs.opencv.org/2.4/doc/tutorials/imgproc/histograms/template_matching/template_matching.html) – jedwards Jul 20 '16 at 06:45
  • It will give coordinates. I don't own a mac unfortunately. On windows, I downloaded a precompiled binary and on linux I built it and the bindings (much more annoying). [This site](http://www.pyimagesearch.com/2015/06/15/install-opencv-3-0-and-python-2-7-on-osx/) may help? In any case, on the line you indicate throwing an error, should you be using `bigar` instead of `big`? – jedwards Jul 20 '16 at 06:49
  • I see what you're trying to do, but I think the lines should read something like: `pic = bigar[y:y2, x:x2]; test = (pic == herear)` (note `big` and `here` were both changed to their numpy array versions (so you can use your indexing and equivalence testing) and the second equal sign on the next line was replaced with a double equal sign for equivalence testing, not assignment) – jedwards Jul 20 '16 at 06:55
  • @Divakar, while the question essentially boils down to finding a matching subarray, it's hardly seems like a duplicate, especially because the issue in the poster's code wasn't actually restricted to just this. – jedwards Jul 20 '16 at 07:51
  • @jedwards Well, to me it seems the dup target seems to be doing what OP is trying to achieve. If it doesn't work, I will encourage OP explain why it won't. It could be re-opened then if needed. Hope that sounds reasonable. – Divakar Jul 20 '16 at 07:57
  • 1
    @Divakar No, I agree it will. My point was that there were issues in his code that exceeded just what was outlined in the dupe target. Without my answer, just referencing the dupe target wouldn't have solved his problems. I agree referencing it would be useful, but as far as it being a duplicate, I disagree. It'd be like saying it's a duplicate of [this](http://stackoverflow.com/a/384926/736937). It's part of the solution, yes, but not a duplicate. The point is somewhat moot, however, as I think it was satisfactorily (IMHO) answered before being closed. – jedwards Jul 20 '16 at 08:00
  • @Divakar this is no way duplicate of that....its way different. – vks Jul 20 '16 at 08:24
  • @vks No reasons mentioned as how to this is different, but I am assuming you have good enough reasons, maybe you want to stick to a loopy approach as also listed in the accepted answer. So, though not fully convinced, I am reopening it. – Divakar Jul 20 '16 at 12:18
  • @Divakar can you post the link to ur answer..... I will revisit it – vks Jul 20 '16 at 12:19
  • The question itself : http://stackoverflow.com/questions/32531377/how-can-i-check-if-one-two-dimensional-numpy-array-contains-a-specific-pattern-o There are other helpful answers too. – Divakar Jul 20 '16 at 12:20
  • I am getting ``AttributeError: 'bool' object has no attribute 'all'`` – ishandutta2007 Nov 05 '20 at 08:08
  • @ishandutta2007 check the answer – vks Nov 05 '20 at 08:11

2 Answers2

11

The following code works for me:

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)

Output: 312 237

Graphically:

enter image description here

Test Images Used

lenna.png

Lenna

eye.png

Eye

Note: It's important that you use a lossless image format when you create the smaller, cropped version (PNG is lossless, JPEG is most usually lossy). If you use a lossy format, you risk the pixel values being close, but not identical. The above code based off yours will only find exact pixel-by-pixel matches. The OpenCV template matching functions are a bit more flexible in this regard. That is not to say you couldn't modify your code to work just as well, you could. But as it stands, the code in this answer has that limitation.

More general version

Here, as a function, this gathers all matching coordinates and returns them as a list of (x,y) tuples:

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

Update:

Given the images you provided, you'll notice that the way the matching is set up, it will only match one of the two here's. The top-left one is one the one you cropped for the needle image, so it matches exactly. The bottom-right image would need to match exactly pixel-for-pixel. With this implementation, even a single bit difference in one of the color channels would cause it to be ignored.

As it turns out, the two here's vary quite a bit more: enter image description here

Tested Versions:

  • 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
Community
  • 1
  • 1
jedwards
  • 29,432
  • 3
  • 65
  • 92
  • 1
    Nice Image!!!! but i get `AttributeError: 'bool' object has no attribute 'all'`.... and then on first comparison i get `false` as value of `test`.then this error comes...i am using `png` – vks Jul 20 '16 at 07:30
  • Are you using the exact same code as I listed? What version of python / PIL / numpy are you using? I'll try to match it. – jedwards Jul 20 '16 at 07:36
  • @vks, I corrected the issue and the example code now works for the two images you provided. Do note, however, that it will only detect ***one*** of the "here"s. The second here is not an exact pixel-for-pixel match. – jedwards Jul 20 '16 at 09:22
  • I corrected my code and it works fine but why is the second not same?it looks exactly same doesnt it? – vks Jul 20 '16 at 09:23
  • As a rule of why it wouldn't match, it's because at least one of the pixels differs. In fact, [it's many more](http://imgur.com/a/p4yAG). – jedwards Jul 20 '16 at 09:36
  • Maybe instead of an exact match `arr_t = (arr_s == arr_n)`, you can check a maximum difference `arr_t = ((arr_s / arr_n) < 0.05)`. Disclaimer, I haven't tested it. – PhoneixS Apr 18 '22 at 16:24
2

Here is the code using OpenCV that I research and found online.

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)

It runs faster than the previous code provide by @jedwards: Main Image (click here), Eye Image (click here)

Old version consumes 0:00:01.499806

My version consumes 0:00:00.009005

Note: there will a little warning shown which is libpng warning: iCCP: known incorrect sRGB profile because the image is not comes with sRGB format.

Hope this can solve and improve the time efficiency.

ADD ON : You may test with these two images too to check out the time efficiency

Main Image Main Image

Eye Image Eye Image

Update:

  1. changed from cv2.TM_CCOEFF to cv2.TM_SQDIFF, which is more accurate.
Jay Jay
  • 33
  • 6