1.模板匹配
本质:在一副图像中寻找与模板图像最相似区域的技术。
原理:
- 模板滑动 :将模板图像以逐像素的方式,在原始图像上从左到右、从上到下滑动。
- 子窗口截取:每次滑动后,在原始图像上截取与模板尺寸相同的子窗口(ROI)。
- 相似度计算 :计算模板与当前子窗口的相似度(或差异度),得到一个数值表示匹配程度。
- 最佳匹配定位:遍历所有子窗口后,找到相似度最高(或差异度最低)的子窗口位置,即为模板在原始图像中的匹配区域。
使用前提条件:
1.目标形状固定。
2.无尺度/旋转变化。
3.光照条件稳定(通常在灰度图像上)。
1.1经典模板匹配方法
常用的6种经典的模板匹配方法,核心区别在于相似度/差异度的计算方式。
原始图像为I (尺寸 WxH),模板图像为T(尺寸w x h),滑动窗口的子图像Ix,y,以(x,y)为左上角坐标)






2.OpenCV 中的模板匹配实现
1.cv2.matchTemplate(image, templ, method)
- 参数 :
image:原始图像(灰度图或彩色图,推荐灰度图)。templ:模板图像(需与原始图像通道数一致,尺寸更小)。method:匹配方法(如cv2.TM_CCOEFF_NORMED、cv2.TM_SQDIFF_NORMED)。
- 返回值 :匹配结果矩阵
res,尺寸为(W−w+1)×(H−h+1),每个元素对应子窗口的匹配值。
2.cv2.minMaxLoc(src)
- 参数 :
src:匹配结果矩阵res。 - 返回值 :
(min_val, max_val, min_loc, max_loc),分别为最小匹配值、最大匹配值、最小值位置、最大值位置。 - 注意 :平方差类方法(TM_SQDIFF/TM_SQDIFF_NORMED)取
min_loc为最佳匹配位置,其余方法取max_loc。
实例匹配代码:
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 步骤1:读取原始图像和模板图像
# 原始图像
img = cv2.imread(r'../imgs/original_image.png')
# 模板图像(需提前截取目标区域)
templ = cv2.imread(r'../imgs/template_image.png')
# 步骤2:转换为灰度图(减少计算量,避免色彩干扰)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
templ_gray = cv2.cvtColor(templ, cv2.COLOR_BGR2GRAY)
# 获取模板的宽和高
templ_h, templ_w = templ_gray.shape[:2]
# 步骤3:执行模板匹配(使用归一化相关系数方法,抗光照效果最好)
res = cv2.matchTemplate(img_gray, templ_gray, cv2.TM_CCOEFF_NORMED)
# 步骤4:找到最佳匹配位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# 归一化相关系数方法取max_loc为最佳位置
top_left = max_loc
# 计算匹配区域的右下角坐标
bottom_right = (top_left[0] + templ_w, top_left[1] + templ_h)
# 步骤5:在原始图像上绘制匹配框(BGR格式,红色:(0,0,255))
cv2.rectangle(img, top_left, bottom_right, (0, 0, 255), 2)
# 步骤6:显示结果(Matplotlib默认RGB,需转换通道)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.figure(figsize=(10, 5))
plt.subplot(121), plt.imshow(templ, cmap='gray'), plt.title('Template')
plt.subplot(122), plt.imshow(img_rgb), plt.title('Matching Result')
plt.show()

优点
- 原理简单:无需复杂的特征提取,易理解、易实现。
- 计算速度快:基于像素级遍历,对小模板和简单场景效率高。
- 工具支持完善:OpenCV、PIL 等库均提供成熟的 API,开发成本低。
缺点
- 对变换敏感 :无法处理目标的尺度变化 (如目标变大 / 变小)、旋转 、仿射变换(如倾斜)。
- 抗光照能力有限:仅归一化相关系数方法(TM_CCOEFF_NORMED)对光照有一定鲁棒性,其余方法受亮度影响大。
- 无遮挡鲁棒性:若目标被遮挡,匹配精度会急剧下降。
- 单一模板限制:只能匹配与模板完全一致的目标,无法同时匹配多个不同形态的目标。
2.图像的几何变换
⭐经验:
图像几何变换中,图像的几何变换中的resize,平移,旋转常用于图像数据增强,仿射和透视不用来数据增强,后者情况不明显。
1.平移(Translation)
import cv2 import numpy as np import matplotlib.pyplot as plt path = r'../imgs/original_image.png' img = cv2.imread(path) if img is None: raise ValueError("图像路径错误") def show_img (img,title='img'): #用matplotlib显示图像(转换BGR→RGB) img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) plt.figure(figsize=[6,4]) plt.imshow(img_rgb) plt.title(title) plt.axis("off") plt.show() def Translate_img (img): #定义平移矩阵 dx = 50 #右50 dy = 30 #下30 #构造平移矩阵 M = np.float32([[1, 0, dx], [0, 1, dy]]) #执行平移操作 img_trans = cv2.warpAffine(img, M, (img.shape[1], img.shape[0])) show_img(img,'original_image') show_img(img_trans, title='Translate') if __name__ == '__main__': #平移操作 Translate_img(img)
2.缩放(Scaling)
#核心代码实现 def Scale(img): # 方法1:直接指定目标尺寸(宽度200,高度150) img_resize1 = cv2.resize(img, (200, 150), interpolation=cv2.INTER_LINEAR) # 方法2:按比例缩放(x轴缩放到0.5,y轴缩放到0.8) img_resize2 = cv2.resize(img, None, fx=0.5, fy=0.8, interpolation=cv2.INTER_NEAREST) show_img(img,"Original") show_img(img_resize1,"Resize to (200,150)") show_img(img_resize2,"Scale (fx=0.5, fy=0.8)",)
3.旋转(Rotation)
def Rotation(img): # 旋转中心:图像中心 center = (img.shape[1] // 2, img.shape[0] // 2) # 生成旋转矩阵:逆时针45度,缩放0.8倍 M = cv2.getRotationMatrix2D(center, 45, 0.8) # 执行旋转 img_rot = cv2.warpAffine(img, M, (img.shape[1], img.shape[0])) show_img(img,"Original") show_img(img_rot,"Rotated (45° counter-clockwise, scale=0.8)")
4.翻转(Flipping)
def flip(img): img_flip_x = cv2.flip(img, 0) # 上下翻转 img_flip_y = cv2.flip(img, 1) # 左右翻转 img_flip_xy = cv2.flip(img, -1) # 对角线翻转 show_img(img,"Original") show_img(img_flip_x,"Flip X (up-down)") show_img(img_flip_y,"Flip Y (left-right)") show_img(img_flip_xy,"Flip XY")
5.仿射变换(Affine Transformation)
原理:两个点确定一条线,三个点确定一个面,仿射就是对图片平面的操作。
6.透视变换(Perspective Transformation)
常用于倾斜文档->正角视图
def Perspective_Transformation(img): # 原图中的4个点(比如文档的四个角) src = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]]) # 变换后的目标点(矩形) dst = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]]) # 生成透视矩阵 M = cv2.getPerspectiveTransform(src, dst) # 执行透视变换(输出尺寸设为(300,300)) img_persp = cv2.warpPerspective(img, M, (300, 300)) show_img(img,"Original (Tilted)") show_img(img_persp,"Perspective Correction")
3.轮廓
边缘与轮廓本质区别:
轮廓检测:连续的点组成的一条连续的曲线,具有相同的颜色或灰度连着的边界。
边缘检测:图像梯度有可能是不连续的。
应用场景:
物体检测、形状分析、目标计数、图像分割。
轮廓检测的前提:图像预处理
基础流程:
- 读入图像并转为灰度图(减少计算量);
- 去噪(可选,如高斯模糊);
- 二值化(阈值处理)或边缘检测(如 Canny),得到黑白二值图。
1.灰度图->平滑(可选)->二值化->形态学(可选)->【平滑】->【边缘检测】->轮廓检测。 2.灰度图->平滑(可选)->二值化->形态学(可选)->【平滑】->轮廓检测。
1.轮廓检测与绘制
1.轮廓检测
cv2.findContours() #返回轮廓列表和层级信息contours, hierarchy = cv2.findContours(image, mode, method)
image:输入的二值图像(非 0 为前景,0 为背景);
mode:轮廓检索模式 (决定检测哪些轮廓):
cv2.RETR_EXTERNAL:只检测最外层轮廓(常用,排除内部孔洞);cv2.RETR_LIST:检测所有轮廓,不建立层级关系;cv2.RETR_CCOMP:检测所有轮廓,建立两层层级(外层 + 内层孔洞);cv2.RETR_TREE:检测所有轮廓,建立完整层级树(保留父子关系);
method:轮廓逼近方法 (压缩轮廓点):
cv2.CHAIN_APPROX_NONE:保存所有轮廓点(冗余);cv2.CHAIN_APPROX_SIMPLE:压缩水平 / 垂直 / 对角线方向的点(只保留端点,常用,节省内存
hierarchy是一个数组,每个轮廓对应层级信息[next, prev, child, parent]
next:同层级下一个轮廓的索引(-1 表示无);
prev:同层级上一个轮廓的索引;
child:第一个子轮廓的索引;
parent:父轮廓的索引(-1 表示无)。2.轮廓绘制
cv2.drawContours() #绘制检测到的图像轮廓cv2.drawContours(img, contours, contourIdx, color, thickness)
img:要绘制的图像(需为彩色图,否则颜色无效);contours:轮廓列表;contourIdx:要绘制的轮廓索引(-1 表示绘制所有轮廓);color:轮廓颜色(如(0,255,0)为绿色);thickness:轮廓线宽(-1 表示填充轮廓内部)。
简单案例:
import cv2 path = r'../imgs/ED58CFC52DC2AD3BC6FDE527B49EEA3A.png' def show_img(img, title="img"): cv2.imshow(title, img) cv2.waitKey(0) # 等待 1秒按任意键退出界面 cv2.destroyAllWindows() def preprocess(img): #灰度图 gary = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #高斯去噪 blur = cv2.GaussianBlur(gary, (5, 5), 0) #二值化 ret是阈值,binary是二值化图像 ret,binary = cv2.threshold(blur, 127, 255, cv2.THRESH_BINARY) # 2. 检测轮廓 contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for i , cnt in enumerate(contours): area = cv2.contourArea(cnt) x, y, w, h = cv2.boundingRect(cnt) # 得到轮廓的外接矩形 if area < 200 and w >= 12 and h >= 12: cv2.drawContours(img, contours, i, (0, 255, 0), 2) # 绿色,线宽2 #绘制外接矩形 # cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2) area_list.append(area) # w_list.append(w) # h_list.append(h) # area_list.sort(reverse=True) # w_list.sort(reverse=True) # h_list.sort(reverse=True) print(area_list) # print(w_list) # print(h_list) if __name__ == '__main__': area_list = [] # w_list = [] # h_list = [] img = cv2.imread(path) img = img.copy() show_img(img,title="binary") preprocess(img) show_img(img, title="Contours")
2.轮廓的特征分析
我们经过上图中所展现出来的效果可以看到如果使用轮廓绘制后,部分轮廓有毛刺,形状不规整,且存储起来比较占内存。如下图所示我们需要引入轮廓的部分特征来解决此问题。
cv2.drawContours(img, contours, i, (0, 255, 0), 2) # 绿色,线宽2

1.轮廓的面积和周长
#获取单个轮廓面积
area = cv2.contourArea(contours[0])
上述的综合代码中我们也通过计算轮廓的面积来筛选,符合条件的轮廓图像,我们通常可以将所有轮廓遍历出来然后进行排序,筛选出两极化异常值。

轮廓的周长:
#closed=True表示轮廓是闭合
perimeter = cv2.arcLength(contours[0], True)
2.轮廓逼近和外接矩阵
轮廓逼近的原理:用更少的点来近似表示轮廓,去掉冗余的点,保留关键的形状特性。
优点:
**1.**简化轮廓,减少计算复杂度,后续处理更快。
2.去除噪声引起的轮廓细节,保留核心形状。
3.便于形状分析和识别,比如把不规则的轮廓逼近成规则的多边形(比如矩形、三角形)。
4.减少存储和传输的数据量,因为点变少了。
实现:
contour = contours[0]
perimeter = cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, 0.02*perimeter, True)
外接矩阵的原理:通过计算原点和其对应的w,h可以获得轴对齐外接矩阵(简单但不贴合)
轴对齐外接矩形 (简单但不贴合):cv2.boundingRect(contour)
#cv2.boundingRect(contour)
#返回(x,y,w,h)(左上角坐标、宽、高)
x, y, w, h = cv2.boundingRect(contour)
cv2.rectangle(img, (x,y), (x+w,y+h), (255,0,0), 2) # 绘制蓝色矩形
效果对比:外接矩阵近似相当将轮廓逼近后的轮廓绘制线近似拉直成矩形。
使用轮廓绘制后的图像
使用外接矩阵后的图像
3.轮廓其他形状
1.最小外接矩形 (贴合轮廓,可能旋转):cv2.minAreaRect(contour)
rect = cv2.minAreaRect(contour)
box = cv2.boxPoints(rect)
box = np.int0(box) # 转为整数
cv2.drawContours(img, [box], 0, (0,0,255), 2) # 绘制红色旋转矩形

2. 最小外接圆:cv2.minEnclosingCircle(contour)
返回(center, radius)(圆心、半径)
(x,y), radius = cv2.minEnclosingCircle(contour)
center = (int(x), int(y))
radius = int(radius)
cv2.circle(img, center, radius, (255,255,0), 2) # 绘制青色圆

3.拟合椭圆:cv2.fitEllipse(contour)
ellipse = cv2.fitEllipse(contour)
cv2.ellipse(img, ellipse, (128,128,0), 2) # 绘制椭圆

3.常用轮廓的性质
1.长宽比
AspectRation = Width / Height
x,y,w,h = cv2.boundingRect(cnt)
aspect_ratio = float(w)/h
2.Extent(轮廓面积与边界矩阵面积的比)
Extent = ObjectArea / BoundingRectangleArea
area = cv2.contourArea(cnt)
x,y,w,h = cv2.boundingRect(cnt)
rect_area = w*h
extent = float(area)/rect_area
#实际运用
for i, cnt in enumerate(contours):
# 计算轮廓周长(用于设置逼近精度)
perimeter = cv2.arcLength(cnt, True)
# 轮廓逼近:epsilon设为周长的2%(可根据需求调整)
approx = cv2.approxPolyDP(cnt, 0.02 * perimeter, True)
# 基于逼近后的轮廓计算面积和外接矩形(更稳定)
area = cv2.contourArea(approx)
x, y, w, h = cv2.boundingRect(approx)
ract_area = w * h
if area < 200 and w >= 12 and h >= 12:
# cv2.drawContours(img, [approx], 0, (0, 255, 0), 2) # 绘制逼近后的轮廓
cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)
extent = float(area)/ract_area
area_list.append(area)
extent_list.append(extent)
print("extent_list:", extent_list)
return img
3.极点
本质:一个对象最上面,最下面,最左边,最右边的点。
leftmost = tuple(cnt[cnt[:,:,0].argmin()][0])
rightmost = tuple(cnt[cnt[:,:,0].argmax()][0])
topmost = tuple(cnt[cnt[:,:,1].argmin()][0])
bottommost = tuple(cnt[cnt[:,:,1].argmax()][0])

4.总结
本章节,笔者主要是通用学习关于一些简单的模板匹配知识,模式匹配在不做深度神经网络的协作下仅仅能匹配到简单的目标,而且受光线影响大,后续优化方法会进一步总结。紧接着又整理了一些关于图像的常见几何变换,需注意平移,缩放,旋转,翻转常可以用来数据增强,仿射和透视数据增强不明显,不建议使用。最后关于轮廓的检测绘制和使用,以及其使用后的效果我也做了一些整理,同时归纳了常用的轮廓性质等。



















