目录
[1. 不支持旋转和缩放](#1. 不支持旋转和缩放)
[2. 对光照变化敏感](#2. 对光照变化敏感)
[3. 速度慢](#3. 速度慢)
[七、模板匹配 vs 特征点匹配](#七、模板匹配 vs 特征点匹配)
一、模板匹配是什么
有时候你需要在一张大图里找某个特定的小图。比如在一堆商品里找某个特定商标,或者在卫星图里找某个建筑物。这种场景用模板匹配(Template Matching)就挺合适。
模板匹配的原理很简单:把小图(模板)在大图上滑动,逐个位置比较相似度,最后找出最相似的位置。
OpenCV 用 cv2.matchTemplate 实现这个功能。
二、基本用法
python代码实践
python
import cv2
import numpy as np
#读取大图和模板
image = cv2.imread('big_image.jpg')
template = cv2.imread('template.jpg')
#转灰度
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
#模板匹配
result = cv2.matchTemplate(gray, template_gray, cv2.TM_CCOEFF_NORMED)
#找最匹配的位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
#根据匹配方法选择最大值还是最小值
#TM_CCOEFF_NORMED 和 TM_CCORR_NORMED 用最大值
#TM_SQDIFF_NORMED 用最小值
if cv2.TM_CCOEFF_NORMED in [cv2.TM_CCOEFF_NORMED, cv2.TM_CCORR_NORMED]:
top_left = max_loc
else:
top_left = min_loc
#画框
h, w = template_gray.shape[:2]
bottom_right = (top_left[0] + w, top_left[1] + h)
cv2.rectangle(image, top_left, bottom_right, (0, 255, 0), 2)
cv2.imshow('result', image)
cv2.waitKey(0)
三、匹配方法的选择
cv2.matchTemplate有6种匹配方法,选错了结果会差很多:

实际开发中,我一般用 TM_CCOEFF_NORMED。原因:
-
归一化到 -1, 1,阈值好设
-
对亮度变化有一定鲁棒性
-
1 表示完全匹配,-1 表示完全不匹配,0 表示不相关
TM_SQDIFF_NORMED`也行,只是它是越小越匹配,用起来不如前者直观。
四、多目标检测
上面的代码只能找到一个最匹配的位置。如果大图里有多个相同的目标,得换个思路。
方法是:设定一个阈值,把所有超过阈值的位置都找出来。
python
import cv2
import numpy as np
image = cv2.imread('multiple_objects.jpg')
template = cv2.imread('template.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
#模板匹配
result = cv2.matchTemplate(gray, template_gray, cv2.TM_CCOEFF_NORMED)
#找所有超过阈值的位置
threshold = 0.8
locations = np.where(result >= threshold)
#获取模板尺寸
h, w = template_gray.shape[:2]
#所有匹配位置
for pt in zip(*locations[::-1]):
cv2.rectangle(image, pt, (pt[0] + w, pt[1] + h), (0, 255, 0), 2)
print(f"找到 {len(locations[0])} 个匹配")
cv2.imshow('result', image)
cv2.waitKey(0)
但这样有个问题:同一个目标可能被检测多次(相邻位置的相似度都很高)。得做非极大值抑制(NMS)去掉重复的。
python
def nms(boxes, scores, thresh=0.5):
"""非极大值抑制"""
x1 = boxes[:, 0]
y1 = boxes[:, 1]
x2 = boxes[:, 2]
y2 = boxes[:, 3]
areas = (x2 - x1) * (y2 - y1)
order = scores.argsort()[::-1]
keep = []
while order.size > 0:
i = order[0]
keep.append(i)
xx1 = np.maximum(x1[i], x1[order[1:]])
yy1 = np.maximum(y1[i], y1[order[1:]])
xx2 = np.minimum(x2[i], x2[order[1:]])
yy2 = np.minimum(y2[i], y2[order[1:]])
w = np.maximum(0.0, xx2 - xx1)
h = np.maximum(0.0, yy2 - yy1)
inter = w * h
iou = inter / (areas[i] + areas[order[1:]] - inter)
inds = np.where(iou <= thresh)[0]
order = order[inds + 1]
return keep
#使用NMS
def detect_multiple(image, template, threshold=0.8, nms_thresh=0.5):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
result = cv2.matchTemplate(gray, template_gray, cv2.TM_CCOEFF_NORMED)
locations = np.where(result >= threshold)
h, w = template_gray.shape[:2]
#构建边界框和分数
boxes = []
scores = []
for pt in zip(*locations[::-1]):
boxes.append([pt[0], pt[1], pt[0] + w, pt[1] + h])
scores.append(result[pt[1], pt[0]])
boxes = np.array(boxes)
scores = np.array(scores)
#NMS
keep = nms(boxes, scores, nms_thresh)
#画结果
result_img = image.copy()
for i in keep:
cv2.rectangle(result_img,
(boxes[i, 0], boxes[i, 1]),
(boxes[i, 2], boxes[i, 3]),
(0, 255, 0), 2)
return result_img, len(keep)
五、模板匹配的局限
模板匹配虽然简单直接,但有几个明显的缺点:
1. 不支持旋转和缩放
如果目标在图像里旋转了或者大小变了,模板匹配就失效了。这是最大的硬伤。
#错误示范:目标旋转了30度
#模板匹配找不到
result = cv2.matchTemplate(gray, template_gray, cv2.TM_CCOEFF_NORMED)
#result 里全是低分
如果目标可能有旋转或缩放,得用特征点匹配(SIFT、ORB 那些),或者多尺度模板匹配(把模板缩放到不同尺寸分别匹配,但很慢)。
2. 对光照变化敏感
虽然归一化方法(带 _NORMED 后缀的)对亮度变化有一定鲁棒性,但如果光照变化太大,匹配分数会下降很多。
3. 速度慢
大图配大模板,matchTemplate会很慢。因为它就是朴素的滑动窗口比较,没有优化。
如果速度是瓶颈,可以考虑:
缩小图像再匹配
用 FFT 加速(OpenCV 没直接提供,得自己实现)
换用特征点匹配
六、实战:商品检测
一个实际场景:在货架照片里检测特定商品。
python
import cv2
import numpy as np
def detect_product(shelf_image_path, product_template_path):
"""检测货架上的特定商品"""
shelf = cv2.imread(shelf_image_path)
template = cv2.imread(product_template_path)
#转灰度
shelf_gray = cv2.cvtColor(shelf, cv2.COLOR_BGR2GRAY)
template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
#模板匹配
result = cv2.matchTemplate(shelf_gray, template_gray, cv2.TM_CCOEFF_NORMED)
#多目标检测
threshold = 0.75
locations = np.where(result >= threshold)
h, w = template_gray.shape[:2]
#构建边界框
boxes = []
scores = []
for pt in zip(*locations[::-1]):
boxes.append([pt[0], pt[1], pt[0] + w, pt[1] + h])
scores.append(result[pt[1], pt[0]])
if not boxes:
print("未检测到商品")
return shelf, 0
boxes = np.array(boxes)
scores = np.array(scores)
#NMS 去重
keep = nms(boxes, scores, 0.5)
#画结果
result_img = shelf.copy()
for i in keep:
cv2.rectangle(result_img,
(boxes[i, 0], boxes[i, 1]),
(boxes[i, 2], boxes[i, 3]),
(0, 255, 0), 2)
cv2.putText(result_img, f"{scores[i]:.2f}",
(boxes[i, 0], boxes[i, 1] - 5),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
print(f"检测到 {len(keep)} 个商品")
return result_img, len(keep)
#使用
result, count = detect_product('shelf.jpg', 'product_template.jpg')
cv2.imshow('detection', result)
cv2.waitKey(0)
七、模板匹配 vs 特征点匹配
很多人会纠结:什么时候用模板匹配,什么时候用特征点匹配(SIFT、ORB)?
简单说:
模板匹配:目标外观固定,没有旋转缩放,数量多。比如检测流水线上的零件、货架上的商品
特征点匹配:目标可能有旋转、缩放、视角变化。比如检测建筑物、自然场景中的物体
模板匹配的优势是简单、快(相对特征点匹配)、能同时检测多个目标。劣势是不支持旋转缩放。
特征点匹配的优势是鲁棒性强,能处理各种变形。劣势是慢、可能误匹配、需要调参。
如果你的场景目标外观固定,模板匹配是更好的选择。如果有变形,老老实实用特征点匹配。
八、常见问题
Q:匹配分数很低,找不到目标?
检查模板和图像是否对齐(没有旋转缩放)
检查光照是否一致
降低阈值试试
换用其他匹配方法(比如 TM_SQDIFF_NORMED)
Q:同一个目标被检测多次?用 NMS 去重,或者提高阈值。
Q:匹配太慢?
缩小图像和模板
用 ROI 限制搜索区域
如果目标有旋转缩放,换特征点匹配
Q:能处理旋转吗?matchTemplate本身不支持。要么用特征点匹配,要么做多尺度多角度的模板匹配(把模板旋转缩放到不同尺寸分别匹配),但会很慢。
九、小结
模板匹配是个简单直接的方法,适合目标外观固定的场景。核心函数就一个 cv2.matchTemplate,关键是选对匹配方法(推荐 TM_CCOEFF_NORMED)和做好多目标检测的 NMS。
记住它的局限:不支持旋转缩放,对光照敏感,速度慢。如果这些是问题,换特征点匹配。