OpenCV Template Matching for Object Counting
SkillMediaCount occurrences of a template object in an image using OpenCV template matching with non-maximum suppression.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the OpenCV Template Matching for Object Counting skill
What this skill tells your AI
The instructions your AI receives, as published by cxcscmu/skilllearnbench in skills/b1-one-shot-claude-opus-4-6/video-object-counting/opencv-template-matching/SKILL.md and read by ahel’s review.
Overview
Use cv2.matchTemplate to find all occurrences of a small template image within a larger scene image. Apply thresholding and non-maximum suppression to count distinct objects.
Code
import cv2
import numpy as np
def count_objects(scene_path, template_path, threshold=0.8):
"""Count occurrences of template in scene using template matching."""
scene = cv2.imread(scene_path, cv2.IMREAD_GRAYSCALE)
template = cv2.imread(template_path, cv2.IMREAD_GRAYSCALE)
if scene is None or template is None:
return 0
th, tw = template.shape[:2]
# Multi-scale matching can help if objects vary in size
result = cv2.matchTemplate(scene, template, cv2.TM_CCOEFF_NORMED)
# Find locations above threshold
locations = np.where(result >= threshold)
points = list(zip(*locations[::-1])) # (x, y) pairs
if len(points) == 0:
return 0
# Non-maximum suppression to avoid double counting
boxes = [(x, y, x + tw, y + th) for x, y in points]
scores = [result[y, x] for x, y in points]
indices = cv2.dnn.NMSBoxes(
[(x, y, tw, th) for x, y, _, _ in boxes],
scores,
threshold,
0.3 # NMS IoU threshold
)
return len(indices)
Multi-scale variant
If templates may appear at different sizes, iterate over scale factors:
def count_objects_multiscale(scene_path, template_path, threshold=0.8, scales=None):
scene = cv2.imread(scene_path, cv2.IMREAD_GRAYSCALE)
template = cv2.imread(template_path, cv2.IMREAD_GRAYSCALE)
if scales is None:
scales = [1.0]
all_boxes = []
all_scores = []
th, tw = template.shape[:2]
for scale in scales:
if scale != 1.0:
new_w = int(tw * scale)
new_h = int(th * scale)
if new_w < 5 or new_h < 5:
continue
tmpl = cv2.resize(template, (new_w, new_h))
else:
tmpl = template
new_w, new_h = tw, th
if tmpl.shape[0] > scene.shape[0] or tmpl.shape[1] > scene.shape[1]:
continue
result = cv2.matchTemplate(scene, tmpl, cv2.TM_CCOEFF_NORMED)
locations = np.where(result >= threshold)
points = list(zip(*locations[::-1]))
for x, y in points:
all_boxes.append((x, y, new_w, new_h))
all_scores.append(float(result[y, x]))
if not all_boxes:
return 0
indices = cv2.dnn.NMSBoxes(all_boxes, all_scores, threshold, 0.3)
return len(indices)
Threshold tuning
0.8is a good default for pixel-perfect matches- Lower to
0.6-0.7for partial matches or slight variations - Use
TM_CCOEFF_NORMEDfor best results with varying brightness
Signals
- GitHub stars
- 83
- Forks
- 5
- Last commit
- Jul 2026
Advanced
- Catalog kind
- skill
- Gateway key
opencv-template-matching- Source
- github.com/cxcscmu/skilllearnbench