Now that we understand the concept behind template matching, sliding a small template image across a larger source image to find the best match, let's put it into practice. This hands-on exercise will guide you through implementing simple template matching using Python and the OpenCV library. We'll load two images, perform the matching, and visualize the result.
Make sure you have your development environment set up as described in Chapter 1, including Python and OpenCV. You will also need two images:
For this example, let's assume you have a source image named main_scene.jpg
and a template image named object_template.png
. You can use your own images, but ensure the template image is considerably smaller than the source image and represents a distinct part of it.
OpenCV provides a convenient function, cv2.matchTemplate()
, that handles the core logic of sliding the template and calculating the similarity score at each location. It returns a result map, which is a grayscale image where each pixel value indicates how well the template matches the neighborhood centered at that pixel in the source image.
Let's walk through the steps:
Import Libraries: We need cv2
for OpenCV functions and numpy
for numerical operations. We might also use matplotlib.pyplot
for easier display in some environments (like Jupyter notebooks), but cv2.imshow()
is also common.
import cv2
import numpy as np
# Optional: for display in environments like Jupyter
# from matplotlib import pyplot as plt
Load Images: Load the source and template images. It's often effective to perform template matching on grayscale images, as this reduces complexity by removing color information and focuses on intensity patterns.
# Load the source image and the template image
# Use cv2.IMREAD_GRAYSCALE to load them as grayscale directly
source_img_bgr = cv2.imread('main_scene.jpg')
source_img_gray = cv2.cvtColor(source_img_bgr, cv2.COLOR_BGR2GRAY)
template_img_gray = cv2.imread('object_template.png', cv2.IMREAD_GRAYSCALE)
# Check if images loaded correctly
if source_img_bgr is None or template_img_gray is None:
print("Error loading images. Check the file paths.")
exit()
# Get the width and height of the template image
# This is needed to draw the bounding box later
w, h = template_img_gray.shape[::-1] # shape gives (height, width), so we reverse it
We load the source image in color first (source_img_bgr
) because we'll want to draw our result rectangle on the color image for better visualization. We then create a grayscale version (source_img_gray
) for the matching process. The template is loaded directly as grayscale. We also store the template's width (w
) and height (h
).
Perform Template Matching: Call cv2.matchTemplate()
. You need to provide the source image (grayscale), the template image (grayscale), and a comparison method.
# Perform template matching
# cv2.TM_CCOEFF_NORMED is often a good choice
# Other methods include: TM_SQDIFF, TM_SQDIFF_NORMED, TM_CCORR, TM_CCORR_NORMED, TM_CCOEFF
method = cv2.TM_CCOEFF_NORMED
result_map = cv2.matchTemplate(source_img_gray, template_img_gray, method)
We've chosen cv2.TM_CCOEFF_NORMED
(Normalized Correlation Coefficient). This method calculates the correlation between the template and the image patch, normalized to handle brightness variations. Values range from -1.0 to 1.0, where 1.0 represents a perfect match. Other methods exist, like cv2.TM_SQDIFF_NORMED
(Normalized Squared Difference), where the lowest value indicates the best match.
Find the Best Match Location: The result_map
contains the similarity scores. We need to find the location (pixel coordinates) of the highest score (or lowest, depending on the method). OpenCV's cv2.minMaxLoc()
function does this efficiently.
# Find the minimum and maximum values and their locations in the result map
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result_map)
# If the method is TM_SQDIFF or TM_SQDIFF_NORMED, take minimum
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
top_left = min_loc
match_value = min_val
else:
top_left = max_loc
match_value = max_val
print(f"Best match top-left coordinate: {top_left}")
print(f"Best match score: {match_value:.4f}")
cv2.minMaxLoc()
returns the minimum value, maximum value, minimum value location (as an (x, y)
tuple), and maximum value location. We select either min_loc
or max_loc
as the top_left
corner of our best match, based on the method used.
Determine Bounding Box Coordinates: The location found (top_left
) is the top-left corner of the matched area. To draw a rectangle, we need the bottom-right corner as well. We can calculate this using the template's width (w
) and height (h
).
# Calculate the bottom-right corner of the bounding box
bottom_right = (top_left[0] + w, top_left[1] + h)
Visualize the Result: Draw a rectangle on the original color source image to highlight the detected region. Then, display the image.
# Draw a rectangle on the source image around the matched region
# We draw on the color image (source_img_bgr) for better visualization
cv2.rectangle(source_img_bgr, top_left, bottom_right, (0, 255, 0), 2) # Green rectangle, thickness 2
# Display the result
cv2.imshow('Matched Result', source_img_bgr)
# Optional: Display the result map (shows intensity of matches)
# cv2.imshow('Result Map', result_map)
# Wait for a key press and then close the windows
cv2.waitKey(0)
cv2.destroyAllWindows()
We use cv2.rectangle()
specifying the image to draw on, the top-left corner, the bottom-right corner, the color (in BGR format - here, green), and the line thickness. cv2.imshow()
displays the image in a window. cv2.waitKey(0)
waits indefinitely for a key press, and cv2.destroyAllWindows()
closes the display windows.
Here is the complete Python script:
import cv2
import numpy as np
# --- Configuration ---
SOURCE_IMAGE_PATH = 'main_scene.jpg'
TEMPLATE_IMAGE_PATH = 'object_template.png'
# Choose a matching method
# Options: cv2.TM_CCOEFF, cv2.TM_CCOEFF_NORMED, cv2.TM_CCORR,
# cv2.TM_CCORR_NORMED, cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED
MATCHING_METHOD = cv2.TM_CCOEFF_NORMED
# -------------------
# 1. Load Images
print("Loading images...")
source_img_bgr = cv2.imread(SOURCE_IMAGE_PATH)
# Check if source image loaded
if source_img_bgr is None:
print(f"Error: Could not load source image at {SOURCE_IMAGE_PATH}")
exit()
# Convert source to grayscale for matching
source_img_gray = cv2.cvtColor(source_img_bgr, cv2.COLOR_BGR2GRAY)
# Load template as grayscale
template_img_gray = cv2.imread(TEMPLATE_IMAGE_PATH, cv2.IMREAD_GRAYSCALE)
# Check if template image loaded
if template_img_gray is None:
print(f"Error: Could not load template image at {TEMPLATE_IMAGE_PATH}")
exit()
# Get template dimensions (width, height)
w, h = template_img_gray.shape[::-1]
print(f"Template dimensions (W x H): {w} x {h}")
# 2. Perform Template Matching
print(f"Performing template matching using method: {MATCHING_METHOD}...")
result_map = cv2.matchTemplate(source_img_gray, template_img_gray, MATCHING_METHOD)
# 3. Find Best Match Location
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result_map)
# Determine top-left corner based on the method
if MATCHING_METHOD in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
# For SQDIFF methods, the best match is the minimum value
top_left = min_loc
match_value = min_val
print(f"Best match found at {top_left} with score (lower is better): {match_value:.4f}")
else:
# For CCOEFF/CCORR methods, the best match is the maximum value
top_left = max_loc
match_value = max_val
print(f"Best match found at {top_left} with score (higher is better): {match_value:.4f}")
# 4. Determine Bounding Box
bottom_right = (top_left[0] + w, top_left[1] + h)
# 5. Visualize Result
print("Drawing bounding box on the source image...")
# Draw rectangle on the original *color* image
cv2.rectangle(source_img_bgr, top_left, bottom_right, (0, 255, 0), 2) # Green rectangle
# Display the image with the bounding box
cv2.imshow('Matched Result', source_img_bgr)
print("Displaying result. Press any key to exit.")
# Optional: Display the result map to see match intensities
# result_map_display = cv2.normalize(result_map, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8U)
# cv2.imshow('Result Map (Match Intensity)', result_map_display)
# Wait indefinitely until a key is pressed
cv2.waitKey(0)
# Clean up: close all OpenCV windows
cv2.destroyAllWindows()
print("Windows closed. Exiting.")
When you run this script (assuming your images are found correctly), you should see a window pop up displaying your source image with a green rectangle drawn around the area that best matches your template. The console output will show the coordinates of the top-left corner of this rectangle and the matching score.
Try experimenting with this code:
TM_CCOEFF_NORMED
) or higher (for TM_SQDIFF_NORMED
) than when a good match exists.cv2.TM_SQDIFF_NORMED
, cv2.TM_CCORR_NORMED
, etc.) and see how the results change. Remember to adjust the logic for selecting min_loc
or max_loc
accordingly.This exercise demonstrates the basic application of template matching. As you experiment, you'll likely encounter situations where it struggles, especially with changes in scale, rotation, lighting, or viewpoint. This directly illustrates the limitations we discussed previously and highlights why more sophisticated techniques, often involving machine learning, are necessary for more general and robust object recognition.
© 2025 ApX Machine Learning