下面我将从基础到进阶,详细讲解如何遍历 contours,并提供清晰的代码示例。

基础概念
回顾一下如何获取 contours:
import cv2
import numpy as np
# 1. 读取图像并转为灰度图
image = cv2.imread('your_image.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 2. 进行二值化处理
# 阈值可以根据你的图像调整
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 3. 查找轮廓
# cv2.findContours() 返回三个值:
# - contours: 一个包含所有轮廓的列表
# - hierarchy: 一个包含轮廓层次信息的数组
# - cv2.RETR_TREE: 检测所有轮廓,并建立嵌套轮廓的层次关系
# - cv2.CHAIN_APPROX_SIMPLE: 压缩轮廓,只保存关键点
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours 就是我们需要遍历的对象。
遍历所有轮廓
这是最简单直接的方法,适用于当你不关心轮廓的内部嵌套关系,只想对每一个轮廓进行操作时。
使用 for 循环(推荐)
这是最常见、最 Pythonic 的方式。

# 遍历 contours 列表中的每一个轮廓
for contour in contours:
# contour 是一个 NumPy 数组,形状为 (N, 1, 2),N 是轮廓上的点数
# 每个点的格式是 [[x, y]]
# 在这里可以对每个轮廓进行操作
# 计算轮廓的面积
area = cv2.contourArea(contour)
print(f"轮廓面积: {area}")
# 绘制轮廓
# -1 表示绘制所有轮廓
# (0, 255, 0) 是颜色 (BGR格式)
# 2 是线条粗细
cv2.drawContours(image, [contour], -1, (0, 255, 0), 2)
# 显示结果
cv2.imshow('Contours', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
关键点:
for contour in contours:会直接获取列表中的每一个轮廓对象。contour的形状是(N, 1, 2),N是构成该轮廓的点数,如果你需要(x, y)坐标,可以访问contour[i, 0]。
使用索引和 len()
如果你需要知道当前是第几个轮廓,或者需要通过索引访问,可以使用这种方法。
# 遍历 contours 列表,使用索引
for i in range(len(contours)):
contour = contours[i]
# 获取当前轮廓的索引
print(f"正在处理第 {i+1} 个轮廓")
# 计算轮廓的周长
perimeter = cv2.arcLength(contour, True) # True表示轮廓是闭合的
print(f"轮廓周长: {perimeter}")
# 绘制轮廓
cv2.drawContours(image, [contour], -1, (255, 0, 0), 2)
cv2.imshow('Contours with Index', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
处理层次结构 (hierarchy)
cv2.findContours 返回的 hierarchy 变量描述了轮廓的父子关系,这对于处理嵌套轮廓(如物体内部的孔洞)非常重要。
hierarchy 是一个形状为 (N, 1, 4) 的数组,N 是轮廓的数量,每个轮廓对应一行 [Next, Previous, First_Child, Parent]。
Next: 同一层级中的下一个轮廓的索引,如果没有,则为-1。Previous: 同一层级中的上一个轮廓的索引,如果没有,则为-1。First_Child: 第一个子轮廓的索引,如果没有,则为-1。Parent: 父轮廓的索引,如果没有,则为-1。
示例:遍历并区分外部轮廓和内部轮廓(孔洞)
假设我们想找到所有外部轮廓(它们的父轮廓是 -1),并用绿色绘制;找到所有内部轮廓(孔洞),并用红色绘制。
# 清空之前的绘制,重新读取图像
image = cv2.imread('your_image.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 遍历所有轮廓及其对应的层次信息
# 使用 enumerate 可以同时获取索引和轮廓对象
for i, contour in enumerate(contours):
# hierarchy[0, i] 获取第 i 个轮廓的层次信息
# [Next, Previous, First_Child, Parent]
# hierarchy[0, i, 3] Parent
parent_index = hierarchy[0, i, 3]
# 如果父轮廓索引为 -1,说明它是顶层/外部轮廓
if parent_index == -1:
cv2.drawContours(image, [contour], -1, (0, 255, 0), 3) # 绿色,粗线
print(f"外部轮廓 #{i+1} 面积: {cv2.contourArea(contour)}")
else:
# 否则,它是一个内部轮廓(孔洞)
cv2.drawContours(image, [contour], -1, (0, 0, 255), 2) # 红色,细线
print(f"内部轮廓 #{i+1} (父轮廓是 #{parent_index+1}) 面积: {cv2.contourArea(contour)}")
cv2.imshow('Hierarchy Contours', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
进阶:筛选轮廓并遍历
在实际应用中,我们通常只对满足特定条件的轮廓感兴趣,例如面积大于某个值的轮廓。
# 清空之前的绘制
image = cv2.imread('your_image.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, _ = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 使用 RETR_LIST 获取所有轮廓
# 设置一个面积阈值
min_area = 1000
# 遍历所有轮廓,并筛选
for contour in contours:
area = cv2.contourArea(contour)
# 只处理面积大于阈值的轮廓
if area > min_area:
print(f"找到有效轮廓,面积: {area}")
# 获取轮廓的边界矩形
x, y, w, h = cv2.boundingRect(contour)
# 在原图上绘制边界矩形
cv2.rectangle(image, (x, y), (x + w, y + h), (255, 255, 0), 2)
# 在轮廓上方显示面积
text = f"Area: {int(area)}"
cv2.putText(image, text, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2)
cv2.imshow('Filtered Contours', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
遍历轮廓上的点
你可能需要访问构成轮廓的每一个像素点。
# 假设我们已经找到了 contours
image = cv2.imread('your_image.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, _ = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# 选择第一个轮廓进行演示
if contours:
contour = contours[0]
# 遍历轮廓上的每一个点
for point in contour:
# point 的形状是 [[x, y]]
# 我们需要提取出 x 和 y
x, y = point[0]
# 在图像上标记这个点
# (0, 0, 255) 是红色
cv2.circle(image, (x, y), 1, (0, 0, 255), -1) # -1 表示填充
cv2.imshow('Contour Points', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
| 遍历目标 | 方法 | 示例代码 |
|---|---|---|
| 所有轮廓 | for contour in contours: |
for cnt in contours: cv2.drawContours(img, [cnt], -1, color, thickness) |
| 带索引的轮廓 | for i in range(len(contours)): |
for i in range(len(contours)): cnt = contours[i] |
| 带层次信息的轮廓 | for i, contour in enumerate(contours): |
for i, cnt in enumerate(contours): parent = hierarchy[0, i, 3] |
| 筛选后的轮廓 | if cv2.contourArea(contour) > threshold: |
for cnt in contours: if area > 1000: ... |
| 轮廓上的点 | for point in contour: |
for pt in cnt: x, y = pt[0]; cv2.circle(img, (x,y), 1, color) |
选择哪种遍历方式取决于你的具体任务,对于大多数情况,for contour in contours: 和结合 enumerate 的方法已经足够强大和灵活。
