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
Template
Result without mask (findImgTresh()
with SQDIFF_NORMED
und thres=.95
-> 4 locations)
Output: [[15, 123], [15, 124], [15, 165], [15, 166]]
Result with mask (findImgTresh()
with SQDIFF_NORMED
und thres=.95
-> 2 locations)
Output: [[15, 123], [15, 165]]
Result without 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!