(纯新手教学)计算机视觉(opencv)实战九——轮廓检测(Contour Detection)cv2.findContours、cv2.drawContours


📌 轮廓检测(Contour Detection)

1. 什么是轮廓检测?

在图像处理中,轮廓(Contour) 是指沿着相同颜色或灰度值边界的连续曲线。
通过轮廓检测,我们可以获取物体的形状、大小、位置等信息,这在 目标检测、物体识别、图像分割、测量分析 等应用中非常常见。

⚠️ 注意:轮廓检测需要在 二值化图像 上进行,也就是说图像中的像素值必须是 0(黑色)或 255(白色),这样轮廓才会清晰。


2. 轮廓检测函数 —— cv2.findContours

image, contours, hierarchy = cv2.findContours(img, mode, method)

参数说明:

参数名说明
image输入的二值化图像(黑白图,像素值仅 0 和 255)。⚠️ 原图会被修改,通常先用 copy()
mode轮廓检索模式:• cv2.RETR_EXTERNAL :只检测最外层轮廓• cv2.RETR_LIST :检测所有轮廓,不建立层级关系• cv2.RETR_CCOMP :返回两层结构,外轮廓为第 1 层,内孔洞为第 2 层• cv2.RETR_TREE :返回所有轮廓,建立完整树形结构
method轮廓近似方法:• cv2.CHAIN_APPROX_NONE :保留轮廓上所有点(点数多,占内存)• cv2.CHAIN_APPROX_SIMPLE :压缩冗余点,仅保留端点(推荐,节省空间)
contours可选参数,一般不用,传入已有的轮廓数据。
hierarchy可选参数,一般不用,传入已有的层次结构。
offset偏移量,所有轮廓点坐标会加上此偏移值(常用于 ROI 情况)。
  • img:输入图像(必须是二值化图像)。

  • mode:轮廓检索模式(决定如何组织轮廓层次结构)

    • cv2.RETR_EXTERNAL:只检测最外层的轮廓,忽略内部轮廓。

    • cv2.RETR_LIST:检测所有轮廓,但不建立层次结构。

    • cv2.RETR_CCOMP:返回两层结构,外轮廓为第 1 层,内孔洞为第 2 层。

    • cv2.RETR_TREE:返回所有轮廓,并建立完整的层次结构(树形结构)。

  • method:轮廓近似方法

    • cv2.CHAIN_APPROX_NONE:保存轮廓上的所有点。

    • cv2.CHAIN_APPROX_SIMPLE:压缩冗余点,只保留拐点。

返回值:

  • image:输入图像的副本(OpenCV 旧版本返回3个,新版本只返回2个)。

  • contours:一个 Python 列表,包含所有轮廓,每个轮廓是一个 numpy.ndarray,存储轮廓点坐标 (x,y)

  • hierarchy:轮廓的层次结构数组,每个元素包含 [Next, Previous, First_Child, Parent]


3. 层次结构(Hierarchy)

hierarchy 是一个 numpy 数组,形状为 (1, 轮廓数量, 4),含义如下:

  • Next:同一层级的下一个轮廓索引。

  • Previous:同一层级的上一个轮廓索引。

  • First Child:第一个子轮廓索引。

  • Parent:父轮廓索引。

例如,检测一个带孔洞的圆,外圆是父轮廓,内圆是子轮廓。


4. 绘制轮廓 —— cv2.drawContours

cv2.drawContours(image, contours, contourIdx, color, thickness)

参数说明:

参数名类型说明
imagendarray要绘制轮廓的输入图像(一般是原图或副本)
contourslistcv2.findContours() 返回的轮廓列表
contourIdxint要绘制的轮廓索引:• -1:绘制所有轮廓• 0,1,2,...:绘制指定索引的轮廓
color(B,G,R) 元组轮廓颜色,例如 (0,255,0) 表示绿色
thicknessint轮廓线条的粗细,-1 表示填充轮廓区域
lineTypeint(可选)线条类型,默认 cv2.LINE_8
hierarchyndarray(可选)轮廓层次结构(由 findContours 返回),可用于选择性绘制
maxLevelint(可选)绘制的最大轮廓层级,0 只绘制当前层级,1 包括子轮廓,以此类推
offset(x,y) 元组(可选)绘制时对轮廓坐标的偏移量
  • image:要绘制轮廓的图像。

  • contours:由 cv2.findContours() 返回的轮廓列表。

  • contourIdx:要绘制的轮廓索引,-1 表示绘制所有轮廓。

  • color:绘制颜色(BGR 格式,例如 (0,255,0) 表示绿色)。

  • thickness:线条粗细,-1 表示填充整个轮廓。


5. 代码示例:手机轮廓检测

图片准备:可直接使用下图,也可以自备图片

以下所有处理后的图片:

import cv2

# 1. 读取图像
phone = cv2.imread('phone.png')
phone_gray = cv2.cvtColor(phone, cv2.COLOR_BGR2GRAY)

# 2. 二值化处理
ret, phone_binary = cv2.threshold(phone_gray, 120, 255, cv2.THRESH_BINARY)

# 3. 查找轮廓
_, contours, hierarchy = cv2.findContours(phone_binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

print("层次结构:", hierarchy)
print("轮廓数量:", len(contours))

# 4. 绘制轮廓
image_copy = phone.copy()
cv2.drawContours(image_copy, contours, -1, (0, 255, 0), 2)
cv2.imshow('phone', phone)
cv2.imshow('phone_gray', phone_gray)
cv2.imshow('Binary Image', phone_binary)
cv2.imshow('Contours Detected', image_copy)
cv2.waitKey(0)
cv2.destroyAllWindows()

6. 实验效果

  • 二值化图像:显示手机黑白分割后的结果。

  • 绘制轮廓图像:显示绿色线条勾勒出的手机外轮廓和按钮等细节。


7. 轮廓检测的应用

  • 物体识别:通过轮廓形状判断物体类别。

  • 目标跟踪:提取目标的轮廓进行追踪。

  • 测量分析:计算物体的面积、周长、重心。

  • 图像分割:通过轮廓划分前景与背景。


8. 注意事项

  1. 必须二值化:直接用彩色图像检测会失败。

  2. 噪声干扰:噪声会被识别为小轮廓,可在检测前进行 平滑处理

  3. 选择合适的 mode:根据任务需求选择 RETR_EXTERNALRETR_TREE

  4. 层次结构:在复杂图像中,理解 hierarchy 对处理父子轮廓很重要。


✅ 总结:
轮廓检测是图像处理中非常基础且重要的操作。通过 cv2.findContours() 获取轮廓,再配合 cv2.drawContours() 进行可视化,我们不仅能提取图像的外形,还能进行更复杂的几何分析,为后续的目标检测和图像理解打下基础。


<think>我们注意到用户提供的引用[1][3][4]提到了cv2.findContours返回值解包错误的问题。特别是,在OpenCV的不同版本中,cv2.findContours返回值的数量发生了变化:-在OpenCV3.x中,函数返回三个值:image,contours,hierarchy-在OpenCV4.x中,函数返回两个值:contours,hierarchy用户代码中这样使用:contours,_=cv2.findContours(fgmask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)如果用户使用的是OpenCV3.x,那么这样写会报错,因为会返回三个值,而这里试图解包成两个。解决方案:我们可以修改代码以兼容不同版本的OpenCV。一种方法是检查返回值的数量,另一种方法是使用一个中间变量来接收返回值,然后根据返回值的个数来解析。修改后的代码:```pythonimportcv2importnumpyasnp#打开摄像头cap=cv2.VideoCapture(0)#初始化背景减除器fgbg=cv2.createBackgroundSubtractorMOG2()whileTrue:#读取摄像头帧ret,frame=cap.read()ifnotret:print("无法捕获视频流")break#转换为灰度图gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)gray=cv2.GaussianBlur(gray,(21,21),0)#应用背景减除器fgmask=fgbg.apply(gray)#形态学操作以去除噪声kernel=np.ones((5,5),np.uint8)fgmask=cv2.erode(fgmask,kernel,iterations=1)fgmask=cv2.dilate(fgmask,kernel,iterations=5)#查找轮廓-兼容不同版本的OpenCV#在OpenCV4中,findContours返回两个值:contours,hierarchy#在OpenCV3中,返回三个值:image,contours,hierarchy#因此我们用一个变量接收,然后判断cnts=cv2.findContours(fgmask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)#如果返回的是三个元素,那么取第二个元素(轮廓)和第三个(层次结构)#但是我们只需要轮廓#常见的做法是:检查返回的列表的长度iflen(cnts)==2:contours=cnts[0]else:contours=cnts[1]#另一个常见做法是检查OpenCV的版本,但我们这里用上述判断#遍历轮廓forcontourincontours:ifcv2.contourArea(contour)>500:#过滤小区域(x,y,w,h)=cv2.boundingRect(contour)cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2)cv2.putText(frame,"Person",(x,y-10),cv2.FONT_HERSHEY_SIMPLEX,0.9,(0,255,0),2)#显示结果cv2.imshow("DetectedPersons",frame)#按下'q'键退出ifcv2.waitKey(1)&0xFF==ord('q'):break#释放资源cap.release()cv2.destroyAllWindows()```但是,上述方法中,我们实际上需要的是轮廓contours),而不管版本如何,我们可以这样:实际上,在OpenCV4中,返回值是两个,其中第一个就是轮廓。在OpenCV3中,返回值是三个,其中第二个是轮廓。所以我们可以这样:```pythoncnts=cv2.findContours(fgmask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)contours=cnts[0]iflen(cnts)==2elsecnts[1]```但是,注意在OpenCV3中,第一个返回值是处理后的图像(我们不需要),第二个是轮廓,第三个是层次结构。而OpenCV4中第一个是轮廓,第二个是层次结构。因此,我们也可以这样写(更通用):```python#获取轮廓cnts=cv2.findContours(fgmask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)contours=cnts[0]iflen(cnts)==2elsecnts[1]```但是,实际上在OpenCV3中,如果使用`cv2.RETR_EXTERNAL`等参数,返回的三个值,我们只需要第二个(轮廓)。而OpenCV4中,我们只需要第一个(轮廓)。因此,我们可以通过检查返回的元组(或列表)的元素个数来提取轮廓。另一种常见的兼容性写法是:```python#使用一个变量接收所有返回值result=cv2.findContours(fgmask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)#根据返回值数量判断iflen(result)==3:#在OpenCV3中,result为(image,contours,hierarchy)contours=result[1]eliflen(result)==2:#在OpenCV4中,result为(contours,hierarchy)contours=result[0]```然而,实际上OpenCV3和4中,返回值个数都是2?根据引用[4]中的代码,在OpenCV3中,使用`binary,contours,hierarchy=cv2.findContours(...)`,说明返回了三个值。但是,在OpenCV4中,只返回两个值。所以上述代码可以。但是,我查阅OpenCV4的官方文档,`cv.findContours`确实只返回两个值(contours,hierarchy)。所以,我们按照上面的方法。更简单的方法(也是网上常见的):```python#在OpenCV3和4中,我们可以统一用下面的方式获取轮廓#因为OpenCV4中,findContours返回两个值,而OpenCV3返回三个值,但我们可以通过取第一个或第二个索引来兼容#实际上,我们注意到不管版本,我们需要的轮廓总是在返回值的倒数第二个?不对,在OpenCV3中是第二个(索引1),在OpenCV4中是第一个(索引0)?#因此,我们可以用一个中间变量,然后取第二个索引(如果有三个)或者取第一个索引(如果有两个)?#其实,我们观察到:在OpenCV3中,返回三个,我们需要的轮廓是索引1;在OpenCV4中,返回两个,我们需要的轮廓是索引0。所以无法用统一索引。#另一种方法:使用OpenCV的版本判断#例如:#importcv2#ifcv2.__version__.startswith('3'):#_,contours,_=cv2.findContours(...)#else:#contours,_=cv2.findContours(...)#但是,这里我们选择根据返回值的长度来提取轮廓```综合考虑,我们采用根据返回值长度来提取轮廓的方式。修改后的代码片段如下:```python#查找轮廓,并兼容不同版本的OpenCVcnts=cv2.findContours(fgmask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)#返回值可能是两个或三个#如果是三个,则取第二个元素(索引1)为轮廓;如果是两个,则取第一个元素(索引0)为轮廓iflen(cnts)==3:contours=cnts[1]else:contours=cnts[0]```但是注意:在OpenCV3中,返回三个元素,第一个是图像,第二个是轮廓,第三个是层次结构。在OpenCV4中,返回两个元素,第一个是轮廓,第二个是层次结构。因此,我们确实需要这样区分。然而,根据引用[2]中的错误,是“expected3,got2”,说明在OpenCV3中代码写了三个返回值,但是在OpenCV4中只返回两个。因此,我们的代码要确保无论是两个还是三个返回值,我们都能正确拿到轮廓。所以,我们采用:```python#查找轮廓result=cv2.findContours(fgmask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)#如果返回结果是两个,那么说明是OpenCV4,第一个元素是轮廓;如果是三个,那么第二个元素是轮廓iflen(result)==2:contours=result[0]else:contours=result[1]```这样修改后,无论用户使用OpenCV3还是4,都能正确获取轮廓。因此,我们修改原始代码中的轮廓查找部分。最终代码:</think> # 解决cv2.findContours报错"too many values to unpack"问题 这个错误通常是由于OpenCV版本差异导致的返回值数量不一致问题,需要针对不同OpenCV版本做兼容处理。 ## 问题原因分析 OpenCV中`cv2.findContours`函数的返回值在不同版本有变化: - **OpenCV 3.x版本**:返回三个值 `(image, contours, hierarchy)` - **OpenCV 4.x版本**:返回两个值 `(contours, hierarchy)` 当使用`contours, hierarchy = cv2.findContours(...)`这种解包方式时: - 在OpenCV 4.x下运行正常(期望2个返回值) - 但在OpenCV 3.x下会报错(实际返回3个值,超出了2个变量的解包能力)[^1][^3] ## 解决方案 ### 方法一:版本兼容写法(推荐) ```python import cv2 # 读取图像并预处理(根据实际场景调整) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) gray = cv2.GaussianBlur(gray, (3, 3), 0) ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 兼容不同OpenCV版本的findContours调用 contours = [] # 初始化轮廓列表 if cv2.__version__.startswith('3'): # OpenCV 3.x版本 _, contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) else: # OpenCV 4.x版本 contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 后续处理轮廓 for contour in contours: if cv2.contourArea(contour) > 500: # 绘制边界框或其他操作 ``` ### 方法二:通用返回值处理 ```python # 获取findContours所有返回值 result = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 根据返回值数量确定轮廓位置 if len(result) == 3: # OpenCV 3.x _, contours, hierarchy = result else: # OpenCV 4.x contours, hierarchy = result ``` ## 在人影检测中的完整应用 ```python import cv2 import numpy as np cap = cv2.VideoCapture(0) fgbg = cv2.createBackgroundSubtractorMOG2() while True: ret, frame = cap.read() if not ret: break gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray = cv2.GaussianBlur(gray, (21, 21), 0) fgmask = fgbg.apply(gray) kernel = np.ones((5, 5), np.uint8) fgmask = cv2.erode(fgmask, kernel, iterations=1) fgmask = cv2.dilate(fgmask, kernel, iterations=5) # 兼容版本的轮廓检测 result = cv2.findContours(fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if len(result) == 3: _, contours, _ = result else: contours, _ = result for contour in contours: if cv2.contourArea(contour) > 500: (x, y, w, h) = cv2.boundingRect(contour) cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.putText(frame, "Person", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2) cv2.imshow("Motion Detection", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值