Template Matching with Python 3.9.1, opencv.python 4.5.1.48 and mask (transparency)
Asked Answered
K

0

3

I had the same problem as this question since 2J, but none of the answers worked with Python 3, so today I took all day to solve the problem: python opencv cv2 matchTemplate with transparency (graphics taken from the question) I finally managed to write a cv2.matchTemplate() with mask (transparency / alpha channel) and python (3.9.1) and opencv-python (4.5.1.48).

The problem was that I passed the mask as RGBA (RGB as zeros) as in Python 2, and always found (0,0) as the location, because all values of the result were always zero. When I then tried to pass only the alpha channel, it worked as expected! Because I didn't find anything about the topic with Python 3, I write a new question, hoping to get some suggestions for improvement. I have written two functions in my code, one to traditionally find only one location (findImgLoc()). The other (findImgTresh()) to get all coordinates where the value is above the threshold. Both functions normalize (0 - 1) all methods and invert the result of SQDIFF and SQDIFF NORMED, so you get only the coordinates back, or in the 2nd case a list of coordinates, which is in my opinion more beginner friendly. For testing I have a test() function to get the output graphics and information in the cmd output. And just because I read this again and again in old answers, with the code the mask works with all matching methods, not only with TM_SQDIFF and TM_CCORR_NORMED!

Image

Source Image

Template

Template Image

Result without mask (findImgTresh() with SQDIFF_NORMED und thres=.95 -> 4 locations)

Output: [[15, 123], [15, 124], [15, 165], [15, 166]]

Result without mask

Result with mask (findImgTresh() with SQDIFF_NORMED und thres=.95 -> 2 locations)

Output: [[15, 123], [15, 165]]

Result with mask

Result without mask on image

Result without mask on image

Result with mask on image

Result with mask on image

Code

import cv2, os
import numpy as np


# Method: 0: SQDIFF | 1: SQDIFF NORMED | 2: TM CCORR | 3: TM CCORR NORMED | 4: TM COEFF | 5: TM COEFF NORMED
img_path = "/Path/to/your/file/source_image_name.png"
tem_path = "/Path/to/your/file/template_image_name.png"


def findImgLoc(image_path, template_path, mask=False, method=1):
    img = cv2.imread(img_path, cv2.IMREAD_UNCHANGED) # cv2.IMREAD_COLOR (cv2.IMREAD_UNCHANGED)
    tem = cv2.imread(tem_path, cv2.IMREAD_UNCHANGED) # cv2.IMREAD_COLOR (cv2.IMREAD_UNCHANGED)

    # Match template with and without mask
    if mask and img.shape[2] == 4:
        alpha_channel = np.array(cv2.split(tem)[3])
        result = cv2.matchTemplate(img, tem, method, mask=alpha_channel)

    else:
        result = cv2.matchTemplate(img, tem, method)

    # Nomrmalize result data to percent (0-1)
    result = cv2.normalize(result, None, 0, 1, cv2.NORM_MINMAX, -1)

    # Invert Image to work similar across all methods!
    if method == 0 or method == 1: result = (1 - result)
    _minVal, _maxVal, minLoc, maxLoc = cv2.minMaxLoc(result, None)

    matchLoc = maxLoc

    # Make the Result viewable
    # view_result = cv2.normalize(result, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
    cv2.rectangle(result, matchLoc, (matchLoc[0] + tem.shape[0], matchLoc[1] + tem.shape[1]), (255,0,0), 2)

    return matchLoc

def findImgthres(image_path, template_path, mask=False, method=1, thres=.95):
    img = cv2.imread(img_path, cv2.IMREAD_UNCHANGED) # cv2.IMREAD_COLOR (cv2.IMREAD_UNCHANGED)
    tem = cv2.imread(tem_path, cv2.IMREAD_UNCHANGED) # cv2.IMREAD_COLOR (cv2.IMREAD_UNCHANGED)

    # Match template with and without mask
    if mask and img.shape[2] == 4:
        alpha_channel = np.array(cv2.split(tem)[3])
        result = cv2.matchTemplate(img, tem, method, mask=alpha_channel)

    else:
        result = cv2.matchTemplate(img, tem, method)

    # Nomrmalize result data to percent (0-1)
    result = cv2.normalize(result, None, 0, 1, cv2.NORM_MINMAX, -1)
    
    # Invert Image to work similar across all methods!
    if method == 0 or method == 1: result = (1 - result)

    result_list = np.argwhere(result > thres)

    return result_list

def test():
    global img_path, tem_path

    def findImgthres(image_path, template_path, mask=False, method=1, thres=.95):
        img = cv2.imread(img_path, cv2.IMREAD_UNCHANGED) # cv2.IMREAD_COLOR (cv2.IMREAD_UNCHANGED)
        tem = cv2.imread(tem_path, cv2.IMREAD_UNCHANGED) # cv2.IMREAD_COLOR (cv2.IMREAD_UNCHANGED)

        # Match template with and without mask
        if mask and img.shape[2] == 4:
            alpha_channel = np.array(cv2.split(tem)[3])
            result = cv2.matchTemplate(img, tem, method, mask=alpha_channel)

        else:
            result = cv2.matchTemplate(img, tem, method)

        # Nomrmalize result data to percent (0-1)
        result = cv2.normalize(result, None, 0, 1, cv2.NORM_MINMAX, -1)
        
        # Invert Image to work similar across all methods!
        if method == 0 or method == 1: result = (1 - result)

        result_list = np.argwhere(result > thres)

        return result, result_list

    tmp_source = cv2.imread(img_path, cv2.IMREAD_UNCHANGED)
    tmp_mask_source = cv2.imread(img_path, cv2.IMREAD_UNCHANGED)
    tmp_tem = cv2.imread(tem_path, cv2.IMREAD_UNCHANGED)
    result = findImgthres(img_path, tem_path)
    mask_result = findImgthres(img_path, tem_path, mask=True)

    # Mark original Image with mask
    matchLoc = mask_result[1]
    if matchLoc is not None:
        for loc in matchLoc:
            cv2.rectangle(tmp_mask_source, tuple(loc)[::-1], (loc[1] + tmp_tem.shape[1], loc[0] + tmp_tem.shape[0]), (0,0,0), 1)

    # Mark original Image without mask
    matchLoc = result[1]
    if matchLoc is not None:
        for loc in matchLoc:
            cv2.rectangle(tmp_source, tuple(loc)[::-1], (loc[1] + tmp_tem.shape[1], loc[0] + tmp_tem.shape[0]), (0,0,0), 1)

    # Save Images
    _dir = os.path.dirname(__file__)
    cv2.imwrite(os.path.join(_dir, "source.png") , tmp_source)
    cv2.imwrite(os.path.join(_dir, "mask_source.png") , tmp_mask_source)
    cv2.imwrite(os.path.join(_dir, "result.png") , 255*result[0])
    cv2.imwrite(os.path.join(_dir, "mask_result.png") , 255*mask_result[0])

    # Output Result
    print(f"[NO MASK] Match Locs: {result[1]}")
    print(f"[MASK] Match Locs: {mask_result[1]}")


if __name__ == "__main__":
    test()

I hope I could help further and am happy about suggestions for improvement!

Kamakura answered 28/1, 2021 at 21:24 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.