news 2026/4/15 18:18:28

【OpenCV】Python图像处理之查找并绘制轮廓

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【OpenCV】Python图像处理之查找并绘制轮廓

本文旨在帮助掌握 OpenCV-Python 中图像轮廓的查找与绘制方法,轮廓是图像中连续的、具有相同像素值的边界曲线,是图像分割、目标检测、形状分析的核心工具。OpenCV 中通过cv2.findContours()查找轮廓,cv2.drawContours()绘制轮廓,核心流程是图像预处理(二值化)→ 查找轮廓 → 绘制轮廓,下面会从原理、完整流程、API 详解、实战示例和高级应用逐步讲解,内容贴合实际开发需求。

一、轮廓的核心概念

  1. 轮廓是二值图像中白色前景的边界(黑色为背景),因此查找轮廓前必须将图像转为二值图(灰度→阈值分割 / 边缘检测);
  2. 轮廓与边缘的区别:边缘是离散的像素点,轮廓是连续的闭合曲线,且轮廓只针对前景区域;
  3. 轮廓的层级:图像中轮廓可能存在嵌套关系(如大矩形内有小矩形),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:绘制当前 + 子轮廓

三、查找并绘制轮廓的标准流程

所有轮廓操作的基础是高质量的二值图,预处理不到位会导致轮廓检测混乱,标准步骤如下:

  1. 读取图像,转为灰度图(cv2.cvtColor());
  2. 图像预处理(可选):去噪(cv2.GaussianBlur()),避免噪声被检测为轮廓;
  3. 二值化(cv2.threshold()/cv2.adaptiveThreshold()):将图像转为黑白,突出前景;
  4. (可选)形态学运算(cv2.dilate()/cv2.erode()):膨胀填充前景孔洞,腐蚀去除微小噪声;
  5. 查找轮廓(cv2.findContours()):复制二值图避免被修改;
  6. 绘制轮廓(cv2.drawContours()):用彩色图绘制,方便可视化;
  7. 显示 / 保存结果。
基础实现代码(最常用场景:检测外轮廓并绘制)
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)

七、常见问题与解决方案

  1. 检测不到轮廓
    • 二值图前景 / 背景颠倒:改用cv2.THRESH_BINARY_INV反向二值化;
    • 图像有噪声:增加高斯去噪或形态学运算;
    • 轮廓是黑色:二值图必须白色为前景,黑色为背景。
  2. 检测到大量冗余小轮廓:按轮廓面积 / 周长筛选,或增大形态学腐蚀的核尺寸。
  3. 轮廓不闭合:用形态学闭运算填充轮廓间隙,或调整二值化阈值。
  4. OpenCV3 和 OpenCV4 的 API 差异
    • OpenCV3:image, contours, hierarchy = cv2.findContours(...)(多返回一个修改后的图像);
    • OpenCV4+:contours, hierarchy = cv2.findContours(...)(推荐,简化返回值)。

八、完整综合示例(轮廓检测 + 筛选 + 形状识别 + 绘制)

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 查找并绘制轮廓的核心要点:

  1. 预处理是基础:灰度→去噪→二值化→(形态学优化),确保二值图白色前景、黑色背景,无噪声和孔洞;
  2. API 核心参数
    • 查找轮廓:cv2.RETR_EXTERNAL(外轮廓)+cv2.CHAIN_APPROX_SIMPLE(压缩点)是最常用组合;
    • 绘制轮廓:contourIdx=-1绘所有轮廓,thickness=-1填充内部;
  3. 实用技巧
    • cv2.contourArea()/cv2.arcLength()筛选有效轮廓,排除噪声;
    • cv2.boundingRect()/cv2.minEnclosingCircle()实现目标定位;
    • cv2.approxPolyDP()做轮廓逼近,实现形状识别;
  4. 层级轮廓cv2.RETR_TREE检索所有嵌套轮廓,通过hierarchy区分内外轮廓。

轮廓操作是 OpenCV 图像处理的核心技能,广泛应用于目标检测、图像分割、工业检测(如瑕疵识别)、形状分析等场景,掌握上述方法即可应对绝大多数实际开发需求。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/14 7:41:23

基于单片机的水缸加热温控控制系统设计

一、系统整体设计方案 本系统以 STC89C52RC 单片机为控制核心&#xff0c;聚焦水产养殖、家庭储水加热等场景的水缸水温管控需求&#xff0c;可实现水温实时采集、目标温度设定、自动加热调节、超温保护及状态反馈功能&#xff0c;兼顾控温精度与使用安全性&#xff0c;为水缸水…

作者头像 李华
网站建设 2026/4/10 21:26:43

铝板加热件工艺设计

摘 要 传送带是生产生活中不可缺少的&#xff0c;它可使我们的工作提高很多效率&#xff0c;但是传送带损坏会给生产带来很多麻烦&#xff0c;但是传送带的损坏又是不可避免的&#xff0c;所以我们需要快速修理传送带的机器——皮带硫化机。 铝板加热件是皮带硫化机中的加热零…

作者头像 李华
网站建设 2026/4/11 9:59:50

基于 8086 双机通信数据互发仿真设计

一、数据互发核心目标与场景设定 在双机通信系统中&#xff0c;数据互发是实现信息交互的核心功能&#xff0c;直接影响系统协同工作的效率与可靠性。基于 8086 的双机串行口通信系统&#xff0c;需确保两台主机能实时、准确地双向传输数据&#xff0c;涵盖单字符即时互发和多字…

作者头像 李华
网站建设 2026/4/3 18:23:19

你的内测用户,正在被糟糕的分发流程劝退

我们痴迷于谈论“用户体验”&#xff08;UX&#xff09;&#xff0c;为App里的每一个按钮、每一个交互细节反复打磨。但我们似乎常常忽略了另一群人的体验——那些在我们产品正式上线前&#xff0c;就愿意投入时间、陪我们一起成长的内-测用户。他们可能是你的朋友、同事&#…

作者头像 李华
网站建设 2026/4/11 15:27:42

Java源码:无人共享健身房物联网方案

以下是一个基于Java的无人共享健身房物联网方案源码级实现框架&#xff0c;包含架构设计、核心模块、技术细节及优化策略&#xff1a; 一、系统架构设计 分层架构&#xff1a;采用Spring Cloud服务架构&#xff0c;实现高可用性和可扩展性。 表现层&#xff1a;使用UniApp实现…

作者头像 李华
网站建设 2026/4/13 8:33:15

Java源码:打造无人共享健身房物联网

以下是一套基于Java的无人共享健身房物联网系统源码实现方案&#xff0c;涵盖系统架构、核心模块、技术细节及优化策略&#xff1a;一、系统架构设计采用分层架构&#xff0c;基于Spring Cloud构建微服务系统&#xff0c;实现高可用性和可扩展性。系统主要分为以下几个层次&…

作者头像 李华