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

OPENCV——查找图形轮廓

图像形状查找在OPENCV里面是非常常见的功能,它常用于视觉任务、目标检测、图像分割等等。在OPENCV中通常使用Canny函数、findContours函数、drawContours函数结合在一起去做轮廓的形检测。

一、重要函数讲解

1.1findContours函数的简介以及定义

在OPENCV中通常使用findContours函数去寻找图片的轮廓,也是OPENCV中处理轮廓最重要的函数之一,它常用于找到二值图像中所有物体的轮廓。它的实现原理是通过扫描一张二值图像,然后找到所有的轮廓,并把所有的数据存储在向量里面。下面我们来看看findContours的函数定义

void findContours( InputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset = Point() );

第一个参数:image输入的二值图像,这个图像通常是用在边缘检测、阈值处理等等

第二个参数:contours输出的轮廓集合,每一个轮廓都是由点组成,通常用vector<vector<Point>>来表示

第三个参数:hierarchy输出的轮廓层次结构,这通常表示轮廓之间的父子关系,这个是可选参数,通常用vector<Vec4i> hierarchy来表示。比方说,第i个轮廓,hierarchy[i][0]、hierarchy[i][1]、hierarchy[i][2]、hierarchy[i][3], 依次为第i个轮廓[Next、Pervious、First_Child,Parent], 这表示的是相同等级下种下一轮廓、前一轮廓,第一个子轮廓和父轮廓的索引号。若轮廓i没有下一个,前一个或者父级轮廓,则层次相应的元素是负数。如下图:

Next表示同一级别的下一个轮廓索引,若我们图片中取出轮廓0,同一水平的下一个是轮廓1。所以说当轮廓 == 0的时候,NEXT就是轮廓1。

Previous表示同一级别的上一个轮廓索引,如轮廓1的同一级别的上一个是轮廓0。以此类推,轮廓2的上一个轮廓是轮廓1。

First_Child表示的是当前轮廓的第一个子轮廓的索引。比方说,对于轮廓2,子轮廓是2a,所以轮廓2的First_Child是轮廓2a相对应的索引值。而对于3a来说,它有两个轮廓分别是6,7, 但这里只能取第一个轮廓,所以这里是6。

Parent表示的是当前轮廓的父轮廓索引,比方说对于轮廓6和轮廓7来说,它们的父轮廓都是3a。

第四个参数:mode轮廓检索模式,通常有以下选项

枚举值作用适用场景
RETR_EXTERNAL只检测最外层轮廓,忽略所有内部孔洞轮廓最常用,嵌入式优先推荐;只需要物体外轮廓、不需要内部细节时使用,计算量最小
RETR_LIST检测所有轮廓,但不建立层级关系,所有轮廓同级需要全部轮廓但不需要父子关系时使用
RETR_CCOMP检测所有轮廓,建立两层层级(外层 + 内层孔洞)有孔洞的物体(比如圆环、带孔零件)检测
RETR_TREE检测所有轮廓,建立完整的树形层级关系需要完整轮廓嵌套结构的复杂场景

第五个参数:method轮廓近似方法,通常有以下的几种方法

枚举值作用适用场景
CHAIN_APPROX_SIMPLE压缩水平、垂直、对角线方向的冗余点,只保留线段端点;比如矩形轮廓只存 4 个角点强烈推荐,大幅减少轮廓点数量,节省内存和计算量,嵌入式必用
CHAIN_APPROX_NONE保存轮廓上所有像素点,点数量极多需要精确轮廓轨迹的高精度场景,一般不用

第六个参数:offset轮廓点偏移量,默认(0,0)

输出数据结构说明

  • contours是二维向量:contours[0]是第一条轮廓,contours[0][0]是第一条轮廓的第一个点坐标;
  • 每条轮廓都是一组连续的Point(x,y)坐标,围成闭合的边缘。

1.2 ​​​​​​​drawContours函数的简介以及定义

在OPENCV中drawContours常用于绘制图像的轮廓,如上图,我们来看看这个函数的API定义:

void drawContours( InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar& color, int thickness = 1, int lineType = LINE_8, InputArray hierarchy = noArray(), int maxLevel = INT_MAX, Point offset = Point() );

第一个参数:image输出图像,即绘制轮廓后的图像

第二个参数:contours轮廓的集合,它是由一系列的点组成

第三个参数:contourIdx、轮廓索引数组,指定要绘制哪些轮廓

第四个参数:contourColor轮廓颜色,使用Scalar类型表示

第五个参数:thickness轮廓线宽,默认1

第六个参数:lineType轮廓线类型,默认为LINE_8

第七个参数:hierarchy轮廓层次结构,用于绘制轮廓的父子关系。默认为noArray()

第八个参数:maxLevel表示绘制轮廓的最大层级数量若maxLevel 为0,则只绘制指定的轮廓;若maxLevel 为1,则绘制轮廓极其所有嵌套轮廓;若maxLevel 为2,则绘制轮廓、所有嵌套轮廓、所有嵌套到嵌套的轮廓。

第九个参数:轮廓点的偏移量,默认为(0,0)

1.3 ​​​​​​​Canny函数的简介以及定义

Canny函数主要用在OPENCV的边缘检测计算,边缘检测是OPENCV图像中非常重要的功能,它的功能如(上图一)。它能够高效地提取图像中的边缘信息,而Canny边缘检测是OPENCV里面最优秀和最精准的边缘检测方法。Canny的工作原理可以分为以下比较重要的步骤进行处理,分别是高斯滤波(将图像转换为灰度图像,高斯滤波作用是平滑图像,让Canny检测的时候准确率更高)、梯度强度和方向的计算(计算图像中每个像素的强度和方向、强度表示像素点的边缘强度、梯度表示的是边缘方向这里的梯度需要用到sobel因子)、非极大抑制(经过NMS操作后,会除去一些不是边缘的像素点)、双阈值处理(给出一个阈值,若超过这个阈值的边缘则会被保留)、边缘链接(经过双阈值处理过后,强边缘则会留下来,弱边缘则会被抑制,并会把所有的强边缘全部连接起来),步骤如下图2

下面是双阈值的处理的图解:当梯度值大于maxVal则认为是强边界;当minVal < 梯度值 < maxVal跟边界有连接的部分则保留,否则废弃;梯度值< minVal则废弃。另外需要注意的是高阈值与低阈值的比例最好是2:13:1之间。

下面我们来看看Canny的函数定义:

void Canny( InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize = 3, bool L2gradient = false );

第一个参数:image输入的图像,这个图像一定要单通道灰度图

第二个参数:edges输出的边缘图像,这个图像也必须是单通道黑白图

第三个参数:threshold1第一个滞后性阈值,低阈值,小于低阈值则认为是弱边缘,就是需要抛弃的边缘。

第四个参数:threshold2第二个滞后性阈值,高阈值,大于高阈值被认为强边缘,需要保留的边缘

第五个参数:apertureSize指的是Sobel算子大小,这个值默认为3,代表的是3*3的矩阵大小。

第六个参数:L2gradient是计算图像梯度幅度值的情况,这个值默认为False;若选择True,则使用更精确的L2范数进行计算

二、查找图形轮廓并画框

经过上一章节的讲解,我们对整个OPENCV提取轮廓的API有了一个大致的了解。本章节主要是讲解如何通过代码来实现OPENCV的轮廓检测提取然后进行画框。具体的如下图:

完成轮廓检测,需要做以上步骤。分别是imread读取图片(这个图片默认是3通道)、利用cvtColor把8VU3的三通道图片转换成灰度图(8VU1)、调用Canny对灰度图像进行边缘检测、调用findContours去查找轮廓、循环轮廓数量然后调用drawContours进行画框操作。

代码

#include <opencv2/opencv.hpp> #include <opencv2/dnn.hpp> #include <opencv2/imgcodecs.hpp> #include <opencv2/imgproc.hpp> #include <iostream> using namespace cv; using namespace std; int main() { Mat img = imread("shape.png"); Mat imgGray; cvtColor(img, imgGray, COLOR_RGB2GRAY); Mat imgCanny; Canny(imgGray, imgCanny, 25, 75); vector<vector<Point>> contours; vector<Vec4i> hierarchy; findContours(imgCanny, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); Mat drawing = Mat::zeros(imgCanny.size(), CV_8UC3); for(int i = 0; i < contours.size(); i++) { Scalar color = Scalar(255, 255, 0); drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point()); } imwrite("contour,jpg",drawing); return 0; }

2.1. 读取原图

Mat img = imread("shape.png");
  • 读取当前目录下的shape.png,默认以BGR 三通道彩色格式加载。

2.2. 灰度化转换

Mat imgGray; cvtColor(img, imgGray, COLOR_RGB2GRAY);
  • 功能:将彩色图转为单通道灰度图,Canny 只支持单通道输入。

2.3. Canny 边缘检测

Mat imgCanny; Canny(imgGray, imgCanny, 25, 75);
  • 功能:对灰度图做边缘检测,输出单通道二值边缘图(白色为边缘,黑色为背景)。
  • 参数说明:低阈值 25,高阈值 75,高低阈值比例为 3:1,大于75表示的是强边缘需要保存下来的,小于25是弱边缘需要忽略的,25-75之间的边缘则会用算法进行候选;默认使用 3×3 Sobel 算子。

2.4. 轮廓检测

vector<vector<Point>> contours; vector<Vec4i> hierarchy; findContours(imgCanny, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

处理完边缘后,就findContours可以通过findContours去查找所有图像的轮廓了,由于我们检测的图像没有内嵌的形状,所以我们选择RETR_EXTERNAL的模式只检测外轮廓,Method的方法则选择CHAIN_APPROX_SIMPLE存储所有的轮廓点。注意:contours一般是用vector<vector<point>>表示,hierarchy通常用vector<Vec4i>表示.

2.5. 轮廓绘制与保存

1. 创建绘制画布

Mat drawing = Mat::zeros(imgCanny.size(), CV_8UC3);
  • 创建一张和原图尺寸一致的纯黑三通道图像,作为绘制轮廓的背景板,方便突出显示轮廓。

2. 循环绘制所有轮廓

for(int i = 0; i < contours.size(); i++) { Scalar color = Scalar(255, 255, 0); drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point()); }
  • 遍历每一条检测到的轮廓,逐个绘制到黑色画布上。
  • 参数说明:
    • 颜色Scalar(255,255,0):BGR 格式下为青色(蓝 + 绿满值,红为 0)。
    • 线宽2:轮廓线条粗细为 2 像素。
    • 线型8:8 连通线型,线条更平滑。
    • maxLevel=0:只绘制当前层级轮廓,配合RETR_EXTERNAL使用时无额外效果。

3. 保存结果图

imwrite("contour,jpg",drawing);
http://www.jsqmd.com/news/1084477/

相关文章:

  • 3分钟解锁VLC点击暂停插件:让视频控制变得如此简单!
  • 单节点跑业务稳如泰山 扩容高可用集群反而频繁卡死 复盘完整连接交互揪出深层根因
  • 现场 w3wp 卡顿,dump 抓回来托管栈全是死的:一次从 696 万对象里挖根因的排查实录
  • Boss直聘批量投递工具:5倍效率提升的求职价值重构指南
  • 设计 Token 多主题管理与跨端同步:从单一变量到系统化主题引擎
  • Claude Code国内配置总失败?macOS保姆级安装教程:从Node.js到API直连,10分钟跑通
  • PCL2启动器Java配置终极指南:5步彻底解决Minecraft启动问题
  • 8个实用技巧:如何让qBittorrent搜索功能变得像谷歌一样强大
  • WindowResizer:打破窗口尺寸限制的终极免费解决方案
  • 音频格式解密:ncmdump技术解析与跨平台音乐自由实践
  • 光伏并网逆变器设计与优化:全国大学生电子设计竞赛实战
  • 如何快速提升中文文献管理效率:Zotero茉莉花插件的终极解决方案
  • IDEA书签功能终极避坑清单:12个导致书签丢失/失效的配置陷阱,第9个连JetBrains Support都曾忽略
  • AzurLaneAutoScript:碧蓝航线智能自动化解决方案的技术架构与实践
  • 3个核心场景深度解析:WELearn网课助手如何重塑你的学习体验
  • PotPlayer字幕翻译插件终极配置指南:三步实现免费多语言观影体验
  • Java数组与链表终极对决:谁更胜一筹?
  • 3分钟掌握猫抓浏览器扩展:从零到精通的完整资源嗅探指南
  • 微信消息智能路由系统:构建高效群组通信网络的技术实现
  • 3分钟快速上手:Windows版Spotify无广告体验完整指南
  • Boss直聘批量投递工具:5倍提升求职效率的智能解决方案
  • AI 做的 PPT 永远在念稿——不是你不会用 Gamma,是演示文稿的品控规则它没装
  • 解锁qBittorrent搜索新境界:20+开源搜索引擎插件全攻略
  • 三步解锁PotPlayer智能字幕翻译:免费实现多语言视频无障碍观看
  • 告别重复点击:碧蓝航线Alas脚本如何实现7x24小时智能游戏管理
  • AI云防护实战:如何用行为建模精准识别低频率CC攻击
  • 微信群消息自动转发终极指南:如何告别手动复制粘贴
  • 医用超声图像模拟系统算法:弹性成像原理与实践
  • 猫抓浏览器扩展:三步解决在线视频下载难题的终极指南
  • 3步搞定窗口遮挡难题:AlwaysOnTop让你告别Alt+Tab的终极方案