目录
[近似精度 epsilon 的设置](#近似精度 epsilon 的设置)
图像轮廓检测与边缘检测不同,前者旨在检测图像中物体的边界,通常表现为闭合的曲线。
轮廓检测是 OpenCV 中目标检测、形状识别、轮廓拟合的核心基础算法。轮廓指图像中连续的目标边缘像素点集合,可以帮助我们精准锁定图像物体边界。
轮廓检测
图像预处理
轮廓检测不支持直接对彩色图运算,必须通过灰度化、二值化预处理,将图像转化为黑白二值图像(0 纯黑、255 纯白),消除色彩干扰、强化边缘边界。
预处理步骤:彩色原图 → 灰度化预处理 → 二值化分割 → 轮廓检索
python
import cv2
# 1. 读取原始彩色图像
phone = cv2.imread('phone.png')
# 2. 图像预处理1:彩色图转灰度图
phone_gray = cv2.cvtColor(phone, cv2.COLOR_BGR2GRAY)
cv2.imshow('灰度图', phone_gray)
cv2.waitKey(0)
# 3. 图像预处理2:灰度图二值化处理
# 阈值120:像素>125置为255(白),像素≤120置为0(黑)
ret, phone_binary = cv2.threshold(phone_gray, thresh=120, maxval=255, type=cv2.THRESH_BINARY)
cv2.imshow('二值图', phone_binary)
cv2.waitKey(0)
注意OpenCV3.x 与 OpenCV4.x API 存在迭代差异:
-
OpenCV3.x:返回 3 个参数 image, contours, hierarchy,第一个参数为冗余原图,无实际作用
-
OpenCV4.0 及以上:彻底删除第一个返回值 image,仅返回 contours, hierarchy
若高版本使用三参数接收,会直接报参数数量不匹配错误。
通用兼容写法:通过反向索引 -2 直接提取轮廓,适配所有 OpenCV 版本。
python
_, contours, hierarchy = cv2.findContours(phone_binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print(hierarchy)
# 兼容OpenCV3.x/4.x所有版本的轮廓检测代码
# contours = cv2.findContours(phone_binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2]
print(f"当前图像检测到的轮廓总数:{len(contours)}")
查找轮廓
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
自动压缩冗余像素,仅保留轮廓拐点、端点

返回:
image: 返回处理的原图
contours: 包含图像中所有轮廓的list对象。其中每一个独立的轮廓信息以边界点坐标(x,y)的形式储存在numpy数组中。
hierarchy: 轮廓的层次结构。一个包含4个值的数组: Next, Previous, First Child, Parent
Next: 与当前轮廓处于同一层级的下一条轮廓
Previous: 与当前轮廓处于同一层级的上一条轮廓
First Child: 当前轮廓的第一条子轮廓
Parent: 当前轮廓的父轮廓

这张图一共检测到了 9 个轮廓,索引从 0 到 8。
轮廓 2: 3, 1, -1, 0
Next = 3:下一个是 3
Previous = 1:上一个是 1
First Child = -1:无子轮廓
Parent = 0:父轮廓是 0
轮廓的绘制
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。
python
image_copy = phone.copy()
cv2.drawContours(image=image_copy, contours=contours, contourIdx=-1, color=(0, 255, 0), thickness=2)
cv2.imshow('Contours_show', image_copy)
cv2.waitKey(0)

简单轮廓索引定位
当前通过遍历索引(如for循环)来确定特定轮廓的索引是一种临时方法,仅适用于轮廓较少的简单场景。
python
for i in range(len(contours)):
image_copy = cv2.drawContours(image=image_copy, contours=contours, contourIdx=i, color=(0,255,0), thickness=3)
cv2.imshow('Contours_show', image_copy)
cv2.waitKey(0)
轮廓特征
轮廓面积
cv2.contourArea(contour, oriented) -> retval
contour: 它本质上是一个 numpy 数组,里面存着轮廓的所有顶点坐标。(如轮廓列表contours中的一个轮廓)
oriented: 定向区域标志,默认值为False,返回面积的绝对值,True时则根据轮廓方向返回带符号的数值。
python
area_0 = cv2.contourArea(contours[0]) # 轮廓面积
print(area_0)
area_1 = cv2.contourArea(contours[1])
print(area_1)
轮廓周长
cv2.arcLength(InputArray curve, bool closed)
用来计算轮廓或曲线的周长 / 长度,单位是像素。
curve: 输入的二维点集(轮廓顶点),可以是vector或Mat类型。
closed: 布尔值,用于指示曲线是否封闭。
python
length = cv2.arcLength(contours[0], closed=True)
print(length)
根据面积显示特定轮廓
python
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)
先初始化空列表用于存储复合的轮廓
遍历进行条件筛选:遍历所有检测到底轮廓,计算当前轮廓面积,只保留面积大于 10000 像素的轮廓,把满足条件的轮廓存入列表。
复制原始图像。避免直接修改原图,保证后续代码可正常使用原图。
绘制筛选之后的轮廓,在原图副本上用绿色粗线绘制这些轮廓并展示。

根据轮廓面积排序定位
python
sortcnt = sorted(contours, key=cv2.contourArea, reverse=True)[0] # 选取最大面积的轮廓
image_contours = cv2.drawContours(image_copy, contours=[sortcnt], contourIdx=-1, color=(0, 0, 255), thickness=3) # 绘制轮廓
cv2.imshow('image_contours', image_contours)
cv2.waitKey(0)
筛选面积最大的轮廓
sorted():对轮廓列表做排序
key=cv2.contourArea:以轮廓面积作为排序依据
reverse=True:降序排列,面积从大到小
0:取排序后第一个元素,即面积最大的轮廓,结果存入sortcnt
绘制并展示
image_copy:在上一步处理后的图像上绘图
color=(0, 0, 255):BGR 色彩,代表红色

外接圆与外接矩形
python
'''外接圆、外接矩形'''
cnt = contours[6]
(x, y), r = cv2.minEnclosingCircle(cnt) # 计算轮廓的外接圆
phone_circle = cv2.circle(phone, center=(int(x), int(y)), radius=int(r), color=(0, 255, 0), thickness=2) # 绘制外接圆的方法
cv2.imshow('phone_circle', phone_circle)
cv2.waitKey(0)
x, y, w, h = cv2.boundingRect(cnt) # 计算轮廓的最小外接矩形
phone_rectangle = cv2.rectangle(phone, pt1=(x, y), pt2=(x+w, y+h), color=(0, 255, 0), thickness=2) # 在图像上绘制矩形
cv2.imshow('phone_rectangle', phone_rectangle)
cv2.waitKey(0)
取出索引为 6的单个轮廓,赋值给变量 cnt,后续对该轮廓做几何包围计算。
针对第 6 号轮廓,分别计算并绘制最小外接圆和最小外接矩形。
注意:坐标、半径是浮点数,绘图前必须转 int,否则会报错。

轮廓的近似
轮廓近似是 OpenCV 轮廓处理的核心进阶技巧,通过 cv2.approxPolyDP 可以将复杂轮廓简化为少量顶点的多边形,大幅降低后续形状识别、轮廓匹配的计算量。
approx = cv2.approxPolyDP(curve, epsilon, closed)
将复杂轮廓简化为近似多边形轮廓,保留关键顶点的同时去除冗余点。
参数说明:
curve:输入轮廓。
epsilon:近似精度,即两个轮廓之间最大的欧式距离。该参数越小,得到的近似结果越接近实际轮廓;反之,得到的近似结果会更加粗略。
closed:布尔类型的参数,表示是否封闭轮廓。如果是 True,表示输入轮廓是封闭的,近似结果也会是封闭的;否则表示输入轮廓不是封闭的,近似结果也不会是封闭的。
返回值:
approx:近似结果,是一个ndarray数组,为1个近似后的轮廓,包含了被近似出来的轮廓上的点的坐标
python
import cv2
# 1. 图像预处理:灰度化+二值化
phone = cv2.imread('phone.png')
phone_gray = cv2.cvtColor(phone, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, phone_thresh = cv2.threshold(phone_gray, thresh=120, maxval=255, cv2.THRESH_BINARY) # 二值化
# 2. 轮廓检测(兼容所有OpenCV版本)
contours = cv2.findContours(phone_thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2]
# 3. 设置近似精度(epsilon):用轮廓周长的1%作为精度
epsilon = 0.01 * cv2.arcLength(contours[0], closed=True)
# 4. 轮廓近似
approx = cv2.approxPolyDP(contours[0], epsilon, closed=True)
# 5. 对比原轮廓与近似轮廓的顶点数量
print("原轮廓顶点数量:", contours[0].shape)
print("近似轮廓顶点数量:", approx.shape)
# 6. 绘制并显示结果
phone_new = phone.copy()
image_contours = cv2.drawContours(phone_new, contours=[approx], contourIdx=-1, color=(0, 255, 0), thickness=3)
cv2.imshow('原始图像', phone)
cv2.waitKey(0)
cv2.imshow('近似轮廓结果', image_contours)
cv2.waitKey(0)
cv2.destroyAllWindows()
近似精度 epsilon 的设置
原理:将 epsilon 与轮廓周长绑定,保证不同尺寸的图像都能获得合理的简化效果。
参数解析:
cv2.arcLength(contours0, closed=True):计算第 0 个轮廓的周长(像素长度)。
0.01:系数,代表取周长的 1% 作为近似精度。系数可调整:
系数调小(如 0.005):近似更精细,顶点更多,轮廓更接近原图。
系数调大(如 0.05):近似更粗糙,顶点更少,轮廓更简化。
轮廓近似
输入原轮廓 contours0,按设置的 epsilon 精度进行简化,输出近似后的多边形轮廓 approx。
近似后的 approx 与原轮廓格式一致,可直接用于 cv2.drawContours 绘制。
顶点数量对比
原轮廓 contours0.shape 通常为 (N, 1, 2),N 为顶点总数(可能上千个)。
近似后的 approx.shape 顶点数量大幅减少(如矩形轮廓仅保留 4 个顶点),可直观看到简化效果。

不同系数对比效果对比
python
# 系数0.005:近似精细,顶点多,轮廓接近原图
epsilon_1 = 0.005 * cv2.arcLength(contours[0], closed=True)
approx_1 = cv2.approxPolyDP(contours[0], epsilon_1, closed=True)
# 系数0.05:近似粗糙,顶点少,轮廓简化明显
epsilon_2 = 0.05 * cv2.arcLength(contours[0], closed=True)
approx_2 = cv2.approxPolyDP(contours[0], epsilon_2, closed=True)

