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

news2024/11/26 14:34:45

本章主要介绍图像处理中一个比较基础的操作: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代表是3x3
    bool 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-bit
    OutputArrayOfArrays 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转gray
    cvtColor(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.coloradmin.cn/o/750158.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数字孪生,建设智慧城市的新型“加速器”

城市是什么&#xff1f; 是现代文明与生态的联结&#xff0c;是自然与人友好栖息的空间&#xff0c;是运转复杂庞大的系统。 今天&#xff0c;中国的城市在历经十余年的“智慧城市”建设后已经被赋予了数智融合的全新解读。随着近年来5G、云计算、人工智能爆发式能量增长&#…

常见的bug---4、在DataGrip上跑本地模式报return 2异常

文章目录 问题描述原因分析&#xff1a;解决方案&#xff1a; 问题描述 FAILED: Execution Error, return code 2 from org.apache.hadoop.hive.ql.exec.mr.MapRedTask 在DataGrip上设置了Hive的本地模式。虽然可以建表、但是无法对表进行插入数据 原因分析&#xff1a; 在插…

深入理解张量维度的真正含义

在人工智能领域&#xff0c;比如深度学习&#xff0c;机器学习&#xff0c;张量这一概念被频繁使用。虽然网上有很多关于张量的文章&#xff0c;但基本都是废话太多&#xff0c;而且复制粘贴说不到重点。 今天我就来讲解一下张量维度的真正含义。 首先. 张量并不是一个简单多…

STM32F103驱动VL53L0X激光测距模块

STM32F103驱动VL53L0X激光测距模块 简介引脚定义STM32F103ZET6开发板与VL53L0X模块接线测试代码实验结果 简介 TOF 是飞行时间&#xff08;Tlme of Flight&#xff09;技术的缩写&#xff0c;即传感器发出经过调制的近红外光&#xff0c;遇到物体后反射&#xff0c;传感器通过…

bug:file name too long文件名超出系统最大限制

各操作系统支持最长的文件和目录名称长度&#xff08;Linux、Win、Mac&#xff09; 今天开发需求的时候发现无法新建文件&#xff0c;提示file name too lang&#xff0c;于是翻阅和查询了一些资料&#xff0c;发现不同操作系统下文件名和目录名最长的长度不同。 操作系统文件名…

游戏如何应对虚拟定位问题

在游戏系统设计中&#xff0c;排行榜这一设计可谓是十分巧妙。它可以充分调动玩家的“胜负欲”&#xff0c;给予玩家前进的目标及动力&#xff0c;满足玩家的心理需求。 排行榜的设计使用范围广&#xff0c;对游戏留存、付费等指标提升效果出众&#xff0c;在不少游戏中都可以…

玩机搞机---某些安卓定制机解除系统安装限制的思路与分析过程

定制机的意义不多做解释&#xff0c;懂的人都明白. 在一些定制机中会限制用户安装app。当你使用第三方工具或者adb指令安卓app是他会提示de xxx.apk. autostarter is not auth,install failed!等等类似的提示&#xff0c;在解决某些定制系统中类似问题的过程中&#xff0c;基本…

Unity知识记录--项目升级URP

URP是指Unity的通用渲染管线&#xff0c;此处主要针对原有项目进行升级使用&#xff0c;但并不是所有的内容都可以直接通过升级完成&#xff0c;直接使用的Unity默认的shader通常可以完成直接升级&#xff0c;自己编写的shader通常需要重做。 首先我们先要安装这个Package&…

汇报方案设计方案规划方案资源下载

标题汇报方案设计方案规划方案资源下载https://wheart.cn/so/home?mdw&tag%E5%AE%89%E5%85%A8文章标签事业单位人事人才信息综合管理系统建设设计报价方案人事系统,人事人才,事业单位,工资系统,职称系统xx纪检委智慧监督平台建设方案汇报.docx建设方案,规划设计,汇报方案营…

程序员如何走向世界!

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

训练速度提升300倍,Niantic最新VPS视觉定位方案亮相

近年来在谷歌、Niantic等推动下&#xff0c;视觉定位对于AR应用的重要性越来越明显&#xff0c;尤其是在室内导航场景&#xff0c;定位精度可超越传统GPS方案。为了进一步提升视觉定位、深度视觉地图构建的准确性&#xff0c;Niantic Labs在CVPR 2023期间公布了ACE方案&#xf…

[MySql]表的增删查改

目录 前言: 1.插入数据 2.查询数据 2.1全列查询 2.2指定查询 2.3别名 2.4去重 2.5排序 2.6条件查询 2.7聚合查询 2.7.1group by语句 2.7.2havin语句 2.8联合查询 2.8.1内连接 2.8.2外连接 2.8.3自连接 2.8.4子查询 3.修改 4.删除 前言: 本次大多数使用…

【计算机视觉】MobileSAM论文解读:比SAM小60倍,比FastSAM快4倍,速度和效果双赢

文章目录 一、导读二、摘要三、Introduction三、Related work3.1 SAM&#xff1a;泛化和通用功能性3.2 ViT&#xff1a;轻巧高效 四、Mobile-Friendly SAM4.1 Background and Project Goal4.1.1 Background on SAM4.1.2 Project goal 4.2 Proposed Method4.2.1 耦合蒸馏4.2.2 从…

这个工具,补齐了 JMeter性能分析最后一公里短板

要说现在企业中主流的性能测试工具是什么&#xff1f; 答案肯定有 JMeter。 但是Jmeter 贵为测试工具界的“大牛”&#xff0c;在性能分析能力方面&#xff0c;还是有些欠缺的。 像Allen巨佬在高级性能实际教学的过程中就会经常遇到&#xff0c;学生截取一个监听器的图&…

使用传统的jdbc方式操作数据库

现在开发中有了mybatis、jdbcTemplate之后&#xff0c;已经很少公司会直接使用jdbc来连接数据库了&#xff0c;但是无论是mybatis还是jdbcTemplate&#xff0c;其底层都是jdbc。 这篇文章就主要介绍一下怎么通过jdbc来连接数据库。 在这之前&#xff0c;创建数据库jdbc&#xf…

JS相关介绍

1.JS引入&#xff1a; 内部&#xff1a;直接在html文件内部使用script标签调用 外部&#xff1a;另外新建JS文件&#xff0c;再在html文件中调用 2.输入输出&#xff1a; 输入&#xff1a;prompt(请输入您的姓名&#xff1a;) 输出&#xff1a;document.write(你们真是天才) 注…

2.9 Bootstrap 辅助类

文章目录 Bootstrap 辅助类文本背景其他更多实例关闭图标插入符快速浮动内容居中清除浮动显示和隐藏内容屏幕阅读器 Bootstrap 辅助类 下面将讨论 Bootstrap 中的一些可能会派上用场的辅助类。 文本 以下不同的类展示了不同的文本颜色。如果文本是个链接鼠标移动到文本上会变…

【自定义类型】(结构体、枚举、联合)

结构体内存对齐&#xff1a; 计算结构体的大小 结构体成员不是按照顺序在内存中连续存放的而是有一定的对齐规则的 结构体内存对齐的规则&#xff1a; 1、结构体的第一个成员永远放在相比于结构体变量起始位置的偏移量为0的位置。 2、从第二个成员开始&#xff0c;往后的每…

我打赌!这个 SQL 题,大部分人答不出来

周末的时候&#xff0c;一个读者问了我一个很有意思的问题&#xff0c;是关于 MySQL 中 update 加锁的问题。 他用下面这张数据库表&#xff0c;做了个 MySQL 实验的时候。 发现事务 B 的 update 不会阻塞&#xff0c;而事务 C 的 update 会阻塞&#xff0c;都是对 id 10 这条…