目录
[一、轮廓检测:从边缘到 "完整形状"](#一、轮廓检测:从边缘到 “完整形状”)
[1. 核心逻辑](#1. 核心逻辑)
[2. 实战代码(直接复用)](#2. 实战代码(直接复用))
[3. 效果图表分析(图 5:轮廓检测流程效果)](#3. 效果图表分析(图 5:轮廓检测流程效果))
[1. 实战代码(直接复用)](#1. 实战代码(直接复用))
[2. 效果图表分析(图 6:轮廓特征筛选效果)](#2. 效果图表分析(图 6:轮廓特征筛选效果))
[四、轮廓近似与外接图形:简化轮廓 + 定位目标](#四、轮廓近似与外接图形:简化轮廓 + 定位目标)
[1. 轮廓近似:压缩轮廓点,简化形状](#1. 轮廓近似:压缩轮廓点,简化形状)
[2. 外接图形:用几何图形框选目标](#2. 外接图形:用几何图形框选目标)
[3. 实战代码(直接复用)](#3. 实战代码(直接复用))
[4. 效果图表分析(图 7:轮廓近似 + 外接图形效果)](#4. 效果图表分析(图 7:轮廓近似 + 外接图形效果))
[五、模板匹配:在图像中 "精准找模板"](#五、模板匹配:在图像中 “精准找模板”)
[1. 核心逻辑](#1. 核心逻辑)
[2. 实战代码(直接复用)](#2. 实战代码(直接复用))
[3. 效果图](#3. 效果图)
引言
在上一篇(中)我们吃透了边缘检测的四大算子,而边缘只是 "零散的特征点",想要从图像中定位目标、提取形状特征,还得靠轮廓检测 ;想要在复杂图像中精准找到指定模板,模板匹配是最直接的方案!今天这篇,我们把轮廓检测的 "查找、绘制、特征分析" 和模板匹配的 "精准定位" 全讲透,代码直接复用,看完就能实现 "图像目标识别"!
一、轮廓检测:从边缘到 "完整形状"
轮廓是 "连续的、闭合的边缘",是图像中目标的 "形状骨架"。OpenCV 轮廓检测流程:灰度化→二值化→查找轮廓→绘制 / 分析轮廓。
二、轮廓的查找与绘制
1. 核心逻辑
轮廓检测的前提是图像为 "二值图"(像素值仅 0/255),需先做灰度化 + 阈值二值化;cv2.findContours查找轮廓,cv2.drawContours绘制轮廓。
2. 实战代码(直接复用)
python
'''----------------轮廓检测-----------------------'''
# 查找轮廓的API:image, contours, hierarchy = cv2.findContours(img, mode, method)#
# 参数:img:需要实现轮廓检测的原图
# mode: 轮廓的检索模式,主要有四种方式:
# cv2.RETR_EXTERNAL:只检测外轮廓,所有子轮廓被忽略
# cv2.RETR_LIST:检测的轮廓不建立等级关系,所有轮廓属于同一等级
# cv2.RETR_CCOMP:返回所有的轮廓,只建立两个等级的轮廓。一个对象的外轮廓为第1级组织结构。
# 而对象内部中空洞的轮廓为第2级组织结构,空洞中的任何对象的轮廓又是第 1 级组织结构。
# cv2.RETR_TREE:返回所有的轮廓,建立一个完整的组织结构的轮廓。
# method:轮廓的近似方法,主要有以下两种:
# cv2.CHAIN_APPROX_NONE:存储所有的轮廓点。
# cv2.CHAIN_APPROX_SIMPLE:压缩模式,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息。
# 返回:image:返回处理的原图
# contours:包含图像中所有轮廓的list对象。其中每一个独立的轮廓信息以边界点坐标(x,y)的形式储存在numpy数组中。
# hierarchy:轮廓的层次结构。一个包含4个值的数组:[Next, Previous, First Child, Parent]
# Next:与当前轮廓处于同一层级的下一条轮廓
# Previous:与当前轮廓处于同一层级的上一条轮廓
# First Child:当前轮廓的第一条子轮廓
# Parent:当前轮廓的父轮廓
# 注意:做轮廓检测前需要将图片读取为二值数据,即像素值只为0和255。
import cv2
phone = cv2.imread('phone.png')#读取原图
phone_gray = cv2.cvtColor(phone,cv2.COLOR_BGR2GRAY)#灰度图的处理
cv2.imshow('phone_b',phone_gray)
cv2.waitKey(0)
# phone_gray=cv2.imread('phone.png',0) #读取灰度图
ret, phone_binary = cv2.threshold(phone_gray, 120, 255, cv2.THRESH_BINARY)#阈值处理为二值
cv2.imshow('phone_binary',phone_binary)
cv2.waitKey(0)
contours, hierarchy = cv2.findContours(phone_binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
print(hierarchy)
print(len(contours))
# 轮廓的绘制
# cv2.drawContours(image, contours, contourIdx, color, thickness=None,
# lineType=None, hierarchy=None, maxLevel=None, offset=None)
# 参数含义如下:
# image:要在其上绘制轮廓的输入图像。
# contours:轮廓列表,通常由cv2.findContours()函数返回。
# contourIdx:要绘制的轮廓的索引。如果为负数,则绘制所有轮廓。 -1
# color:轮廓的颜色,以BGR格式表示。例如,(0, 255, 0)表示绿色。
# thickness:轮廓线的粗细。默认值为1。
# lineType:轮廓线的类型。默认值为cv2.LINE_8。
# hierarchy:轮廓层次结构。通常由cv2.findContours()函数返回。
# maxLevel:绘制的最大轮廓层级。默认值为None,表示绘制所有层级。
# offset:轮廓点的偏移量。默认值为None。
image_copy = phone.copy()
image_copy = cv2.drawContours(image=image_copy, contours=contours, contourIdx=6,color=(0,255,0),thickness=3)
cv2.imshow('Contours_show', image_copy)
cv2.waitKey(0)
3. 效果图表分析(图 5:轮廓检测流程效果)

| 处理步骤 | 效果描述 |
|---|---|
| 原始图(phone.png) | 彩色手机截图,包含多个 UI 元素(按钮、图标、文字) |
| 灰度图 | 去除色彩干扰,仅保留明暗信息 |
| 二值图 | 明暗区域被简化为黑 / 白,轮廓的边界清晰可分 |
| 绘制轮廓后 | 索引为 6 的轮廓(如手机图标)被绿色线条标注,轮廓闭合且完整 |
三、轮廓特征分析:量化目标形状
找到轮廓后,可提取面积、周长等特征,实现 "按特征筛选轮廓"(过滤小噪声轮廓,保留目标轮廓)。
1. 实战代码(直接复用)
python
import cv2
phone = cv2.imread('phone.png')#读取原图
phone_gray = cv2.cvtColor(phone,cv2.COLOR_BGR2GRAY)#灰度图的处理
ret, phone_binary = cv2.threshold(phone_gray, 120, 255, cv2.THRESH_BINARY)#阈值处理为二值
contours, hierarchy = cv2.findContours(phone_binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
# 轮廓特征
# cv2.contourArea(contour[, oriented]) → retval 面积
# contour:顶点构成的二维向量组(如轮廓列表contours中的一个轮廓)
# oriented:定向区域标志,默认值为 False,返回面积的绝对值,Ture 时则根据轮廓方向返回带符号的数值
area_0 = cv2.contourArea(contours[0])
print(area_0)
area_1 = cv2.contourArea(contours[1])
print(area_1)
# arcLength(InputArray curve, bool closed) 周长
# curve,输入的二维点集(轮廓顶点),可以是 vector 或 Mat 类型。
# closed,用于指示曲线是否封闭。
length = cv2.arcLength(contours[0],closed=True)
print(length)
# 根据面积显示特定轮廓
a_list=[]
for i in contours:
if cv2.contourArea(i) > 10000:
a_list.append(i)
image_copy = phone.copy()
image_copy = cv2.drawContours(image=image_copy, contours=a_list, contourIdx=-1,color=(0,255,0),thickness=3)
cv2.imshow('Contours_show_10000', image_copy)
cv2.waitKey(0)
#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
2. 效果图表分析(图 6:轮廓特征筛选效果)

| 筛选条件 | 效果描述 |
|---|---|
| 无筛选(全轮廓) | 图像中所有小轮廓(如文字、小图标)都被绘制,画面杂乱 |
| 面积 > 10000 | 仅保留大轮廓(如手机屏幕、大按钮),小噪声轮廓被过滤,目标轮廓更突出 |
| 核心价值 | 特征筛选是 "从多轮廓中定位目标" 的关键,适合复杂背景下的目标提取 |
四、轮廓近似与外接图形:简化轮廓 + 定位目标
1. 轮廓近似:压缩轮廓点,简化形状
通过cv2.approxPolyDP用更少的点描述轮廓(如把圆角按钮近似为矩形),减少计算量。
2. 外接图形:用几何图形框选目标
包括外接圆、最小外接矩形,快速定位轮廓的位置和大小。
3. 实战代码(直接复用)
python
# 轮廓的近似
# approx = cv2.approxPolyDP(curve, epsilon, closed)
# 参数说明:
# curve:输入轮廓。
# epsilon:近似精度,即两个轮廓之间最大的欧式距离。该参数越小,得到的近似结果越接近实际轮廓;反之,得到的近似结果会更加粗略。
# closed:布尔类型的参数,表示是否封闭轮廓。如果是 True,表示输入轮廓是封闭的,近似结果也会是封闭的;否则表示输入轮廓不是封闭的,近似结果也不会是封闭的。
# 返回值:approx:近似结果,是一个ndarray数组,为1个近似后的轮廓,包含了被近似出来的轮廓上的点的坐标
phone = cv2.imread('phone.png')
phone_gray = cv2.cvtColor(phone,cv2.COLOR_BGR2GRAY) #转换为灰度图
ret,phone_thresh = cv2.threshold(phone_gray,120,255,cv2.THRESH_BINARY) #二值化
contours, hierarchy = cv2.findContours(phone_thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)#获取轮廓
# #
# epsilon = 0.01 * cv2.arcLength(contours[0],True) #设置近似精度
# approx = cv2.approxPolyDP(contours[0], epsilon, True) #对轮廓进行近似
# print(approx.shape)
# phone_new = phone.copy()
# image_contours = cv2.drawContours(phone_new,[approx],contourIdx=-1,color=(0,255,0),thickness=3)#绘制轮廓
# cv2.imshow('phone',phone)
# cv2.waitKey(0)
# cv2.imshow('image_contours',image_contours)
# cv2.waitKey(0)
# # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
# # 外接圆、外接矩形,...
cnt = contours[6]
(x,y),r = cv2.minEnclosingCircle(cnt)#计算轮廓的外接圆
phone_circle = cv2.circle(phone,(int(x),int(y)),int(r),(0,255,0),2)#绘制外接圆的方法
cv2.imshow('phone_circle',phone_circle)
cv2.waitKey(0)
x,y,w,h = cv2.boundingRect(cnt)#计算轮廓的最小外接矩形
phone_rectangle = cv2.rectangle(phone,(x,y),(x+w,y+h),(0,255,0),2) #在图像上绘制矩形
cv2.imshow('phone_rectangle',phone_rectangle)
cv2.waitKey(0)
4. 效果图表分析(图 7:轮廓近似 + 外接图形效果)

| 处理类型 | 效果描述 |
|---|---|
| 原始轮廓 | 轮廓点数量多,形状不规则(如带圆角的按钮) |
| 轮廓近似后 | 用少量点近似轮廓,圆角按钮被近似为矩形,形状更简洁 |
| 外接圆 | 以轮廓中心为圆心、最大距离为半径绘制圆,快速圈定目标范围 |
| 最小外接矩形 | 用矩形精准框选目标,坐标(x,y)+ 宽高(w,h)可直接定位目标位置 |
五、模板匹配:在图像中 "精准找模板"
模板匹配是最直接的 "目标检索" 方法:用指定模板在待搜索图像中滑动,计算匹配度,匹配度最高的位置即为目标位置。
1. 核心逻辑
cv2.matchTemplate计算匹配度矩阵,cv2.minMaxLoc找到匹配度最高的位置,最后用矩形标注目标区域。
2. 实战代码(直接复用)
python
'''-----------------模板匹配--------------------------------'''
# cv2.matchTemplate(image, templ, method, result=None, mask=None)
# image:待搜索图像
# templ:模板图像
# method:计算匹配程度的方法,可以有:
# TM_SQDIFF 平方差匹配法:该方法采用平方差来进行匹配;匹配越好,值越小;匹配越差,值越大。
# TM_CCORR 相关匹配法:该方法采用乘法操作;数值越大表明匹配程度越好。
# TM_CCOEFF 相关系数匹配法:数值越大表明匹配程度越好。
# TM_SQDIFF_NORMED 归一化平方差匹配法,匹配越好,值越小;匹配越差,值越大。
# TM_CCORR_NORMED 归一化相关匹配法,数值越大表明匹配程度越好。
#-> TM_CCOEFF_NORMED 归一化相关系数匹配法,数值越大表明匹配程度越好。
kele = cv2.imread('kele.png')
template = cv2.imread('template.png')
cv2.imshow('kele',kele)
cv2.imshow('template',template)
cv2.waitKey(0)
h, w = template.shape[:2]
res = cv2.matchTemplate(kele, template, cv2.TM_CCOEFF_NORMED) #返回匹配结果的矩阵,其中每个元素表示该位置与模板的匹配程度
# cv2.minMaxLoc可以获取矩阵中的最小值和最大值,以及最小值的索引号和最大值的索引号
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) # 最小值、最大值、最小值位置、最大值位置
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
kele_template = cv2.rectangle(kele, top_left, bottom_right, (0, 255, 0), 2) # 绘制矩形
cv2.imshow('kele_template', kele_template)
cv2.waitKey(0)
3. 效果图

六、总结
从基础的图像读写、形态学变换,到边缘检测、轮廓分析,再到模板匹配,我们完整走完了 OpenCV "从像素处理到目标匹配" 的核心流程:
- 基础操作:搞定图像 / 视频的读写、通道分离、缩放、运算等底层技能;
- 边缘检测:用 Sobel/Scharr/Laplacian/Canny 提取图像边缘特征;
- 轮廓检测:从边缘中提取闭合形状,分析特征、近似形状、定位目标;
- 模板匹配:在复杂图像中精准检索指定模板,实现目标定位。
这些技能是计算机视觉的 "基本功",无论是目标识别、图像分割还是 OCR,都离不开这些核心操作。建议把代码跑起来,调整参数(如阈值、匹配方法),直观感受不同参数的效果,真正吃透 OpenCV 的实战精髓!