当前位置: 首页 > news >正文

OpenCV 图像轮廓检测与相关技术全解析

目录

一、图像轮廓检测基础

1. 什么是图像轮廓检测?

2. 轮廓检测的核心作用

二、轮廓检测与绘制(实战优化)

1. 轮廓检测的关键步骤

键参数说明:

2. 轮廓绘制

三、轮廓特征提取(实战与应用)

1. 轮廓的周长

2. 轮廓的面积

3. 轮廓的外接圆

4. 轮廓的外接矩形

(1)轴对齐外接矩形(cv2.boundingRect)

(2)旋转外接矩形(cv2.minAreaRect)

四、轮廓近似(简化轮廓)

1. 什么是轮廓近似?

2. 轮廓近似的作用

3. 代码实现(优化版)

关键注意:

五、模板匹配(基于轮廓的目标定位)

1. 什么是模板匹配?

2. 模板匹配的作用

3. 代码实现

图像轮廓检测是计算机视觉中提取物体形态特征的核心技术,结合模板匹配等延伸方法,可实现目标识别、形状分析等关键应用。

一、图像轮廓检测基础

1. 什么是图像轮廓检测?

图像轮廓是图像中灰度值发生突变的连续像素点集合,本质是物体边界的抽象表示(区别于 “边缘”:边缘是孤立像素点,轮廓是连续边缘构成的闭合曲线)。
通过轮廓检测,可将图像中的物体从背景中 “分离”,用数学坐标(如(x,y)点集)描述物体形状,为后续分析提供结构化数据。

2. 轮廓检测的核心作用

轮廓检测是计算机视觉任务的 “桥梁”,连接原始图像与高层分析,主要应用场景包括:

  • 图像分割:分割图像中的不同物体(如从水果篮照片中分离苹果、香蕉)。
  • 形状分析:判断物体形状(如区分圆形瓶盖与方形盒子)。
  • 目标识别:结合轮廓特征识别特定物体(如工业质检中识别合格零件)。
  • 目标跟踪:在视频中跟踪物体运动(如监控中跟踪行人轨迹)。
  • 尺寸测量:通过轮廓计算物体实际尺寸(如医学影像中测量细胞直径)。

二、轮廓检测与绘制(实战优化)

轮廓检测需遵循 “预处理→找轮廓→绘制轮廓” 的流程,你提供的代码存在版本兼容性问题(如cv2.findContours返回值在 OpenCV 3.x 与 4.x 不同),以下为适配 OpenCV 4.x 的完整代码及关键说明:

1. 轮廓检测的关键步骤

轮廓检测对图像质量敏感,需先通过 “灰度化→二值化” 简化图像(消除颜色干扰、突出物体边界):


import cv2
import numpy as np# 1. 读取图像(建议用绝对路径,避免路径错误)
img = cv2.imread("phone.png")
if img is None:print("Error: 未找到图像文件,请检查路径!")exit()# 2. 预处理:灰度化(消除RGB通道干扰)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 3. 预处理:二值化(将灰度图转为黑白二值图,突出轮廓)
# 阈值120:灰度值>120的像素设为255(白),否则设为0(黑)
# cv2.THRESH_BINARY_INV:若物体是黑色、背景是白色,需用反转二值化(避免轮廓检测背景)
ret, binary = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY)# 4. 查找轮廓(OpenCV 4.x返回值:contours=轮廓列表,hierarchy=轮廓层级)
_,contours, hierarchy = cv2.findContours(image=binary,mode=cv2.RETR_TREE,  # 提取所有轮廓并保留层级关系(如嵌套轮廓)method=cv2.CHAIN_APPROX_NONE  # 存储轮廓所有像素点(CHAIN_APPROX_SIMPLE可压缩冗余点)
)# 查看轮廓信息:contours是列表,每个元素是一个轮廓(ndarray,shape=(N,1,2),N=像素点数)
print(f"检测到的轮廓数量:{len(contours)}")
print(f"第一个轮廓的像素点数:{len(contours[0])}")
print(f"轮廓层级信息(父子轮廓关系):\n{hierarchy}")

               

键参数说明:
  • mode(轮廓提取模式):
    • cv2.RETR_EXTERNAL:只提取最外层轮廓(忽略嵌套轮廓,如只提取手机外壳,不提取屏幕内的图标)。
    • cv2.RETR_LIST:提取所有轮廓,但不保留层级关系(适合无需区分父子轮廓的场景)。
  • method(轮廓逼近方法):
    • cv2.CHAIN_APPROX_SIMPLE:压缩轮廓(如矩形只保留 4 个顶点,减少内存占用),多数场景优先使用。
    • cv2.CHAIN_APPROX_NONE:保留轮廓所有像素点(适合需要精细轮廓的场景,如手写笔迹分析)。

2. 轮廓绘制

通过cv2.drawContours将检测到的轮廓叠加在原图上,便于可视化验证:

# 1. 创建原图副本(避免直接修改原图)
img_copy = img.copy()# 2. 绘制轮廓
img_with_contours = cv2.drawContours(image=img_copy, contours=contours,  # 要绘制的轮廓列表contourIdx=-1,  # 绘制所有轮廓(若为0则只绘制第一个轮廓,1则绘制第二个,以此类推)color=(255, 0, 0),  # 轮廓颜色(BGR格式,(255,0,0)=蓝色)thickness=3  # 轮廓线宽度(若为-1则填充轮廓内部)
)# 3. 显示结果
cv2.imshow("Binary Image", binary)  # 显示二值图
cv2.imshow("Image with Contours", img_with_contours)  # 显示带轮廓的原图
cv2.waitKey(0)  # 等待按键(0=无限等待,按任意键关闭窗口)
cv2.destroyAllWindows()  # 关闭所有窗口

三、轮廓特征提取(实战与应用)

轮廓特征是描述物体形态的关键数据,常用特征包括周长、面积、外接圆 / 矩形等,可用于筛选目标、判断形状。

1. 轮廓的周长

通过cv2.arcLength计算轮廓的周长(闭合轮廓需指定closed=True):

# 计算第一个轮廓的周长(假设是闭合轮廓)
perimeter = cv2.arcLength(curve=contours[0], closed=True)
print(f"第一个轮廓的周长:{perimeter:.2f} 像素")

                              

2. 轮廓的面积

通过cv2.contourArea计算轮廓包围的面积(单位:像素 ²),可用于筛选目标(如过滤小噪声轮廓):

# 1. 计算单个轮廓的面积
area_0 = cv2.contourArea(contour=contours[0], oriented=False)  # oriented=False:返回面积绝对值
print(f"第一个轮廓的面积:{area_0:.1f} 像素²")# 2. 应用:筛选面积>10000像素²的轮廓(过滤小噪声,如图片中的灰尘点)
large_contours = []
for cnt in contours:area = cv2.contourArea(cnt)if area > 10000:large_contours.append(cnt)# 绘制筛选后的轮廓(红色)
img_large_cnt = img.copy()
img_large_cnt = cv2.drawContours(img_large_cnt, large_contours, -1, (0, 0, 255), 3)
cv2.imshow("Large Contours", img_large_cnt)
cv2.waitKey(0)
cv2.destroyAllWindows()

输出结果如下:

                    

3. 轮廓的外接圆

通过cv2.minEnclosingCircle找到轮廓的最小外接圆(能完全包围轮廓的最小圆),可用于判断物体是否接近圆形:

# 选择一个轮廓(如第7个轮廓,对应你教程中的“笔杆”)
cnt = contours[6]# 计算最小外接圆:返回(圆心(x,y),半径r)
(x, y), radius = cv2.minEnclosingCircle(cnt)
center = (int(x), int(y))  # 圆心坐标需转为整数(像素坐标为整数)
radius = int(radius)  # 半径转为整数# 绘制外接圆(蓝色,线宽2)
img_circle = img.copy()
img_circle = cv2.circle(img_circle, center, radius, (255, 0, 0), 2)
# 绘制圆心(红色圆点,半径5,填充)
img_circle = cv2.circle(img_circle, center, 5, (0, 0, 255), -1)cv2.imshow("Contour with Min Circle", img_circle)
cv2.waitKey(0)
cv2.destroyAllWindows()

4. 轮廓的外接矩形

外接矩形分两种:轴对齐外接矩形( sides 平行于图像坐标轴)和旋转外接矩形(贴合轮廓的最小矩形,可旋转)。

(1)轴对齐外接矩形(cv2.boundingRect

适合快速定位物体,计算简单:

# 计算轴对齐外接矩形:返回(左上角x, 左上角y, 宽度w, 高度h)
x, y, w, h = cv2.boundingRect(cnt)# 绘制外接矩形(绿色,线宽2)
img_rect = img.copy()
img_rect = cv2.rectangle(img_rect, (x, y), (x + w, y + h), (0, 255, 0), 2)cv2.imshow("Axis-Aligned Rect", img_rect)
cv2.waitKey(0)

(2)旋转外接矩形(cv2.minAreaRect

适合不规则物体(如倾斜的书本),更贴合轮廓:

# 计算旋转外接矩形:返回(中心点(x,y), (宽w,高h), 旋转角度θ)
rect = cv2.minAreaRect(cnt)
center = (int(rect[0][0]), int(rect[0][1]))
size = (int(rect[1][0]), int(rect[1][1]))
angle = rect[2]# 绘制旋转矩形(需先将矩形的4个顶点转为整数坐标)
box = cv2.boxPoints(rect)  # 计算矩形的4个顶点(浮点数)
box = np.int0(box)  # 转为整数img_rot_rect = img.copy()
img_rot_rect = cv2.drawContours(img_rot_rect, [box], 0, (0, 255, 255), 2)  # 黄色矩形cv2.imshow("Rotated Rect", img_rot_rect)
cv2.waitKey(0)
cv2.destroyAllWindows()

四、轮廓近似(简化轮廓)

1. 什么是轮廓近似?

轮廓近似是通过减少轮廓像素点数量,用更简单的多边形拟合原始轮廓(如将 “带锯齿的圆形” 拟合为 “光滑圆形”,将 “不规则四边形” 拟合为 “标准矩形”),核心是保留轮廓的 “整体形状”,去除 “细节噪声”。

其原理基于Douglas-Peucker 算法

  1. 连接轮廓的起点 A 和终点 B,得到线段 AB;
  2. 找到轮廓上离 AB 最远的点 C,若 C 到 AB 的距离 > 阈值epsilon,则将 AB 分为 AC、CB,重复步骤 1;
  3. 若所有点到线段的距离≤epsilon,则用线段代替原始轮廓段,完成近似。

2. 轮廓近似的作用

  • 降低计算复杂度:简化后的轮廓像素点减少,后续形状分析(如匹配、分类)速度更快。
  • 抗噪声干扰:去除轮廓上的细小锯齿(如因图像模糊产生的噪声),让轮廓更平滑。
  • 突出形状本质:如将 “手写的不规整正方形” 近似为 “标准正方形”,便于形状识别。

3. 代码实现(优化版)

import cv2
import numpy as np# 读取图像并预处理(以“不规则形状图像lk1.jpg”为例)
img = cv2.imread("lk1.jpg")
img = cv2.resize(img, (500, 600))  # 调整尺寸,便于显示
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY_INV)  # 按需调整二值化模式# 查找轮廓(只提取最外层轮廓,避免嵌套干扰)
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnt = contours[0]  # 选择第一个(也是唯一的外层)轮廓# 1. 计算原始轮廓的周长(用于确定epsilon阈值)
perimeter = cv2.arcLength(cnt, closed=True)# 2. 轮廓近似(关键:epsilon阈值的选择)
# epsilon = 0.01 * perimeter:阈值越小,近似轮廓越接近原始轮廓(细节保留越多)
# epsilon = 0.05 * perimeter:阈值越大,轮廓越简化(可能丢失部分形状信息)
epsilon1 = 0.01 * perimeter  # 精细近似
epsilon2 = 0.05 * perimeter  # 粗略近似approx1 = cv2.approxPolyDP(curve=cnt, epsilon=epsilon1, closed=True)
approx2 = cv2.approxPolyDP(curve=cnt, epsilon=epsilon2, closed=True)# 3. 绘制对比图
img_original = img.copy()
img_approx1 = img.copy()
img_approx2 = img.copy()# 原始轮廓(蓝色)
cv2.drawContours(img_original, [cnt], 0, (255, 0, 0), 2)
# 精细近似轮廓(绿色)
cv2.drawContours(img_approx1, [approx1], 0, (0, 255, 0), 2)
# 粗略近似轮廓(红色)
cv2.drawContours(img_approx2, [approx2], 0, (0, 0, 255), 2)# 显示结果(横向拼接三张图,便于对比)
combined = np.hstack((img_original, img_approx1, img_approx2))
cv2.imshow("Original vs Approx (0.01) vs Approx (0.05)", combined)
cv2.waitKey(0)
cv2.destroyAllWindows()# 查看近似效果:输出轮廓的像素点数(近似后点数减少)
print(f"原始轮廓像素点数:{len(cnt)}")
print(f"精细近似(0.01*perimeter)像素点数:{len(approx1)}")
print(f"粗略近似(0.05*perimeter)像素点数:{len(approx2)}")

关键注意:
  • epsilon阈值无固定标准,需根据实际场景调整:
    • 若需保留细节(如文物轮廓分析),取0.005~0.01 * perimeter
    • 若需快速分类(如区分三角形 / 矩形),取0.02~0.05 * perimeter

五、模板匹配(基于轮廓的目标定位)

1. 什么是模板匹配?

模板匹配是在目标图像中寻找与模板图像(小图)最相似区域的技术,核心是 “滑动窗口”:

  1. 将模板图像(如 “可乐瓶 logo”)作为滑动窗口,在目标图像(如 “超市货架照片”)上逐像素滑动;
  2. 对每个窗口位置,计算 “模板与窗口区域” 的相似度(如平方差、相关系数);
  3. 找到相似度最高的窗口位置,即为模板在目标图像中的匹配区域。

你提供的教程中,模板匹配是基于像素灰度的匹配(cv2.matchTemplate),而基于轮廓的模板匹配(如cv2.matchShapes)更适合 “目标尺度 / 旋转变化” 的场景,以下补充两种方法的实现。

2. 模板匹配的作用

  • 目标定位:在复杂背景中找到特定目标(如在监控画面中定位灭火器)。
  • 批量检测:工业质检中检测零件是否存在(如电路板上的电容是否缺失)。
  • 字符识别:简单 OCR(如识别验证码中的数字)。

3. 代码实现

import cv2kele = cv2.imread('Dog.png')
kele=cv2.resize(kele,(500,500))
moban = cv2.imread('dd.png')
cv2.imshow('kele', kele)
cv2.imshow('moban', moban)
cv2.waitKey(0)h, w = moban.shape[:2]  # 获取模版图片的高宽
res = cv2.matchTemplate(kele, moban, cv2.TM_CCOEFF_NORMED)  # 返回一个矩阵,其中每个元素表示该位置与模板的匹配程度
# cv2.minMaxLoc可以获取矩阵中的最小值和最大值,以及最小值的索引号和最大值的索引号
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)  # 最小值、最大值、最小值位置、最大值位置
top_left = max_loc  # 最大值为匹配到的模板的左上角
bottom_right = (top_left[0] + w, top_left[1] + h)  # 主图片中用模版匹配到的位置
kele_template = cv2.rectangle(kele, top_left, bottom_right, (255, 0, 0), thickness=3)cv2.imshow('kele_template', kele_template)
cv2.waitKey(0)

输出:

左边是原图,中间是匹配使用的模版,右边是模版匹配到的矩形区域

http://www.dtcms.com/a/357326.html

相关文章:

  • 阿瓦隆 A1346 107T 矿机深度评测:性能参数、能效及使用体验解析
  • 面试tips--java--equals() hashCode()
  • 莱特莱德:以“第四代极限分离技术”,赋能生物发酵产业升级
  • 自动驾驶中的传感器技术36——Lidar(11)
  • 可解释人工智能XAI
  • 手写MyBatis第40弹:手写MyBatis框架阶段性总结,你的ORM框架已达生产级雏形
  • 【nvidia-B200】生产报错common.h:14:10: fatal error: mpi.h: No such file or directory
  • (论文速读)RADIOv2.5:聚合式视觉基础模型
  • 美摄科技受邀参加2025中关村论坛年会,以超高清车载影像技术赋能智慧出行新体验!
  • 【报错】RuntimeError: HIP error: invalid device function
  • Python计算点云的均值、方差、标准差、凸点(顶点)、质心和去中心化
  • week5-[二维数组]对角线
  • idea2025.1.5安装+pj
  • 计算机视觉第一课opencv(四)保姆级教学
  • 构建稳定和可扩展云基础设施的首选服务:AWS的EC2实例
  • 【三维渲染技术讨论】Blender输出的三维文件里的透明贴图在Isaac Sim里会丢失, 是什么原因?
  • 2024年09月 Python(四级)真题解析#中国电子学会#全国青少年软件编程等级考试
  • JVM_JMM
  • Java ThreadLocal为什么要用弱引用
  • Vue2 和 Vue3 里的防抖:简单说清楚怎么用
  • 【C语言入门级教学】sizeof和strlen的对⽐
  • 数据存储——数据库
  • 并发编程——07 深入理解AQS之独占锁ReentrantLock源码分析
  • 编程设计模式
  • 【系列02】端侧AI:构建与部署高效的本地化AI模型 第1章:为什么是端侧AI?
  • 【LINUX】常用基本指令(1)
  • go 使用rabbitMQ
  • 神经网络|(十六)概率论基础知识-伽马函数·中
  • Hugging Face入门指南:AI创客的数字游乐场
  • 解析json