本文旨在帮助掌握 OpenCV-Python 中图像轮廓的查找与绘制方法,轮廓是图像中连续的、具有相同像素值的边界曲线,是图像分割、目标检测、形状分析的核心工具。OpenCV 中通过cv2.findContours()查找轮廓,cv2.drawContours()绘制轮廓,核心流程是图像预处理(二值化)→ 查找轮廓 → 绘制轮廓,下面会从原理、完整流程、API 详解、实战示例和高级应用逐步讲解,内容贴合实际开发需求。
一、轮廓的核心概念
- 轮廓是二值图像中白色前景的边界(黑色为背景),因此查找轮廓前必须将图像转为二值图(灰度→阈值分割 / 边缘检测);
- 轮廓与边缘的区别:边缘是离散的像素点,轮廓是连续的闭合曲线,且轮廓只针对前景区域;
- 轮廓的层级:图像中轮廓可能存在嵌套关系(如大矩形内有小矩形),OpenCV 会记录轮廓的父子层级关系,便于筛选内外轮廓。
二、核心 API 详解
1. 查找轮廓:cv2.findContours()
函数原型(OpenCV4 + 版本,返回值简化为 2 个,轮廓和层级):
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()
函数原型:
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()):用彩色图绘制,方便可视化; - 显示 / 保存结果。
基础实现代码(最常用场景:检测外轮廓并绘制)
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()四、关键优化:形态学运算优化二值图
如果二值图存在前景孔洞或微小噪声点,会导致检测到多余轮廓或轮廓不闭合,此时用形态学膨胀 / 闭运算优化:
# 二值化后添加形态学运算 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. 绘制单个轮廓 & 填充轮廓内部
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())筛选有效轮廓:
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()(最小外接圆)实现:
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):
# 重新查找所有轮廓,建立树形层级 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())判断形状(如三角形、矩形、圆形),核心原理是用最少的点逼近轮廓,点的数量对应形状的边数:
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:
八、完整综合示例(轮廓检测 + 筛选 + 形状识别 + 绘制)
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 图像处理的核心技能,广泛应用于目标检测、图像分割、工业检测(如瑕疵识别)、形状分析等场景,掌握上述方法即可应对绝大多数实际开发需求。