终于,我们迎来了本系列的收官之作!在完成本篇的学习之后,大家就将掌握分析重叠正方形目标所需的全部核心技能。上一讲我们重点剖析了边缘检测技术,而今天,我们将深入探索OpenCV中的轮廓查找与角点检测两大关键方法,为实战应用打下最后一块基石。
文章封面

从理论到代码:OpenCV轮廓检测与层次结构分析

1. cv2.findContours() 函数指南

轮廓检测,顾名思义,核心任务就是从图像中精准提取出目标的轮廓信息。在 OpenCV 中,我们可以借助 cv2.findContours() 函数方便地实现这一功能。该函数的原型如下:

contours, hierarchy = cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]])

我们先来详细看一下它的输入参数:

  • mode:轮廓检索模式

该参数决定了函数检索轮廓的方式以及轮廓之间层次关系的构建方法。常用的模式有以下几种:

参数 说明
cv2.RETR_EXTERNAL 只检测最外层轮廓。
cv2.RETR_LIST 检测所有轮廓,但不建立等级关系。
cv2.RETR_CCOMP 检测所有轮廓,并将它们组织为两级层次结构(顶层为外部边界,次层为内部孔的边界)。
cv2.RETR_TREE 检测所有轮廓,并重建一个完整的嵌套轮廓的层次结构。
  • method:轮廓近似方法

该参数控制轮廓点的存储方式,影响最终轮廓的表示精度和压缩程度:

参数 说明
cv2.CHAIN_APPROX_NONE 存储轮廓上的所有点。
cv2.CHAIN_APPROX_SIMPLE 压缩水平、垂直和对角方向的线段,只保留它们的端点(例如,矩形只存储4个顶点)。
cv2.CHAIN_APPROX_TC89_L1/cv2.CHAIN_APPROX_TC89_KCOS 使用 Teh-Chin 链逼近算法的一种。

再来看返回值:

  • contours
    是一个Python列表,其中每个元素都是图像中检测到的一个轮廓。
  • hierarchy
    是一个可选输出的数组,描述了轮廓之间的层次关系。hierarchy的形状通常为 (1, n, 4),其中 n 是轮廓的数量。对于每个轮廓 contours[i],hierarchy[0][i] 是一个包含四个整数的数组 [next, previous, first_child, parent],结构如下:
名称 含义
next 同一层级中下一个轮廓的索引号。如果没有下一个轮廓,则为 -1。
previous 同一层级中上一个轮廓的索引号。如果没有上一个轮廓,则为 -1。
first_child 当前轮廓的第一个子轮廓的索引号。如果没有子轮廓,则为 -1。
parent 当前轮廓的父轮廓的索引号。如果没有父轮廓,则为 -1。

需要注意的是,传入 cv2.findContours() 的图像应是经过二值化处理的单通道二值图。在实际应用中,我们常会将 Canny 边缘检测的结果作为该函数的输入。检测到轮廓之后,还可以使用 cv2.drawContours() 函数将轮廓可视化地绘制在原图上,便于进一步分析和调试。

2. 轮廓检测实战:外部、全部及内部轮廓提取

下面我们通过几个具体的代码示例,来直观地理解OpenCV中轮廓检测的实际应用。

示例一:检测并绘制图像外轮廓

这段代码展示了如何检测图像中最外层的轮廓,并将其绘制出来。关键在于使用cv2.RETR_EXTERNAL检索模式,它只返回最外层的轮廓,忽略内部可能存在的嵌套结构。

contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
result = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)   # 将灰度图转换为BGR格式,便于绘制彩色轮廓
cv2.drawContours(result, contours, -1, (0, 255, 0), 2)   # 参数-1表示绘制列表中的所有轮廓
cv2.imshow("Contours", result)

示例二:检测并绘制全部轮廓

如果想要获取图像中所有的轮廓信息(包括内部嵌套的轮廓),只需将检索模式改为cv2.RETR_TREE即可。这个模式会建立完整的轮廓层次结构,从而得到全部轮廓。

contours, _ = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
result = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)   # 同样转换为BGR图像用于绘制
cv2.drawContours(result, contours, -1, (0, 255, 0), 2)   # 绘制检测到的所有轮廓
cv2.imshow("Contours", result)

示例二实现效果

示例三:检测并仅绘制最内层轮廓

在某些应用场景中,我们可能只关心最内层的轮廓。这时可以利用函数返回的层次结构信息(hierarchy)进行筛选。下面的代码演示了如何找出并绘制没有子轮廓的、最内层的轮廓。

contours, hierarchy = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
innermost_contours = []
if hierarchy is not None:
    hierarchy = hierarchy[0]
    for i, contour_hierarchy in enumerate(hierarchy):
        if contour_hierarchy[2] == -1:   # 通过判断hierarchy[i][2]是否为-1(没有子轮廓)来筛选最内层轮廓
            innermost_contours.append(contours[i])
result = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)   # 转换色彩空间
cv2.drawContours(result, innermost_contours, -1, (0, 255, 0), 2)   # 仅绘制最内层轮廓
cv2.imshow("Contours", result)

OpenCV中的角点检测:Harris与Shi-Tomasi方法

角点检测,顾名思义,是识别图像中角点位置的技术。我们介绍两种在OpenCV中实现角点检测的方法——Harris角点检测与Shi-Tomasi角点检测。

1. Harris角点检测实战指南

作为一篇注重实战的教程,我们将不再赘述Harris角点检测的核心原理,而是把重点放在具体实现方法上。在OpenCV中,我们可以直接调用 cv2.cornerHarris() 函数来实现这一功能,该函数的原型如下:

dst = cv2.cornerHarris(src, blockSize, ksize, k[, dst[, borderType]])

下面我们来详细了解一下它的输入和输出参数:

参数名称 说明
src 输入图像,必须是单通道(灰度)8位或浮点型图像。
blockSize 邻域大小(窗口尺寸),与Harrisi角点检测实现思路有关。
ksize Sobel 算子的孔径参数,用于计算梯度。必须是奇正整数,通常为 3。
k Harris 检测器中的自由参数,即响应函数 R 公式中的 k 值。通常取 0.04~0.06。
dst 输出图像,用于存储 Harris 响应值 R,类型为 CV_32FC1,大小与源图像相同。
borderType 像素外推法(边界模式),一般不用设置。
dst 每个像素点对应的 Harris 响应值 R,值越大,该点越可能是角点。

接下来,我们通过一个实际例子展示如何在OpenCV中运用Harris角点检测:

gray = np.float32(img)   # 将图像转换为浮点型(Harris算法要求)
dst = cv2.cornerHarris(gray, blockSize=2, ksize=3, k=0.04)
result = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
corners = np.argwhere(dst > 0.01 * dst.max())   # 提取响应值大于最大值1%的角点位置
for corner in corners:   # 使用绿色圆圈标记检测到的角点
    y, x = corner   # 注意:numpy数组坐标为(y,x),而OpenCV使用(x,y)顺序
    cv2.circle(result, (x, y), 5, (0, 255, 0), 1)
cv2.imshow('Harris Corners', result)

Harrisi角点检测实现效果

2. Shi-Tomasi角点检测实战指南

在OpenCV中,Shi-Tomasi角点检测(也被称为“良好特征点检测”)通过cv2.goodFeaturesToTrack()函数实现。从函数名就可以看出,它的目的是找到“适合跟踪的好特征”,函数原型如下

corners = cv2.goodFeaturesToTrack(image, maxCorners, qualityLevel, minDistance[, corners[, mask[, blockSize[, useHarrisDetector[, k]]]]])

下面我们来详细了解一下该函数的输入参数与返回值:

参数名称 说明
image 输入图像,必须是单通道(灰度)8位或浮点型图像。
maxCorners 想要返回的最大角点数量。设为 0 表示无限制。
qualityLevel 角点质量水平阈值。
minDistance 角点之间的最小欧氏距离。
mask 可选参数,指定感兴趣区域。只有在 mask 内非零的区域才会检测角点。
blockSize 计算导数协方差矩阵时考虑的邻域大小(窗口尺寸)。
corners 一个 NumPy 数组,形状为 (N, 1, 2),其中 N 是检测到的角点数量。每个角点的格式是 [[x, y]],数据类型通常是 np.float32。

接下来,我们通过一个具体示例展示如何在OpenCV中使用Shi-Tomasi方法进行角点检测:

corners = cv2.goodFeaturesToTrack(img, maxCorners=100, qualityLevel=0.05, minDistance=10)
result = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
if corners is not None:
    corners = np.int0(corners)   # 将浮点坐标转换为整数坐标
    for corner in corners:
        x, y = corner.ravel()    # 坐标解包
        cv2.circle(result, (x, y), 5, (0, 255, 0), 1)
cv2.imshow('Shi-Tomasi Corners', result)

Shi-Tomasi角点检测实现效果

3. 角点检测对比:为何Shi-Tomasi更优?

对比Harrisi角点检测和Shi-Tomasi角点检测的输出结果,Harris算法在实际测试中漏检了一个角点。而且我们可以注意到,两种算法我们使用的是相同的代码绘制绿色圆圈来标记角点,但Harris输出图像中的标记明显更粗。这是由于Harris算法可能在同一个角点位置附近检测出多个响应点,导致标记重叠、显得更密集。另一方面,Harris所使用的经验参数 k 在实际调优过程中往往不够直观,调节起来也较为不便。因此,从稳定性、结果清晰度和参数易用性等方面综合考虑,我们更推荐在实际应用中选择Shi-Tomasi角点检测方法。

基础完结,实战启航:解锁电赛重叠正方形终极算法

从第一篇《放弃OpenMV!这是我们为电赛视觉交的第一笔学费》到本篇为止,我们从为什么选择转战OpenCV,到树莓派快速部署,再到OpenCV的各种基础操作和算法实现,免费基础系列已全部更新完成!

感谢大家的追更和支持!为方便大家学习,可以后台私信 【OpenCV终极大礼包】 领取本系列文章所需的全部代码和资源

系列终级大礼包

然而,从基础到攻克“重叠正方形分析”这类复杂赛题,中间还有最关键的“临门一脚”。这需要一套完整的算法设计思路、工程实现方案和性能优化技巧。许多队伍在此折戟,不是不会OpenCV,而是不知如何将一个个分散的技术点组合成精准的解决方案。

现在,无需再自行摸索!

我们已将攻克该赛题的全套技术方案、优化参数和核心代码,整合为一个完整的 《25年电赛C题:重叠正方形分析终极解决方案》 资源包。

【资源包核心内容】

  • 完整工程源码:开箱即用,基于本系列知识构建的完整可执行项目
  • 核心算法模块:A4纸内边框精准定位与ROI自适应提取技术
  • 独家算法方案:基于直角特征的重叠正方形分割与恢复算法

本免费系列是“渔”,而付费资源包是为你准备好的“鱼”,助你快速拿下赛题,节省大量试错时间。

👉 欢迎有需要的同学,欢迎关注文末GZ号后台私信【C题解决方案】了解详情与获取方式。我们将根据首批用户的反馈,优先为大家制作更详尽的文档或视频教程!

祝各位备赛顺利,我们明年赛场见!

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐