【计算机视觉OpenCV 实战】轮廓检测、轮廓特征提取与轮廓近似(零基础入门

文章目录


前言

在计算机视觉领域,OpenCV 是最经典、最常用的开源图像处理库,无论是基础的图像变换、噪声处理,还是进阶的目标检测、图像分割,都离不开 OpenCV 的核心操作。本次案例全覆盖轮廓检测、轮廓特征筛选、外接圆 / 外接矩形绘制、轮廓近似四大核心模块,搭配逐行代码解析 + 原理详解 + 效果说明。

如果对计算机视觉有兴趣但是没有了解的读者,可以先观看博主的第一篇关于计算机视觉入门的文章。

【计算机视觉】OpenCV 图像处理阈值处理 + 图像编辑 + 噪声滤波 + 图像运算

一、轮廓检测基础

1.概念

轮廓检测是图像分割与目标识别的核心技术,核心逻辑是:在二值图像中,通过边缘点的连接,勾勒出目标物体的边界轮廓,实现前景物体的定位与分离。

简单来说:就是把图像中不同物体的 "外边框" 找出来,后续才能进行物体计数、特征分析等操作。

2. 轮廓检测的前置条件

轮廓检测必须在二值图像上进行,所以完整流程是:

彩色图 → 灰度图 → 二值化处理 → 轮廓检测

3. 完整代码

轮廓的检索模式,主要有四种方式:

  1. cv2.RETR_EXTERNAL:只检测外轮廓,所有子轮廓被忽略
  2. cv2.RETR_LIST:检测的轮廓不建立等级关系,所有轮廓属于同一等级
  3. cv2.RETR_CCOMP:返回所有的轮廓,只建立两个等级的轮廓。一个对象的外轮廓为第1级组织结构。而对象内部中空洞的轮廓为第2级组织结构,空洞中的任何对象的轮廓又是第1级组织结构
  4. cv2.RETR_TREE:返回所有的轮廓,建立一个完整的组织结构的轮廓。

代码完整且可直接运行,有需要可自取

c 复制代码
import cv2
py = cv2.imread('python.png')
py_new = cv2.resize(py,None,fx=0.3,fy=0.3)
py_gray = cv2.cvtColor(py_new,cv2.COLOR_BGR2GRAY)
cv2.imshow('py_b',py_gray)
cv2.waitKey(0)

ret,py_binary = cv2.threshold(py_gray,120,255,cv2.THRESH_BINARY)
cv2.imshow('py_binary',py_binary)
cv2.waitKey(0)
contours = cv2.findContours(py_binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)[-2]
print(len(contours))


image_copy = py_new.copy()
cv2.drawContours(image=image_copy,contours=contours,contourIdx=-1,color=(0,255,0),thickness=2)
# 查看检测到了多少个轮廓
print("检测到的轮廓总数:", len(contours))
cv2.imshow('Contours_show',image_copy)
cv2.waitKey(0)
cv2.destroyAllWindows()

4. 代码详解

在编写代码前,我们需要先安装核心工具包:

c 复制代码
pip install opencv-python numpy -i https://pypi.tuna.tsinghua.edu.cn/simple/

首先导入 OpenCV 库,读取本地图像并做基础预处理:

  • cv2.imread():读取彩色图像,参数为图片路径;
  • cv2.resize():按比例缩放图像,fx/fy 为宽高缩放比例,避免图像过大导致轮廓检测混乱;
  • cv2.cvtColor():将彩色图像转为灰度图,cv2.COLOR_BGR2GRAY 表示 BGR 转灰度,二值化处理必须使用灰度图。
c 复制代码
import cv2
py = cv2.imread('python.png')
py_new = cv2.resize(py,None,fx=0.3,fy=0.3)
py_gray = cv2.cvtColor(py_new,cv2.COLOR_BGR2GRAY)

调用 cv2.threshold() 将灰度图转为黑白二值图。

这涉及到图像的阈值处理,博主也有相关介绍的文章,感兴趣的读者也可以去看看,这里不过多赘述,仅说明方法。

c 复制代码
ret,py_binary = cv2.threshold(py_gray,120,255,cv2.THRESH_BINARY)

这是轮廓检测核心函数,会返回三个值,轮廓列表、层次结构等,-2 表示只取轮廓列表;

轮廓检索模式cv2.RETR_TREE:建立轮廓的完整层次结构.

轮廓逼近方法 cv2.CHAIN_APPROX_NONE:保留轮廓上的所有点。

c 复制代码
contours = cv2.findContours(py_binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)[-2]

绘制轮廓

  • image_copy:在原图副本上绘制轮廓,避免修改原图;
  • contourIdx=-1:表示绘制所有轮廓,若指定索引则只绘制对应轮廓;
  • color=(0, 255, 0):轮廓颜色,这里是绿色;代表BGR通道。
  • thickness=2:轮廓线的粗细。
c 复制代码
image_copy = py_new.copy()
cv2.drawContours(image=image_copy,contours=contours,contourIdx=-1,color=(0,255,0),thickness=2)

程序运行结束后关闭窗口。

c 复制代码
cv2.imshow('Contours_show', image_copy)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行结果:

二、轮廓特征筛选与外接图形绘制

1. 概念

轮廓检测会返回大量轮廓(包括噪声、小斑点等无效轮廓),需要通过轮廓面积筛选有效目标;同时可以为轮廓绘制外接圆、外接矩形,用于目标定位与特征分析。

2. 完整代码

c 复制代码
import cv2
py = cv2.imread('python.png')
py_new = cv2.resize(py,None,fx=0.3,fy=0.3)
py_gray = cv2.cvtColor(py_new,cv2.COLOR_BGR2GRAY)
cv2.imshow('py_b',py_gray)
cv2.waitKey(0)

ret,py_binary = cv2.threshold(py_gray,120,255,cv2.THRESH_BINARY)
cv2.imshow('py_binary',py_binary)
cv2.waitKey(0)
contours = cv2.findContours(py_binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)[-2]
print(len(contours))


area_0 = cv2.contourArea(contours[0])
print(area_0)
area_1 = cv2.contourArea(contours[1])
print(area_1)

length = cv2.arcLength(contours[0],closed=True)
print(length)

a_list = []
for i in contours:
    if cv2.contourArea(i)>4000:#做筛选,面积大于4000的轮廓
        a_list.append(i)
image_copy = py_new.copy()
image_copy = cv2.drawContours(image=image_copy,contours=a_list,contourIdx=-1,color=(0,255,0),thickness=3)
cv2.imshow('Contours_show_4000',image_copy)
cv2.waitKey(0)

sortcnt = sorted(contours,key=cv2.contourArea,reverse=True)[0]
image_contours = cv2.drawContours(image_copy,[sortcnt],contourIdx=-1,color=(255,0,0),thickness=3)
cv2.imshow('image_contours',image_contours)
cv2.waitKey(0)

#外接圆,外接矩阵
cnt = contours[5]
(x,y),r = cv2.minEnclosingCircle(cnt)
py_new_circle = cv2.circle(py_new,(int(x),int(y)),int(r),(0,255,0),3)
cv2.imshow('py_new_circle',py_new_circle)
cv2.waitKey(0)

x,y,w,h = cv2.boundingRect(cnt)
py_new_rectangle = cv2.rectangle(py_new,(x,y),(x+w,y+h),(0,255,0),3)
cv2.imshow('py_new_rectangle',py_new_rectangle)
cv2.waitKey(0)

3. 代码详解

上方一段代码和轮廓检测是一样的,这里不过多赘述。

这里我们检测轮廓的面积,周长大小,并输出。

面积大小用于代码筛选的条件。

c 复制代码
area_0 = cv2.contourArea(contours[0])
print(area_0)
area_1 = cv2.contourArea(contours[1])
print(area_1)
length = cv2.arcLength(contours[0],closed=True)
print(length)

这里设立for循环遍历语句,目的是筛选出轮廓大小大于4000的轮廓。(单位为像素)

再将筛选出来的轮廓添加到列表中。

c 复制代码
a_list = []
for i in contours:
    if cv2.contourArea(i)>4000:
    		a_list.append(i)

我们这里通过copy的方法,复制原图像,之后的操作都在复制图像上操作,而不会对原图像有影响。

对复制图像进行轮廓检测,并输出图像。

c 复制代码
image_copy = py_new.copy()
image_copy = cv2.drawContours(image=image_copy,contours=a_list,contourIdx=-1,color=(0,255,0),thickness=3)
cv2.imshow('Contours_show_4000',image_copy)
cv2.waitKey(0)

按面积排序,找到最大轮廓。以轮廓的面积为关键字,降序排列;后输出图像。

这里用到的颜色为蓝色,便于后面区分。

c 复制代码
sortcnt = sorted(contours,key=cv2.contourArea,reverse=True)[0]
image_contours = cv2.drawContours(image_copy,[sortcnt],contourIdx=-1,color=(255,0,0),thickness=3)
cv2.imshow('image_contours',image_contours)
cv2.waitKey(0)

我们设cnt为第六个轮廓,绘制外接圆。

要绘制出圆,我们需要圆心坐标和半径r。

cv2.minEnclosingCircle(cnt):计算轮廓的最小外接圆,返回圆心坐标 (x, y) 和半径 r;

同时,cv2.circle():在图像上绘制圆形,参数为图像、圆心、半径、颜色、线宽。

c 复制代码
cnt = contours[5]
(x,y),r = cv2.minEnclosingCircle(cnt)
py_new_circle = cv2.circle(py_new,(int(x),int(y)),int(r),(0,255,0),3)
cv2.imshow('py_new_circle',py_new_circle)
cv2.waitKey(0)

绘制外接矩形,我们需要的是左上角坐标和右下角坐标。从代码中,我们可以得到左上角坐标(x,y),和宽w,高h。

在这里我们需要注意的是在pycharm中的坐标系与我们在数学中的形态是不同的。

c 复制代码
x,y,w,h = cv2.boundingRect(cnt)
py_new_rectangle = cv2.rectangle(py_new,(x,y),(x+w,y+h),(0,255,0),3)
cv2.imshow('py_new_rectangle',py_new_rectangle)
cv2.waitKey(0)

运行结果:

三、轮廓近似(多边形逼近)

1.概念

轮廓近似是用更少的点来近似表示轮廓,核心逻辑是通过设置近似精度 epsilon,将复杂轮廓简化为多边形轮廓,保留关键形状特征,减少轮廓点数量,提高后续处理效率。

2. 完整代码

c 复制代码
import cv2

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=cv2.findContours (phone_thresh, cv2.RETR_TREE, cv2. CHAIN_APPROX_NONE)[-2]
epsilon = 0.01 * cv2.arcLength(contours[0],True)
approx = cv2.approxPolyDP(contours[0],epsilon,True)

print(contours[0].shape)
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)

3. 代码详解

在对图像做完预处理之后,计算轮廓周长

  • cv2.arcLength(contours0, True):计算轮廓的周长,True 表示轮廓是封闭的;
  • epsilon:近似精度,这里取轮廓周长的 1%,值越小,近似后的轮廓越接近原轮廓;值越大,轮廓越简化。
c 复制代码
contours=cv2.findContours (phone_thresh, cv2.RETR_TREE, cv2. CHAIN_APPROX_NONE)[-2]
epsilon = 0.01 * cv2.arcLength(contours[0],True)

轮廓近似核心函数cv2.approxPolyDP(),有以下几个参数需要注意,其他的参数可暂时用默认。

  • contours0:需要近似的原轮廓;
  • epsilon:近似精度,即轮廓上的点到近似多边形的最大距离;
  • True:表示近似后的轮廓是封闭的。
c 复制代码
approx = cv2.approxPolyDP(contours[0],epsilon,True)

打印第一个轮廓的形状,后输出轮廓的格式,一般是(点数, 1, 2)

然后打印打印近似后的经过 approxPolyDP 简化后的轮廓的形状,输出会比原来小很多。

最后在复制的图上,用绿色线画出简化后的轮廓。

c 复制代码
print(contours[0].shape)
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)

代码输出:

第一行输出是原轮廓。

第二行是简化后的轮廓。

c 复制代码
(759, 1, 2)
(8, 1, 2)

输出结果:

我们观察可以发现简化后的轮廓共8个点,符合结果。

之后也可以尝试不同的近似精度对图像,对轮廓的影响。

相关推荐
dianziqian11 小时前
什么是电子签?
大数据·网络·人工智能
小美美大白蛋11 小时前
从词袋模型到预训练语言模型:文本表示方法的演进
人工智能·语言模型·自然语言处理
学习要积极11 小时前
Spring AI 与阿里云 AI 快速入门:从零搭建智能应用
人工智能·spring·阿里云
crazyme_611 小时前
软件工程实践:从零到一,开发 AI 提示注入闯关平台
人工智能·软件工程
Mr数据杨11 小时前
【CanMV K210】传感器实验 HC-SR04 超声波测距与状态判断
人工智能·硬件开发·canmv k210
beyond阿亮11 小时前
PicoClaw皮皮虾: 端侧设备能跑AI智能体 超轻量AI智能体 极低成本硬件跑AI Agent,内存小于10MB
人工智能·ai·openclaw·picoclaw
kennyS_Titan11 小时前
PCB多层板升级,支撑智能硬件加速落地
人工智能·智能硬件
新加坡内哥谈技术11 小时前
Apple 和 Google 正在如何改造你的 push notification
人工智能
我材不敲代码11 小时前
【OpenCV零基础实战】键盘交互、像素位运算、通道离合、色彩转换与智能抠像
人工智能·opencv·计算机外设