0

im using matchtemplate to detect 67x45 sqaure on background. i think my code works fine without any problem but the problem is i have to set high threshold for it to detect successfully otherwise it would give so many false detections. so i tried changing method to cv2.TM_CCOEFF_NORMED but it didnt work well. i also tried to look for opencv document to understand how all these methods work but its really hard for me to understand since they just provide math formulas.. so my question is am i fine with my code? or is there a better way to accomplish what i want to do? (im also not sure if im using 'template' parameter in matchtemplate in the correct way)

import cv2
import numpy as np 
import win32gui, win32ui, win32con

    
def imagesearch(per):


    filename = 'target.png'

    img = cv2.imread(filename, cv2.IMREAD_GRAYSCALE)
    img1 = cv2.imread(filename)

    template = cv2.imread('./map/6745.png', cv2.IMREAD_GRAYSCALE)

    w, h = template.shape[::-1]

    meth = [cv2.TM_CCOEFF, cv2.TM_CCOEFF_NORMED, cv2.TM_CCORR, cv2.TM_CCORR_NORMED, cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]

    res = cv2.matchTemplate(img, template, meth[3], None, template)


    threshold = per 
    loc = np.where(res>=threshold)


    if loc[0].any():



        for pt in zip(*loc[::-1]):
            cv2.rectangle(img1, pt, (pt[0] + w, pt[1] + h), (0,0,255), 1) 


    cv2.imshow("dst", img1)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


imagesearch(0.99)

enter image description here

image

enter image description here

template

enter image description here

result with threshold = 0.99

enter image description here

result with threshold = 0.95

as @Christoph Rackwitz suggested,

import cv2
import numpy as np 
import win32gui, win32ui, win32con


def imagesearch():


    filename = 'target.png'

    img = cv2.imread(filename, cv2.IMREAD_GRAYSCALE)
    img1 = cv2.imread(filename)

    template = cv2.imread('./map/6745.png', cv2.IMREAD_GRAYSCALE)

    w, h = template.shape[::-1]

    meth = [cv2.TM_CCOEFF, cv2.TM_CCOEFF_NORMED, cv2.TM_CCORR, cv2.TM_CCORR_NORMED, cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]

    method = meth[5]
    res = cv2.matchTemplate(img, template, meth[5], None, template)

    if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:

        min_val,max_val,min_loc, max_loc = cv2.minMaxLoc(res)
        top_left = min_loc
         
        bottom_right = (top_left[0]+w,top_left[1]+h)
        cv2.rectangle(img1,top_left,bottom_right,255,1)

    cv2.imshow("dst", img1)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


imagesearch()

this one doesnt work either.

enter image description here

wookidookik123
  • 79
  • 2
  • 10
  • look up "non-maximum suppression". and use `TM_SQDIFF*`. the other methods are **useless** in your situation. – Christoph Rackwitz Oct 23 '22 at 10:35
  • @Christoph Rackwitz i tried TM_SQDIFF but its not working as i expected. can you check the edited post – wookidookik123 Oct 23 '22 at 11:54
  • Check out: https://stackoverflow.com/questions/61166180/detect-rectangles-in-opencv-4-2-0-using-python-3-7#61578273 ( Detect rectangles in OpenCV (4.2.0) using Python (3.7) ) and consider to filter the source image by the specific color of the rectangle to detect. – Claudio Oct 23 '22 at 12:09
  • @Claudio the rectangle doesnt have specific color. it gets changed every few mins. – wookidookik123 Oct 23 '22 at 12:17
  • And what if the rectangle would have the same/similar color as the background? What exactly assures that the rectangle will be visible as such? – Claudio Oct 23 '22 at 12:45
  • https://stackoverflow.com/questions/74167407/python-opencv-how-do-i-matchtemplate-for-same-shapes-with-different-colors-and-b Do not use TM_COEFF_NORMED also just search for the best match – fmw42 Oct 23 '22 at 16:49
  • 1
    Please do some research by searching Google and this forum for similar posts before asking questions. – fmw42 Oct 23 '22 at 18:43
  • 1
    You asked this question before at https://stackoverflow.com/questions/74167407/python-opencv-how-do-i-matchtemplate-for-same-shapes-with-different-colors-and-b. Why are you repeating it? I answered you with code that works at this link. What did you not understand about my comments and answer? In your question above, you cannot use your template for the mask. The template to use in matchTemplate needs to be the BGR channels from your transparent template and the mask needs to be the alpha channel from the transparent template. – fmw42 Oct 23 '22 at 18:49
  • @fmw42 thank you for the previous answer to my question but your method didn't work well. it failed to search with different color and i always google and look for documents and do everything i could before i post here. the thing is none of the articles i found was not really helpful. – wookidookik123 Oct 23 '22 at 18:53
  • and also the solution i got from the previous question was not working for this problem even though i expeced it to work the same. i have zero intent to spamming and the only reason i posted a similiar question is because i put so much effort and time in it but i couldt find a way to accomplish what i want – wookidookik123 Oct 23 '22 at 19:03
  • Please explain what you mean by not matching to a different color. What are you trying to do. Do you mean a template of one color is not matching to some similar figure of a different color? That is not how template matching works. If not grayscale, it matches to the existing color only. There is no point in trying to match to every method. The only good ones are TM_SQDIFF or in my opinion TM_CORR_NORMED. I think all the COEFF ones are useless. – fmw42 Oct 23 '22 at 19:13
  • Filtering on threshold is also useless as you have to tune it for every image. You should just find the best match, unless you are looking for multiple matches in different locations in the same image. – fmw42 Oct 23 '22 at 19:16
  • allow me to throw a little something into the pot that discusses how to get TM_SQDIFF working. https://stackoverflow.com/questions/73177065/deno-template-matching-using-opencv-gives-no-results/73178549#73178549 – Christoph Rackwitz Oct 23 '22 at 19:20
  • @fmw42 im trying to detect two types of squares in the image. first one is 67x45 sized square and the other one is 2x2 sized squares. the first type of sqaure (67x45) may have different size sometimes (83x57) but the second type of squares are always 2x2 and they both have randomized colors and location. and yes i know there is no point in using every method and thats not what im trying to do. i just dont know if the one im using to detect those is the correct way since i have zero understanding of how all the matchtemplate methods work. – wookidookik123 Oct 23 '22 at 19:50
  • and for threshold, i just used it to see how many false detections it would have. i know its not needed for detecting one sqaure. – wookidookik123 Oct 23 '22 at 19:53
  • 1
    Here are two other ways to do non-maxima suppression: https://pyimagesearch.com/2014/11/17/non-maximum-suppression-object-detection-python/ and https://stackoverflow.com/questions/67368951/opencv-matchtemplate-and-np-where-keep-only-unique-values/67374288#67374288 especially for getting multiple match locations in one image. – fmw42 Oct 23 '22 at 19:54
  • If you are trying to match multiple size rectangles and also squares, I do not think you can use the same template unless you resize it. But that would cause problems with a thin line around the border of your black. And if the colors are different from your template, then you will need to match in grayscale and adjust the gray of the template to match the gray in your image. So I do not think what you are trying to do makes much sense to me. Do I not understand? If I do not, then please explain better and show better images. – fmw42 Oct 23 '22 at 20:14
  • Also if the rectangle in the image is not enclosing pure black, then you should be using an outline mask to avoid mismatch with the interior of your rectangle as I showed in my answer to your other question. – fmw42 Oct 23 '22 at 20:16
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/249002/discussion-between-fmw42-and-wookidookik123). – fmw42 Oct 23 '22 at 20:16

1 Answers1

1

I am using matchtemplate to detect 67x45 square on background.

In the special case of a bounding-box-like rectangle it makes sense to detect it using own method working directly on the numpy image array instead of using OpenCV matching functions with a template image and coping with different matching algorithms and thresholds.

The code below detects any number of rectangles with one-pixel wide sides of the shape given by the template image. It works for any rectangle line color so it doesn't matter if it is a white rectangle on dark background or black rectangle on bright background or if the grey value of the rectangle equals that of the background because it uses full color information.

To simplify the search conditions the BGR-tuples of the image are converted to single integer values representing RGB-colors so the image array for the search algorithm is a pure 2D one (like it is when using grayscale).

To make sure that rectangles within a single-colored background are not detected one inner pixel in the left topmost corner of a rectangle is checked and has to have another color as the rectangle. This condition could lead to a not detected rectangle and require then checking more than one pixel, but extending the check to more than one pixel can be easily implemented into the sequence of checked conditions if it turns out it will be necessary. And if the background image does not have large areas of single color of the size of the rectangle the pixel check can be entirely removed from the sequence of checked conditions.

The Python loops in the provided code can maybe be replaced by a vectorized numpy approach, so comments on how to accomplish that are welcome.

import cv2
import numpy as np 

img_filename = "openCV_squareOnBackground.png"
ibgr = cv2.imread(img_filename, cv2.IMREAD_UNCHANGED)
assert ibgr.shape[2] == 3
sizeY, sizeX, _ = ibgr.shape 
# Convert uint8 BGR tuples to uint32 value: 
iint32 = np.dstack((ibgr, np.zeros(ibgr.shape[:2], 'uint8'))).view('uint32').squeeze(-1)

img_filename = "openCV_squareOnBackground_template.png"
grey_t = cv2.imread(img_filename, cv2.IMREAD_GRAYSCALE)
t_sizeY, t_sizeX = grey_t.shape

rectAt = []
for x in range(sizeX - t_sizeX):
    for y in range(sizeY - t_sizeY):
        if \
           (iint32[y,x] == iint32[y+1:y+t_sizeY  ,x              ]).all() and \
           (iint32[y,x] == iint32[y              ,x+1:x+t_sizeX  ]).all() and \
           (iint32[y,x] == iint32[y+1:y+t_sizeY  ,x    +t_sizeX-1]).all() and \
           (iint32[y,x] == iint32[y    +t_sizeY-1,x+1:x+t_sizeX  ]).all() and \
           (iint32[y,x] != iint32[y+1,x+1]) and \
                       True:
            rectAt.append((x,y))

print(rectAt)

for x, y in rectAt:
    cv2.rectangle(ibgr, (x-1,y-1), (x+t_sizeX, y+t_sizeY), (0,255,255), 1) 

cv2.imshow("ibgr", ibgr)
cv2.waitKey(0)
cv2.destroyAllWindows()

The code finds in the provided image exactly one rectangle:

result

at [(89, 13)] .

Claudio
  • 7,474
  • 3
  • 18
  • 48