【OpenCV】图像的轮廓检测

目录

轮廓检测

图像预处理

查找轮廓

轮廓的绘制

简单轮廓索引定位

轮廓特征

轮廓面积

轮廓周长

根据面积显示特定轮廓

根据轮廓面积排序定位

外接圆与外接矩形

轮廓的近似

[近似精度 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 存在迭代差异:

  1. OpenCV3.x:返回 3 个参数 image, contours, hierarchy,第一个参数为冗余原图,无实际作用

  2. 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)

相关推荐
久菜盒子工作室15 小时前
深科技最近的经营状况
大数据·人工智能·科技
yubo050915 小时前
计算机视觉第一课:环境搭建 + 第一个 CV 程序
人工智能·计算机视觉
weixin_3975740915 小时前
食品包装AI质检时代来了,标签审核效率提升千倍
人工智能
一头爱吃肉的牛15 小时前
2026年支持自定义模板的AI PPT工具测评:5款工具横向对比
人工智能·powerpoint
EIConferenceEmma15 小时前
【合作EI期刊 | IEEE出版 | 中国石油大学(华东)主办】第六届先进算法与神经网络国际学术会议(AANN 2026)
人工智能·神经网络·算法·机器学习
jiayong2315 小时前
harness 与 hermes-agent 源码阅读路线和维护建议
人工智能·ai·智能体·harness·hermes-agent
量子-Alex15 小时前
【大模型智能体】A practical guide to building agents
大数据·数据库·人工智能
千寻girling15 小时前
机器学习 | 监督学习算法(了解) | 尚硅谷学习
开发语言·人工智能·后端·python·学习·算法·机器学习
蓦然回首却已人去楼空15 小时前
深度学习进阶:自然语言处理|6.1.4 QA|L2 范数、梯度裁剪与 L1/L2 正则化详解
人工智能·深度学习·自然语言处理