什么是轮廓(Contour)?
轮廓是连续边界点的集合,描述了图像中某一连通区域(对象)的形状。
特点:
- 是根据二值图像得到的边界点序列。
- 轮廓本质上是一个 N×1×2 的点集合数组。
- 高度依赖于图像阈值化的质量。
- 常用于物体识别、测量、定位等任务。
总结一句话:
轮廓 = 物体边缘的有序点集。
轮廓查找的前提:使用二值图像
OpenCV 的轮廓检测基于 Canny 边缘 或 二值化结果(Binary Image)。
最常见的操作流程:
rust
原图 -> 灰度图 -> 二值化/边缘检测 -> 轮廓查找
示例预处理:
python
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
或 使用 Canny:
python
edges = cv2.Canny(gray, 100, 200)
建议:如果目标形状明确,二值化更稳定;边缘检测适合细节复杂图像。
轮廓查找的原理
OpenCV 轮廓查找主要基于 边界跟踪(Boundary Following / Border Following) ,这是经典图像处理中的一类算法。
其核心思想是:
从二值图像中定位非零像素的连通区域,并跟随其边界,记录下边界点序列,形成轮廓(Contour)。
要理解轮廓查找,必须先理解三个关键基础:
- 二值图像
- 连通域(Connected Components)
- 边界跟踪(Border Following / Suzuki--Abe 算法)
轮廓查找为什么必须使用二值图?
因为轮廓检测的本质是:
找到黑(0) 与 白(255) 之间的界线。
只有在二值图中,像素有明确的"前景/背景"区分。
OpenCV 把:
- 非零像素视为前景(白)
- 零像素视为背景(黑)
因此一定要先做:
- 二值化 threshold()
- 或边缘检测 Canny()
这就是:
原图 → 灰度 → 二值 → 查找轮廓
连通域(Connected Components)
轮廓本质是在连通区域的边界上采样。
OpenCV 使用 8 邻域连通(默认):
↖ ↑ ↗
← P →
↙ ↓ ↘
只要前景像素是连通的,就认为是同一个区域(对象)。
轮廓查找核心算法:Suzuki--Abe 算法(1985)
OpenCV 的 findContours() 就是基于此经典论文:
Topological Structural Analysis of Digitized Binary Images by Border Following 作者:Suzuki & Abe
这是迄今最广泛使用的"边界跟踪算法"。
Suzuki--Abe 算法步骤
Step 1:扫描整个二值图像
从左到右,从上到下,逐像素扫描。
遇到以下情况时触发"边界跟踪":
✔ 情况 1:从背景 0 → 前景 1(Left to Right)
说明这是一个新轮廓(外部轮廓)。
✔ 情况 2:从前景 1 → 背景 0
说明进入一个孔洞的边界(内部轮廓)。
因此算法可自动识别:
- 外轮廓(object)
- 内轮廓(hole)
Step 2:开始边界跟踪(Border Following)
一旦检测到边界起点,算法开始"绕着边走"。
边界跟踪过程:
- 在当前位置找到一个外侧邻接像素
- 顺时针或逆时针检查 8 邻域
- 找到下一个边界点
- 继续移动到新的点
- 直到回到起点则结束
这些点序列被记录下来,形成:
(contour) = [ (x1,y1), (x2,y2), ... , (xn,yn) ]
Step 3:生成层级关系(Hierarchy)
Suzuki--Abe 原理自动建立轮廓树:
- 谁是父轮廓(包含别人)
- 谁是子轮廓(在内部)
- 谁是同一级的兄弟轮廓
OpenCV 会生成一个:
N x 4 的 hierarchy 数组
分别表示:
| index | 含义 |
|---|---|
| 0 | Next 同一级的下一个轮廓 |
| 1 | Previous 上一个 |
| 2 | First Child 子轮廓 |
| 3 | Parent 父轮廓 |
这就是为什么 cv2.RETR_TREE 能返回完整层级结构。
OpenCV 中的 findContours() 函数解析
OpenCV 查找轮廓的核心函数:
python
contours, hierarchy = cv2.findContours(image, mode, method)
各参数含义如下:
参数:image
-
必须是 二值图像
-
非零像素被认为是轮廓区域
-
Zero-1-image 被修改,因此常使用拷贝:
contours, hierarchy = cv2.findContours(thresh.copy(), ...)
参数:mode(轮廓检索模式)
| 模式 | 作用 |
|---|---|
| cv2.RETR_EXTERNAL | 仅检索最外层轮廓(用于计数) |
| cv2.RETR_LIST | 检索所有轮廓,不建立层级关系 |
| cv2.RETR_TREE | 建立完整层级体系(父轮廓/子轮廓) |
| cv2.RETR_CCOMP | 建立两层轮廓结构 |
最常用:
- RETR_EXTERNAL 只要最外层轮廓,速度快。
- RETR_TREE 建层级结构,适合复杂对象。
参数:method(轮廓逼近方法)
| 方法 | 说明 |
|---|---|
| cv2.CHAIN_APPROX_NONE | 保存所有点,不简化 |
| cv2.CHAIN_APPROX_SIMPLE | 压缩水平/垂直线段,只保留拐点(常用) |
实际工作中几乎 99% 用:
cv2.CHAIN_APPROX_SIMPLE
可将轮廓数据减少到拐点,提高效率。
示例
python
import cv2
import numpy as np
# --------------------------------------------------------
# 自定义生成图像(取代读取 test.png)
# --------------------------------------------------------
img = np.zeros((400, 600, 3), dtype=np.uint8)
# 白色矩形
cv2.rectangle(img, (50, 50), (200, 200), (255, 255, 255), -1)
# 白色圆形
cv2.circle(img, (350, 150), 80, (255, 255, 255), -1)
# 白色三角形
pts = np.array([[300,300], [400,350], [350,250]], np.int32)
cv2.fillPoly(img, [pts], (255,255,255))
# --------------------------------------------------------
# 转灰度图
# --------------------------------------------------------
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 查找轮廓
contours, hierarchy = cv2.findContours(
thresh,
cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE
)
# 绘制轮廓(绿色)
cv2.drawContours(img, contours, -1, (0, 255, 0), 3)
# --------------------------------------------------------
# 显示结果
# --------------------------------------------------------
cv2.imshow("Contours", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
执行效果:
