目录一、模板匹配是什么二、基本用法三、匹配方法的选择四、多目标检测五、模板匹配的局限1. 不支持旋转和缩放2. 对光照变化敏感3. 速度慢六、实战商品检测七、模板匹配 vs 特征点匹配八、常见问题九、小结一、模板匹配是什么有时候你需要在一张大图里找某个特定的小图。比如在一堆商品里找某个特定商标或者在卫星图里找某个建筑物。这种场景用模板匹配Template Matching就挺合适。模板匹配的原理很简单把小图模板在大图上滑动逐个位置比较相似度最后找出最相似的位置。OpenCV 用 cv2.matchTemplate 实现这个功能。二、基本用法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]阈值好设2. 对亮度变化有一定鲁棒性3. 1 表示完全匹配-1 表示完全不匹配0 表示不相关TM_SQDIFF_NORMED也行只是它是越小越匹配用起来不如前者直观。四、多目标检测上面的代码只能找到一个最匹配的位置。如果大图里有多个相同的目标得换个思路。方法是设定一个阈值把所有超过阈值的位置都找出来。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去掉重复的。def nms(boxes, scores, thresh0.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, threshold0.8, nms_thresh0.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 没直接提供得自己实现换用特征点匹配六、实战商品检测一个实际场景在货架照片里检测特定商品。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匹配分数很低找不到目标1. 检查模板和图像是否对齐没有旋转缩放2. 检查光照是否一致3. 降低阈值试试4. 换用其他匹配方法比如 TM_SQDIFF_NORMEDQ同一个目标被检测多次用 NMS 去重或者提高阈值。Q匹配太慢1. 缩小图像和模板2. 用 ROI 限制搜索区域3. 如果目标有旋转缩放换特征点匹配Q能处理旋转吗matchTemplate本身不支持。要么用特征点匹配要么做多尺度多角度的模板匹配把模板旋转缩放到不同尺寸分别匹配但会很慢。九、小结模板匹配是个简单直接的方法适合目标外观固定的场景。核心函数就一个 cv2.matchTemplate关键是选对匹配方法推荐 TM_CCOEFF_NORMED和做好多目标检测的 NMS。记住它的局限不支持旋转缩放对光照敏感速度慢。如果这些是问题换特征点匹配。