Hi! I'm trying to add a rectangle with a translucent fill and a solid stroke over template matches in OpenCV with Python. The material I'm working with is from a game called Sun Haven by Pixel Sprout Studios, found on Steam.
Operating System: Microsoft Windows 11
Python version: 3.10.4
OpenCV version: 4.7.0
Currently, I have a script that will do the trick, but I don't like the way it's implemented as I currently have to loop through template results twice:
import cv2 as cv
import numpy as np
# Load the haystack and needle images in OpenCV.
haystack_image = cv.imread('sun_haven_14.jpg', cv.IMREAD_UNCHANGED)
needle_image = cv.imread('sun_haven_beecube.jpg', cv.IMREAD_UNCHANGED)
# Haystack image with drawings on.
scribble_image = haystack_image.copy()
# Grab the dimensions of the needle image.
needle_width = needle_image.shape[1]
needle_height = needle_image.shape[0]
# Match the needle image against the the haystack image.
result = cv.matchTemplate(haystack_image, needle_image, cv.TM_CCOEFF_NORMED)
# Define a standard to which a result is assumed positive.
threshold = 0.5
# Filter out image locations where OpenCV falls below the confidence threshold.
locations = np.where(result >= threshold)
# Convert the locations from arrays to X/Y-coordinate tuples.
locations = list(zip(*locations[::-1]))
# Debug-print the locations to the console.
#print(locations)
rectangles = []
# For all the locations found...
for location in locations:
# Define a rectangle to draw around the location.
rectangle = [int(location[0]), int(location[1]), needle_width, needle_height]
# Append the rectangle to the rectangle collection.
rectangles.append(rectangle)
# Group duplicate rectangles from overlapping results.
rectangles, weights = cv.groupRectangles(rectangles, 1, 1)
# If we have any rectangles at all...
if len(rectangles):
print('Found needle(s)!')
for rectangle in rectangles:
# Determine the location position.
top_left = (rectangle[0], rectangle[1])
bottom_right = (rectangle[0] + rectangle[2], rectangle[1] + rectangle[3])
# Draw a transparent, filled rectangle over the detected needle image.
cv.rectangle(scribble_image, top_left, bottom_right, color = (0, 255, 0), thickness = -1)
result_image = cv.addWeighted(scribble_image, 0.25, haystack_image, 1 - 0.25, 0)
for rectangle in rectangles:
# Determine the location position.
top_left = (rectangle[0], rectangle[1])
bottom_right = (rectangle[0] + rectangle[2], rectangle[1] + rectangle[3])
# Draw a solid line around the detected needle image.
cv.rectangle(result_image, top_left, bottom_right, color = (0, 255, 0), thickness = 2, lineType = cv.LINE_4)
# Display the image in a window.
cv.imshow('Result', result_image)
else:
print('No needle found...')
# Pause the script.
cv.waitKey()
# Destroy all the OpenCV 2 windows.
cv.destroyAllWindows()
I'd prefer to combine the two "for rectangle in rectangles"-loops into a single loop, but if I do, I'm only getting the fill and not the stroke around the results.
As you can see, this detects and highlights the four beecubes from the original image, but I'd rather merge the two loops to not repeat the rectangle iteration and for performance.
I've tried merging the two loops into this:
for rectangle in rectangles:
# Determine the location position.
top_left = (rectangle[0], rectangle[1])
bottom_right = (rectangle[0] + rectangle[2], rectangle[1] + rectangle[3])
# Draw a transparent, filled rectangle over the detected needle image.
cv.rectangle(scribble_image, top_left, bottom_right, color = (0, 255, 0), thickness = -1)
# Draw a solid line around the detected needle image.
cv.rectangle(scribble_image, top_left, bottom_right, color = (0, 255, 0), thickness = 2, lineType = cv.LINE_4)
result_image = cv.addWeighted(scribble_image, 0.25, haystack_image, 1 - 0.25, 0)
# Display the image in a window.
cv.imshow('Result', result_image)
However, this does not yield the results I'm looking for, as it will only draw the transparent fill, not the stroke:
So am I stuck with two almost identical for-loops, or is there a way to do this in a single loop? I'd really like to not repeat something that only needs to be done once, but if I have to, I have to.
Looking forward to feedback, cheers!
Problem solved
Thanks to feedback from @Grismar, I now avoid drawing the solid-line rectangles on the same layer as the transparent fill rectangles, but instead draw directly on the haystack image:
for rectangle in rectangles:
# Determine the location position.
top_left = (rectangle[0], rectangle[1])
bottom_right = (rectangle[0] + rectangle[2], rectangle[1] + rectangle[3])
# Draw a transparent, filled rectangle over the detected needle image.
cv.rectangle(scribble_image, top_left, bottom_right, color = (0, 255, 0), thickness = -1)
# Draw a solid line around the detected needle image.
cv.rectangle(haystack_image, top_left, bottom_right, color = (0, 255, 0), thickness = 2, lineType = cv.LINE_4)
result_image = cv.addWeighted(scribble_image, 0.25, haystack_image, 1 - 0.25, 0)
# Display the image in a window.
cv.imshow('Result', result_image)