视觉opencv学习笔记Ⅳ

1.模板匹配

本质:在一副图像中寻找与模板图像最相似区域的技术。

原理:

  1. 模板滑动 :将模板图像以逐像素的方式,在原始图像上从左到右、从上到下滑动。
  2. 子窗口截取:每次滑动后,在原始图像上截取与模板尺寸相同的子窗口(ROI)。
  3. 相似度计算 :计算模板与当前子窗口的相似度(或差异度),得到一个数值表示匹配程度。
  4. 最佳匹配定位:遍历所有子窗口后,找到相似度最高(或差异度最低)的子窗口位置,即为模板在原始图像中的匹配区域。

使用前提条件:

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_NORMEDcv2.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()

优点

  1. 原理简单:无需复杂的特征提取,易理解、易实现。
  2. 计算速度快:基于像素级遍历,对小模板和简单场景效率高。
  3. 工具支持完善:OpenCV、PIL 等库均提供成熟的 API,开发成本低。

缺点

  1. 对变换敏感 :无法处理目标的尺度变化 (如目标变大 / 变小)、旋转仿射变换(如倾斜)。
  2. 抗光照能力有限:仅归一化相关系数方法(TM_CCOEFF_NORMED)对光照有一定鲁棒性,其余方法受亮度影响大。
  3. 无遮挡鲁棒性:若目标被遮挡,匹配精度会急剧下降。
  4. 单一模板限制:只能匹配与模板完全一致的目标,无法同时匹配多个不同形态的目标。

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.轮廓

边缘与轮廓本质区别:

轮廓检测:连续的点组成的一条连续的曲线,具有相同的颜色或灰度连着的边界。

边缘检测:图像梯度有可能是不连续的

应用场景:

物体检测、形状分析、目标计数、图像分割。

轮廓检测的前提:图像预处理

基础流程:

  1. 读入图像并转为灰度图(减少计算量);
  2. 去噪(可选,如高斯模糊);
  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.总结

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

相关推荐
断剑zou天涯43 分钟前
【算法笔记】KMP算法
java·笔记·算法
程序员东岸1 小时前
《数据结构——排序(下)》分治与超越:快排、归并与计数排序的终极对决
数据结构·c++·经验分享·笔记·学习·算法·排序算法
qq_160144871 小时前
AI爱好者入门:2025年CAIE报考指南与学习路径解析
人工智能·学习
极市平台1 小时前
骁龙大赛技术分享第4期来了
人工智能·经验分享·笔记·后端·个人开发
joenfoc1 小时前
新手小白动手学习大模型应用开发-搭建个人知识库
学习
lingggggaaaa1 小时前
炎魂网络 - 安全开发实习生面经
网络·学习·安全·web安全·网络安全
好奇龙猫1 小时前
日语学习-日语知识点小记-构建基础-JLPT-N3阶段-二阶段(26):语法和单词 第5-6课
学习
知识分享小能手2 小时前
CentOS Stream 9入门学习教程,从入门到精通, CentOS Stream 9中的文件和目录管理(3)
linux·学习·centos
提娜米苏2 小时前
[论文笔记] ASR is all you need: Cross-modal distillation for lip reading (2020)
论文阅读·深度学习·计算机视觉·语音识别·知识蒸馏·唇语识别