0

I'm working with Python and cv2 to try an build a script to find an image inside another image. This is a simplified version of the script I'm working on.

It works fine if the EXACT image used as reference is inside the other image i'm searching inside of.

import cv2


def checkimages(img, template):
    result = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
    min_val = cv2.minMaxLoc(result)[0]
    thr = 10000

    return min_val <= thr


template = cv2.imread('logo3.png')
images = ['withlogo.png','withlogo2.png', 'nologo.png']

for image in images:
    print('-------------------------------------')
    if checkimages(cv2.imread(image), template):
        print('{}: {}'.format(image, 'Logo found.'))
    else:
        print('{}: {}'.format(image, 'No Logo.'))

I'm using TM_SQDIFF of the matchTemplate function to try and get an aprox value and see how likely it is the template image is inside of the other.

As I said, it works fine if the EXACT template image is inside of the other image. But as soon as the template image looks a bit different inside of the other image, it may as well not exist as the result value of the matchTemplate function passes from a 0.0 to 304025248.0. And even more, if I try to find the template in another image that doesn't contain any traces of the template, I also get a massive number, but curiosly it's a lot lower that the previous one that does have a variation of it (104060152.0 for the nologo.png image).

Now, granted, this is the first time I work with cv2, and I may be asking to much of it to find the P logo in other images, without any preprocessing. But I don't understand those matchTemplate results. Do you guys have any recommendations of what I could add or change to make this a bit better.

I realize this won't be fullproof for all images, but I need to get at least an aprox value when the template is inside another image.

------- EDIT 1 - Aug 14 2023

I just made a test script with what @fmw42 commented.

This is the code. Sorry, it's kind of dirty and unoptimized, but what I'm trying to do, using @fmw42 advices is to mask the template and search for it in the main picture. If it's not found I shrink the main pic and check if it can be found in the smaller picture.

import cv2
import numpy as np


confidence_threshold = 0.92
subimage_found = False


def image_resize(image, width = None, height = None, inter = cv2.INTER_AREA):
    dim = None    

    if width is None and height is None:
        return image

    if width is None:
        r = height / float(h)
        dim = (int(w * r), int(height))

    else:
        r = width / float(w)
        dim = (int(width), int(h * r))

    print(f'resize dim: {dim}')
    resized = cv2.resize(image, dim, interpolation = inter)

    return resized


# read  image
img = cv2.imread('withlogo2.png')
h, w = img.shape[:2]

# read template with alpha channel
template_with_alpha = cv2.imread('logochico.png', cv2.IMREAD_UNCHANGED)
hh, ww = template_with_alpha.shape[:2]
# extract base template image and alpha channel and make alpha 3 channels
template = template_with_alpha[:,:,0:3]
alpha = template_with_alpha[:,:,2]
alpha = cv2.merge([alpha,alpha,alpha])

# do masked template matching and save correlation image
correlation = cv2.matchTemplate(img, template, cv2.TM_CCORR_NORMED, mask=alpha)

# get best match
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(correlation)
max_val_corr = '{:.6f}'.format(max_val)
print("correlation score: " + max_val_corr)
print("match location:", max_loc)
max_val_corr = float(max_val_corr)

min_size_reached = False
while not subimage_found and not min_size_reached:
    if max_val_corr > confidence_threshold:
        print("Subimage found")
        subimage_found = True
    else:
        print("Subimage not found. Resizing...")
        for i_ratio in np.arange(0.95, 0.5, -0.05):
            new_height = h*i_ratio
            print(f"Original height: {h}")
            print(f"New height: {new_height}")
            resized_image = image_resize(img, height = new_height)
            cv2.imshow('resized_image'+str(i_ratio),resized_image)
            cv2.waitKey(0)
            correlation = cv2.matchTemplate(resized_image, template, cv2.TM_CCORR_NORMED, mask=alpha)
            min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(correlation)
            max_val_corr = '{:.6f}'.format(max_val)
            max_val_corr = float(max_val_corr)

            print("--------------")
            print(f'Ratio: {i_ratio}')
            print(f"correlation score: {max_val_corr}")
            print(f"match location: {max_loc}")

            if max_val_corr > confidence_threshold:
                print("Subimage found")
                subimage_found = True
                break

        print("Reached min size, no subimage found.")
        min_size_reached = True

# draw match 
result = img.copy()

if subimage_found:
    cv2.rectangle(result, (max_loc), ( max_loc[0]+ww,  max_loc[1]+hh), (255,0,255), 1)

cv2.imshow('template',template)
cv2.imshow('alpha',alpha)
cv2.imshow('result',result)
cv2.waitKey(0)
cv2.destroyAllWindows()

This kinda works.

But not well... the picture is "successfully" found in moto.png, and "successfully" not found in horizon.png... but it isn't found in withlogo2.png either. And the correlation score for horizon.png, even though the image is not found, is extremly high, very close to be a match. for that matter, the correlation score is VERY high with almost all pictures I've used to search for the logo. Thats why the threshold is so high.

I'm guessing I need to do further preprocessing. Any further advice would be greatly appreciated.

Expected Result: the template should only be found when comparing to withlogo2.png. It should return a high correlation score there. It shouldn't be found when comparing to horizon.png.

Actual Result: the template isn't found in either withlogo2.png nor horizon.png, as the threshold is set quite high. If I lower the threshold a bit,the template is found in both. For some reason the correlation score is higher when comparing to horizon.png than comparing to withlogo2.png.

------- EDIT 3 Aug 17th

Sorry, this is the template and the mask that is created (I was having issues with the first template as it did not have a transparency channel):

base enter image description here

template enter image description here

alpha enter image description here

And these are the results (pink squares)...

enter image description here enter image description here enter image description here

As you can see the template is "found" in all 3 images, for all 3 I get a correlation ratio of more than 0.94... but it's all pretty much nonsense... not sure how to correct this.

Alain
  • 339
  • 3
  • 19
  • 1
    For the example you posted, you need to use a mask to remove the background of your template in matchTemplate(). Also note that template matching is very sensitive to rotation and scale changes between the template and your image. The score drops dramatically for off-rotation and scale changes. Create the mask image (binary) and supply it to cv2.matchTemplate(). – fmw42 Aug 10 '23 at 23:49
  • 1
    TM_SQDIFF gives zero for perfect match and the larger the value, the worse the match. You could use TM_CCORR_NORMED and get 1 for perfect match and 0 or negative for bad matches. Or TM_SQDIFF_NORMED and get 0 for perfect match and 1 for bad match. – fmw42 Aug 11 '23 at 01:17
  • By a mask image, you mean something like what they do here? https://stackoverflow.com/questions/61168140/opencv-removing-the-background-with-a-mask-image – Alain Aug 11 '23 at 15:29
  • 1
    See https://stackoverflow.com/questions/59759374/cv2-matchtemplate-finds-wrong-template-in-image/59779209#59779209 and https://stackoverflow.com/questions/71302061/how-do-i-find-an-image-on-screen-ignoring-transparent-pixels/71302306#71302306 and https://stackoverflow.com/questions/68693430/opencv2-matchtemplate-does-not-work-on-different-pictures-with-same-template/68694827#68694827 – fmw42 Aug 11 '23 at 15:56
  • Thanks! I've applied some of your advices. Please check my EDIT 1. Hopefully you can see what I'm missing or doing wrong... – Alain Aug 14 '23 at 22:36
  • Where is it going wrong? Look at each step image and see if you can tell if that step is correct. Show your template base and mask images and your input image. Be sure your mask (from the alpha channel) is white where expected and black every where else. – fmw42 Aug 15 '23 at 01:14
  • Each step looks ok... as you say, the mask is in scales of black and white, and looks fine... My problem is that my code seems to be finding the mask in pretty much every main image I pass to it. The correlation score for pretty much every image I pass to it is always very high (at least 0.8 every time, even for random images from the internet). In a lot of cases, it even gives a higher score to random images than to images that clearly have the logo in it (I posted an example). – Alain Aug 15 '23 at 15:38
  • Please show your base template and the mask and the image you want to compare. Show an example image that you think should match and one that you think should not match with your template and mask. I cannot test your code without having your images. – fmw42 Aug 15 '23 at 16:34
  • Done, added reference images, as well as expected and actual results – Alain Aug 15 '23 at 17:39
  • I do not understand. Your mask should be binary (no transparency). Are you extracting the alpha channel from the transparent template? When I take your transparent template and extract the base image, it is nearly completely white and not red with white letter. When I extract the alpha channel, it is totally white. So what is happening or what have you posted? Do you check each in the code by viewing them? Have you looked at the links I suggested where I do that and show what you should be using? – fmw42 Aug 15 '23 at 22:58
  • 1
    Note my concern for having a mask was with respect to your first image and template. You do not want the background of the template to mismatch with the texture in the image. So the red P template should have a mask that is white for the red P and black elsewhere. For images where the background of the P template is the same as the background in the image, you do not need a mask. You can also test with either TM_CCORR_NORMED, or TM_SQDIFF or TM_SQDIFF_NORMED and see if one or the other is better. For the latter, you need a recent version of OpenCV in order to use the mask. – fmw42 Aug 15 '23 at 23:45
  • 1
    The mask is used when you need to use the same template agains multiple image where you have the same pattern (letter P), but the background of the template does not match everywhere in the main image. Thus it removes everything from matching in the template except for the main pattern (letter P). Thus the background of the template does not contribute to the mis-matching and lowering the score. – fmw42 Aug 15 '23 at 23:53
  • Thanks for all the explaining. You are right, I was getting a wrong alpha. Was having problems since the original tempalte didn't seem to have a transaprency channel. Please check my EDIT3, I uploaded the actual correct images. Although I don't understand the results :S – Alain Aug 17 '23 at 20:20
  • Now your template image is messed up. It has no alpha and is a mix of red with your alpha. Your alpha image needs to be in the 4th channel of your base image. Also your alpha does not match your base. Just threshold your base so that red becomes black to get your alpha. Or show your proper template! – fmw42 Aug 17 '23 at 23:47
  • Note that a white P with a mask with match 100% to any white area that is large enough to encompass the P shape. If you have an image where the P is surrounded by red, then best to match without the mask. The mask is only for images where the background of the P is not red in the image. Also check your template after scaling by visually comparing to the shape in the image. Be sure you are scaling properly. – fmw42 Aug 17 '23 at 23:54

0 Answers0