数字图像处理【11】OpenCV-Canny边缘提取到FindContours轮廓发现

本章主要介绍图像处理中一个比较基础的操作:Canny边缘发现、轮廓发现 和 绘制轮廓。概念不难,主要是结合OpenCV 4.5+的API相关操作,为往下 "基于距离变换的分水岭图像分割" 做知识储备。

Canny边缘检测

在讲述轮廓之前,要花点时间学学边缘检测提取的一个著名算法——Canny边缘提取算法。该算法检测出边相对于其他边缘检测算法的效果显著不同就是,Canny 检测出的边是比较细且清晰。该算法相比之前学习的Sobel和Laplace而言,它是一个应用方法,是真正的做到“提取”边缘这个操作;而Sobel和Laplace只是提留在图像像素的集合中。

Canny 算法的边缘检测到提取,主要有如下几个步骤:

1、灰度化cvColor与高斯滤波GaussianBlur

将图像变为灰度图像,减少通道,高斯滤波的作用是平滑图像,减少噪声,不让Canny算法检测时,误认为是边缘,所以在一开始就使用这个高斯滤波来减少比较突出的地方,简单的来说就是过滤掉图像上不合适的地方。

2、计算图像的梯度和梯度方向Sobel/Scharr

图像的边缘是灰度值急剧变化的位置。比如在灰度图像中它只有明暗的变化,当某个地方的强度变化比较的剧烈,那么它就会形成一个边缘。明暗变化较大的地方,梯度变化也会很大。

3、非极大值抑制

这一步是要做什么的呢?经过上面的操作,我们只是对图像进行了一个增强,而并不是找到真正的边缘。而且经过1-2步骤发现的边缘是一个范围信号,但是边缘只能是一条或者一簇,所以我需要对非边缘的个体进行压制除掉?怎么做呢,就是在边缘的梯度方向上,不是给定的最大值的话,那就去掉不要。(如下图,左边是Sobel求梯度方向,右边是常用的抑制范围选取)

但是这个最大的阈值该如何设置?过高将许多原本是线的位置未设置,过低就会细碎边,我们希望效果是找到清晰、连续的边缘线。

4、双阈值筛选边缘连接

经过非极大值抑制后图像检测出边还是有许多灰色而且不算清晰。所以接下来设置双阈值,规定上下阈值,所谓双阈值就是有两个阈值分别是低阈值和高阈值。如果像素点灰度值是大于最大阈值就直接将其更新为 255 。如果像素点的灰度值小于最小阈值就将其灰度值更新为 0。如果像素点灰度值是处于最大阈值到最小阈值之间,就看其 8 邻域中是否有大于最大阈值的值,如果有也就将其归为 255,也可以取 8 领域的平均值。

Canny边缘检测算法基本上就是经过以上的步骤。OpenCV有对应优化的Canny方法,一起看看如何使用,往下在学发现轮廓的时候就要用到。

CV_EXPORTS_W void Canny(InputArray image,  // 8-bit输入图像OutputArray edges, // 输出的边缘图像,一般都是二值图像,背景是黑色double threshold1, // 低阈值,常取高阈值的1/2或者1/3 double threshold2, // 高阈值int apertureSize = 3,   // Sobel算子的size,取值3代表是3x3bool L2gradient = false // 选择true用L2=sqrt{(dI/dx)^2 + (dI/dy)^2}求梯度方向,// 默认false用L1=|dI/dx|+|dI/dy|计算
);

轮廓(contour)

  • 轮廓发现是基于图像边缘提取的基础上,寻找对象轮廓的方法。所以边缘提取的阈值选定会影响最终轮廓发现的结果
  • API介绍:findContours发现轮廓 / drawContours绘制轮廓

有时候,轮廓和边缘的概念是非常相似的,在单一物体对象上表面的轮廓就相当于其边缘特征。但是多个物体对象叠加之后,轮廓和边缘就不再能这样相提并论了。(如上图示)

轮廓是在边缘的基础上,构成一张轮廓的拓扑图,然后利用不同的拓扑算法去寻找和构建轮廓。所以边缘提取的阈值选定会影响最终轮廓发现的结果。

说完边缘与轮廓的关系与区别之后。那么在OpenCV中,轮廓的发现绘制与绘制要如何实现呢?

  1. 输入图像转为灰度图像cvtColor
  2. 使用Canny进行边缘提取,转化二值图像
  3. 使用findContours发现轮廓
  4. 使用drawContours绘制轮廓

这里先介绍cv::findCountours 和 cv::drawContours这两个api

CV_EXPORTS_W void findContours(InputArray image,             // 输入图像,二值图,一般就是Canny的输出,8-bitOutputArrayOfArrays contours, // 全部发现的轮廓对象,就是一个二维数组,图结构,往下细说OutputArray hierarchy, // 轮廓图的拓扑结构,可选输出,最终的轮廓发现就是基于这个拓扑结构实现int mode,              // 寻找轮廓的模式,一般返回RETR_TREE树模式int method,            // 轮廓发现的方法,一般使用CHAIN_APPROX_SIMPLE简单方式Point offset = Point() // 轮廓像素偏移,默认(0,0)没偏移
);CV_EXPORTS_W void drawContours( InputOutputArray image, // 绘制的目标图像InputArrayOfArrays contours, // 全部轮廓对象,就是findContours的第二个输出参数int contourIdx,              // 轮廓索引号,contours的第一维索引const Scalar& color,         // 绘制颜色int thickness = 1,           // 绘制线宽int lineType = LINE_8,       // 绘制线类型InputArray hierarchy = noArray(), // 拓扑结构图,findContours的第三个可选输出参数int maxLevel = INT_MAX, // 最大层数,0只绘制当前的,1包含内部轮廓,2所有轮廓Point offset = Point()  // 轮廓偏移
);

接着来一段案例代码,讲讲findContours的第二、第三参数如何理解。

int main()
{//读取测试图片src = imread("F:\\other\\learncv\\bottle.png");namedWindow(titleStr + "src", WINDOW_AUTOSIZE);imshow(titleStr + "src", src);//rgb转graycvtColor(src, gray, COLOR_BGR2GRAY);namedWindow(titleStr + "circles", WINDOW_AUTOSIZE);namedWindow(titleStr + "contours", WINDOW_AUTOSIZE);createTrackbar("边缘检测阈值", titleStr + "src", &threshold_value, threshold_max, Callback_Contours);waitKey(0);return 0;
}void Callback_Contours(int pos, void* userdata) {Mat canny_img;Canny(gray, canny_img, threshold_value, threshold_value * 2.0, 3, false);vector<vector<Point>> contours;vector<Vec4i> hierarchy;findContours(canny_img, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));Mat dst1 = Mat::zeros(gray.size(), CV_8UC3);Mat dst2 = Mat::zeros(gray.size(), CV_8UC3);for (size_t i = 0; i < contours.size(); i++) {drawContours(dst1, contours, i, colorWhite, 1, LINE_8, hierarchy, 0, Point(0, 0));vector<Point> contourPoints = contours[i];for (size_t j = 0; j < contourPoints.size(); j++) {circle(dst2, contourPoints[j], 1, colorWhite);}}imshow(titleStr + "contours", dst1);imshow(titleStr + "circles", dst2);
}

以上代码运行的效果,Canny边缘阈值在100~200之间。 其中我把Canny的第二个输出参数contours也以点的方式绘制出来,对应的是图最左边,中间部分是drawContours绘制的轮廓,右边是原图。放大可以清楚观察到 “荷花” 二字上方,荷花瓣的位置,明显看出点的方式是断断续续的中间留有很大一部分的空白,而绘制轮廓后是能把它们连接成一条线。这是因为drawContours会根据contours二维数据的第一维去判断这些是不是属于同一线段。Debug调试就可以知道contours[i]的每一层长度都是不一样的。

至于第三个参数 hierarchy 轮廓拓扑关系,此参数输出的内容 与 第四个参数 mode 寻找轮廓的模式,有莫大的关系,详细看看查阅以下这个同学的详细分析。

(十二) findContours函数的hierarchy详解_findcontours hierarchy_恒友成的博客-CSDN博客获取对象的轮廓,一般最好先对图像进行灰度化再进行阈值处理,然后用来检测轮廓。_findcontours hierarchyhttps://blog.csdn.net/lx_ros/article/details/126258801

Ok,That’s All.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/21605.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Hippo4j源码的方式安装部署教程】

&#x1f680; 线程池管理工具-Hippo4j &#x1f680; &#x1f332; AI工具、AI绘图、AI专栏 &#x1f340; &#x1f332; 如果你想学到最前沿、最火爆的技术&#xff0c;赶快加入吧✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;CSDN-Java领域优质创作者&#…

Object.fromEntries()将键值对列表转换为一个对象

Object.fromEntries() 静态方法将键值对列表转换为一个对象 将 Array 转换成对象&#xff1a; let arr [["name","张三"],["age","40"]] let obj Object.fromEntries(arr); console.log(obj);将 Map 转换成对象&#xff1a; let …

Spring 项目创建和使用2 (Bean对象的存取)

目录 一、创建 Bean 对象 二、将Bean对象存储到 Spring容器中 三、创建 Spring 上下文&#xff08;得到一个Spring容器&#xff09; 1. 通过在启动类中 ApplicationContext 获取一个 Spring容器 2. 通过在启动类种使用 BeanFactory 的方式来得到 Spring 对象 &#xff08;此…

C# Linq 详解一

目录 一、概述 二、Where 三、Select 四、GroupBy 五、First / FirstOrDefault 六、Last / LastOrDefault C# Linq 详解一 1.Where 2.Select 3.GroupBy 4.First / FirstOrDefault 5.Last / LastOrDefault C# Linq 详解二 1.OrderBy 2.OrderByDescending 3.Skip 4.Take …

第一百零六天学习记录:数据结构与算法基础:单链表(王卓教学视频)

线性表的链式表示和实现 结点在存储器中的位置是任意的&#xff0c;即逻辑上相邻的数据元素在物理上不一定相邻 线性表的链式表示又称为非顺序映像或链式映像。 用一组物理位置任意的存储单元来存放线性表的数据元素。 这组存储单元既可以是连续的&#xff0c;也可以是不连续的…

C#生成类库dll以及调用实例

本文讲解如何用C#语言生成类库并用winform项目进行调用 目录 创建C#类库项目 Winform调用dll 创建C#类库项目 编写代码 using System.Threading;namespace ClassLibrary1 {public class Class1{private Timer myTimer = null;//定义定时器用于触发事件//定义公共的委托和调…

短视频抖音seo矩阵系统源码开发者思路(一)

一套优秀的短视频获客系统&#xff0c;支持短视频智能剪辑、短视频定时发布&#xff0c;短视频排名查询及优化&#xff0c;短视频智能客服等&#xff0c;那么短视频seo系统具体开发应该具备哪些功能呢&#xff1f;今天小编就跟大家分享一下我们的技术开发思路。 抖音矩阵系统源…

Qt Https通信: TLS initialization failed 解决方法

Qt Https通信&#xff1a; TLS initialization failed 解决方法&#xff0c;Window端使用Qt 做开发请求Https资源时&#xff0c;会经常遇到 TLS initialization failed。 原因分析&#xff1a; 在Qt中并未包含 SSL所包含的库&#xff0c;因此需要开发者&#xff0c;自己将库拷贝…

百度iOS端长连接组件建设及应用实践

作者 | 百度消息中台团队 导读 在过去的十年里&#xff0c;移动端技术飞速发展&#xff0c;移动应用逐渐成为主要的便捷访问和使用互联网的方式&#xff0c;承接了越来越多的业务和功能&#xff0c;这也意味着对移动端和服务器之间的通信效率和稳定性提出了更高的要求。为了实现…

C语言实现简易通讯录

目录 普通版 功能需求 模块设计 test.c模块实现 contact.h模块实现 类型的声明 函数的声明 头文件、枚举、宏定义 contact.c 模块实现 初始化通讯录 增加联系人 显示所有联系人的信息 查找函数 删除指定联系人 查找指定联系人 修改指定联系人 进阶版通讯录&a…

基于scrcpy的Android群控项目重构,获取Android屏幕元素信息并编写自动化事件

系列文章目录 基于scrcpy的远程调试方案 基于scrcpy的Android群控项目重构 基于scrcpy的Android群控项目重构 进阶版 基于scrcpy的Android群控项目重构&#xff0c;获取Android屏幕元素信息并编写自动化事件&#xff08;视频&#xff09; 基于scrcpy的Android群控项目重构…

面向个人的免费组态软件:摩尔信使MThings

产品官网 现代工业自动化各行各业中均广泛应用工控上位机软件&#xff0c;但同时也面临着一系列挑战和复杂性。 多样化设备组网&#xff1a;工控系统包含不同厂家的各类硬件和设备&#xff0c;如传感器、执行器、PLC等。工控上位机软件需要与不同类型的设备进行集成和通信&am…