本文旨在帮助掌握 OpenCV-Python 中图像轮廓的查找与绘制 方法,轮廓是图像中连续的、具有相同像素值的边界曲线,是图像分割、目标检测、形状分析的核心工具。OpenCV 中通过cv2.findContours()查找轮廓,cv2.drawContours()绘制轮廓,核心流程是图像预处理(二值化)→ 查找轮廓 → 绘制轮廓,下面会从原理、完整流程、API 详解、实战示例和高级应用逐步讲解,内容贴合实际开发需求。
一、轮廓的核心概念
- 轮廓是二值图像中白色前景的边界(黑色为背景),因此查找轮廓前必须将图像转为二值图(灰度→阈值分割 / 边缘检测);
- 轮廓与边缘的区别:边缘是离散的像素点,轮廓是连续的闭合曲线,且轮廓只针对前景区域;
- 轮廓的层级:图像中轮廓可能存在嵌套关系(如大矩形内有小矩形),OpenCV 会记录轮廓的父子层级关系,便于筛选内外轮廓。
二、核心 API 详解
1. 查找轮廓:cv2.findContours()
函数原型(OpenCV4 + 版本,返回值简化为 2 个,轮廓和层级):
python
contours, hierarchy = cv2.findContours(image, mode, method)
关键参数:
| 参数 | 作用 | 常用值 |
|---|---|---|
image |
输入图像 | 必须是二值图(uint8 类型,黑白),建议对原二值图做复制(函数会修改原图) |
mode |
轮廓检索模式(控制查找的轮廓范围和层级) | cv2.RETR_EXTERNAL:仅检索最外层轮廓(最常用)cv2.RETR_LIST:检索所有轮廓,不建立层级cv2.RETR_CCOMP:检索所有轮廓,建立两层层级(外 / 内)cv2.RETR_TREE:检索所有轮廓,建立完整的树形层级 |
method |
轮廓逼近方法(压缩轮廓点,减少冗余) | cv2.CHAIN_APPROX_NONE:保存所有轮廓点(精度最高,数据量大)cv2.CHAIN_APPROX_SIMPLE:压缩水平 / 垂直 / 对角线,仅保存端点(如矩形只存 4 个角点,最常用) |
返回值:
contours:轮廓列表,每个轮廓是N×1×2的 NumPy 数组,存储轮廓的 (x,y) 坐标点;hierarchy:层级数组,形状为1×N×4 ,每个轮廓对应 4 个值[next, prev, child, parent],分别表示「下一个轮廓、上一个轮廓、子轮廓、父轮廓」,无对应轮廓时为 - 1。
2. 绘制轮廓:cv2.drawContours()
函数原型:
python
img = cv2.drawContours(image, contours, contourIdx, color, thickness=None, lineType=None, hierarchy=None, maxLevel=None, offset=None)
关键参数:
| 参数 | 作用 | 常用值 |
|---|---|---|
image |
绘制的目标图像 | 建议用彩色图(方便看轮廓),直接修改原图,可提前复制 |
contours |
待绘制的轮廓列表 | 即cv2.findContours()的返回值 |
contourIdx |
绘制的轮廓索引 | -1:绘制所有轮廓具体数字(如 0、1):绘制指定索引的轮廓 |
color |
轮廓颜色 | BGR 格式,如(0,255,0)(绿色)、(0,0,255)(红色) |
thickness |
轮廓线宽 | 正数:线宽(如 2)-1:填充轮廓内部(绘制实心形状) |
maxLevel |
绘制的轮廓层级 | 配合hierarchy使用,0:仅绘制当前轮廓,1:绘制当前 + 子轮廓 |
三、查找并绘制轮廓的标准流程
所有轮廓操作的基础是高质量的二值图,预处理不到位会导致轮廓检测混乱,标准步骤如下:
- 读取图像,转为灰度图(
cv2.cvtColor()); - 图像预处理(可选):去噪(
cv2.GaussianBlur()),避免噪声被检测为轮廓; - 二值化(
cv2.threshold()/cv2.adaptiveThreshold()):将图像转为黑白,突出前景; - (可选)形态学运算(
cv2.dilate()/cv2.erode()):膨胀填充前景孔洞,腐蚀去除微小噪声; - 查找轮廓(
cv2.findContours()):复制二值图避免被修改; - 绘制轮廓(
cv2.drawContours()):用彩色图绘制,方便可视化; - 显示 / 保存结果。
基础实现代码(最常用场景:检测外轮廓并绘制)
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 1. 读取图像并转灰度
img = cv2.imread('shape.jpg') # 读取彩色图,用于后续绘制轮廓
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow('Gray', gray)
# 2. 预处理:高斯去噪 + 二值化(关键:让前景和背景分离)
blur = cv2.GaussianBlur(gray, (3, 3), 0) # 去噪,避免噪声干扰轮廓检测
# 二值化:大于127设为255(白,前景),小于设为0(黑,背景),THRESH_BINARY_INV为反向
ret, binary = cv2.threshold(blur, 127, 255, cv2.THRESH_BINARY)
cv2.imshow('Binary', binary)
# 3. 查找轮廓:仅找外轮廓,压缩轮廓点(最常用参数组合)
# 注意:传入binary的复制,避免函数修改原二值图
contours, hierarchy = cv2.findContours(binary.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print(f"检测到的轮廓数量:{len(contours)}")
# 4. 绘制轮廓:在彩色原图的复制上绘制,绿色,线宽2,绘制所有轮廓
img_contour = img.copy()
cv2.drawContours(img_contour, contours, -1, (0, 255, 0), 2)
# 5. 显示结果
cv2.imshow('Contour', img_contour)
cv2.waitKey(0)
cv2.destroyAllWindows()
四、关键优化:形态学运算优化二值图
如果二值图存在前景孔洞 或微小噪声点 ,会导致检测到多余轮廓或轮廓不闭合,此时用形态学膨胀 / 闭运算优化:
python
# 二值化后添加形态学运算
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
# 闭运算(膨胀→腐蚀):填充前景内部的小孔洞
binary_close = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
# 膨胀:让前景轮廓更连续(可选,根据图像调整)
# binary_dilate = cv2.dilate(binary_close, kernel, iterations=1)
# 再查找轮廓
contours, hierarchy = cv2.findContours(binary_close.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
五、实战常见需求
1. 绘制单个轮廓 & 填充轮廓内部
python
img_contour = img.copy()
# 绘制第0个轮廓:红色,线宽3
cv2.drawContours(img_contour, contours, 0, (0, 0, 255), 3)
# 绘制第1个轮廓:蓝色,填充内部(thickness=-1)
cv2.drawContours(img_contour, contours, 1, (255, 0, 0), -1)
cv2.imshow('Single & Filled Contour', img_contour)
2. 筛选轮廓(按面积 / 周长)
实际开发中,噪声会被检测为小轮廓,可通过轮廓面积 (cv2.contourArea())或轮廓周长 (cv2.arcLength())筛选有效轮廓:
python
img_filter = img.copy()
valid_contours = [] # 存储有效轮廓
for cnt in contours:
# 计算轮廓面积
area = cv2.contourArea(cnt)
# 计算轮廓周长(True表示轮廓闭合)
perimeter = cv2.arcLength(cnt, True)
# 筛选面积大于100的轮廓(阈值根据图像调整)
if area > 100:
valid_contours.append(cnt)
# 在轮廓旁绘制面积值
# 获取轮廓的外接矩形,确定文字位置
x, y, w, h = cv2.boundingRect(cnt)
cv2.putText(img_filter, f"Area:{int(area)}", (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1)
# 绘制筛选后的有效轮廓
cv2.drawContours(img_filter, valid_contours, -1, (0,255,0), 2)
print(f"筛选后的有效轮廓数量:{len(valid_contours)}")
cv2.imshow('Filtered Contour', img_filter)
3. 绘制轮廓的外接矩形 / 外接圆(目标定位)
轮廓的外接矩形 / 外接圆是目标定位的常用手段,结合cv2.boundingRect()(外接矩形)、cv2.minEnclosingCircle()(最小外接圆)实现:
python
img_outer = img.copy()
for cnt in valid_contours:
# 绘制外接矩形:绿色
x, y, w, h = cv2.boundingRect(cnt)
cv2.rectangle(img_outer, (x,y), (x+w,y+h), (0,255,0), 2)
# 绘制最小外接圆:红色
(x_c, y_c), r = cv2.minEnclosingCircle(cnt)
center = (int(x_c), int(y_c))
radius = int(r)
cv2.circle(img_outer, center, radius, (0,0,255), 2)
cv2.imshow('Outer Shape', img_outer)
4. 检测嵌套轮廓(绘制内外轮廓)
使用cv2.RETR_TREE检索所有层级轮廓,结合hierarchy区分内外轮廓(父轮廓parent=-1,子轮廓parent≥0):
python
# 重新查找所有轮廓,建立树形层级
contours, hierarchy = cv2.findContours(binary_close.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
img_tree = img.copy()
for i, cnt in enumerate(contours):
# 父轮廓索引:hierarchy[0][i][3]
parent = hierarchy[0][i][3]
if parent == -1:
# 无父轮廓 → 外层轮廓:绿色
cv2.drawContours(img_tree, contours, i, (0,255,0), 2)
else:
# 有父轮廓 → 内层轮廓:红色
cv2.drawContours(img_tree, contours, i, (0,0,255), 2)
cv2.imshow('Inner & Outer Contour', img_tree)
六、高级应用:轮廓的形状识别
通过轮廓的逼近多边形 (cv2.approxPolyDP())判断形状(如三角形、矩形、圆形),核心原理是用最少的点逼近轮廓,点的数量对应形状的边数:
python
img_shape = img.copy()
for cnt in valid_contours:
perimeter = cv2.arcLength(cnt, True)
# 轮廓逼近:epsilon为周长的0.04倍(阈值越小,逼近越接近原轮廓)
epsilon = 0.04 * perimeter
approx = cv2.approxPolyDP(cnt, epsilon, True)
# 获取逼近后的顶点数量
vertex_num = len(approx)
# 判断形状
if vertex_num == 3:
shape = "Triangle"
elif vertex_num == 4:
# 4个顶点:判断是否为正方形(长宽比接近1)
x, y, w, h = cv2.boundingRect(approx)
ratio = w / float(h)
shape = "Square" if 0.95 <= ratio <= 1.05 else "Rectangle"
elif vertex_num > 6:
shape = "Circle"
else:
shape = "Other"
# 绘制逼近多边形+形状文字
cv2.drawContours(img_shape, [approx], 0, (0,255,0), 2)
cv2.putText(img_shape, shape, (cnt[:,0,0].min(), cnt[:,0,1].min()-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,0,0), 1)
cv2.imshow('Shape Recognition', img_shape)
七、常见问题与解决方案
- 检测不到轮廓 :
- 二值图前景 / 背景颠倒:改用
cv2.THRESH_BINARY_INV反向二值化; - 图像有噪声:增加高斯去噪或形态学运算;
- 轮廓是黑色:二值图必须白色为前景,黑色为背景。
- 二值图前景 / 背景颠倒:改用
- 检测到大量冗余小轮廓 :按轮廓面积 / 周长筛选,或增大形态学腐蚀的核尺寸。
- 轮廓不闭合 :用形态学闭运算填充轮廓间隙,或调整二值化阈值。
- OpenCV3 和 OpenCV4 的 API 差异 :
- OpenCV3:
image, contours, hierarchy = cv2.findContours(...)(多返回一个修改后的图像); - OpenCV4+:
contours, hierarchy = cv2.findContours(...)(推荐,简化返回值)。
- OpenCV3:
八、完整综合示例(轮廓检测 + 筛选 + 形状识别 + 绘制)
python
import cv2
import numpy as np
# 1. 读取图像并预处理
img = cv2.imread('shape_mix.jpg')
if img is None:
raise ValueError("图像读取失败,请检查路径!")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3, 3), 0)
ret, binary = cv2.threshold(blur, 127, 255, cv2.THRESH_BINARY)
# 2. 形态学优化二值图
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
binary_close = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
# 3. 查找外轮廓
contours, hierarchy = cv2.findContours(binary_close.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 4. 筛选有效轮廓+形状识别+绘制
img_result = img.copy()
valid_contours = []
for cnt in contours:
area = cv2.contourArea(cnt)
if area > 50: # 筛选面积大于50的轮廓
valid_contours.append(cnt)
# 轮廓逼近
perimeter = cv2.arcLength(cnt, True)
epsilon = 0.04 * perimeter
approx = cv2.approxPolyDP(cnt, epsilon, True)
vertex_num = len(approx)
# 判断形状
if vertex_num == 3:
shape = "Triangle"
color = (0, 255, 0)
elif vertex_num == 4:
x, y, w, h = cv2.boundingRect(approx)
ratio = w / float(h)
shape = "Square" if 0.95 <= ratio <= 1.05 else "Rectangle"
color = (0, 0, 255)
elif vertex_num > 6:
shape = "Circle"
color = (255, 0, 0)
else:
shape = "Other"
color = (128, 128, 0)
# 绘制轮廓+外接矩形+形状文字
cv2.drawContours(img_result, [approx], 0, color, 2)
x, y, w, h = cv2.boundingRect(cnt)
cv2.rectangle(img_result, (x, y), (x+w, y+h), color, 1)
cv2.putText(img_result, f"{shape}({int(area)})", (x, y-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.4, color, 1)
# 5. 显示所有结果
cv2.imshow('Original', img)
cv2.imshow('Binary', binary_close)
cv2.imshow('Contour & Shape Recognition', img_result)
print(f"原始轮廓数:{len(contours)},有效轮廓数:{len(valid_contours)}")
cv2.waitKey(0)
cv2.destroyAllWindows()
# 保存结果
cv2.imwrite('contour_result.jpg', img_result)
总结
OpenCV-Python 查找并绘制轮廓的核心要点:
- 预处理是基础 :灰度→去噪→二值化→(形态学优化),确保二值图白色前景、黑色背景,无噪声和孔洞;
- API 核心参数 :
- 查找轮廓:
cv2.RETR_EXTERNAL(外轮廓)+cv2.CHAIN_APPROX_SIMPLE(压缩点)是最常用组合; - 绘制轮廓:
contourIdx=-1绘所有轮廓,thickness=-1填充内部;
- 查找轮廓:
- 实用技巧 :
- 用
cv2.contourArea()/cv2.arcLength()筛选有效轮廓,排除噪声; - 用
cv2.boundingRect()/cv2.minEnclosingCircle()实现目标定位; - 用
cv2.approxPolyDP()做轮廓逼近,实现形状识别;
- 用
- 层级轮廓 :
cv2.RETR_TREE检索所有嵌套轮廓,通过hierarchy区分内外轮廓。
轮廓操作是 OpenCV 图像处理的核心技能,广泛应用于目标检测、图像分割、工业检测(如瑕疵识别)、形状分析等场景,掌握上述方法即可应对绝大多数实际开发需求。