数字图像处理【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代表是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.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

实现大文件传输的几种方法,并实现不同电脑间大文件传输

随着网络技术的快速发展&#xff0c;大文件的传输需求越来越多&#xff0c;如何在不同的电脑之间实现大文件的快速传输&#xff0c;是一个挑战&#xff0c;下面介绍几种常用的方法可以解决这个问题。 1、利用局域网传输&#xff1a;把两台电脑接入同一个网络环境&#xff0c;通…

Redis整合springboot笔记

redis整合springboot学习笔记 pom引入依赖 需要同时引入spring-boot-starter-data-redis和commons-pool2这2个依赖&#xff1b; spring-boot-starter-data-redis是官方封装的redis操作依赖, commons-pool2是redis需要的连接池&#xff0c;不引入这个会导致启动报错. <depe…

17 | 从后端到前端:微服务后,前端如何设计?

微服务架构通常采用前后端分离的设计方式。作为企业级的中台&#xff0c;在完成单体应用拆分和微服务建设后&#xff0c;前端项目团队会同时面对多个中台微服务项目团队&#xff0c;这时候的前端人员就犹如维修电工一样了。 面对如此多的微服务暴露出来的 API 服务&#xff0c…

适用于 Type-C接口PD应用的智能二极管保护开关

日前&#xff0c;集设计、研发、生产和全球销售一体的著名功率半导体、芯片及数字电源产品供应商Alpha and Omega Semiconductor Limited&#xff08;AOS, 纳斯达克代码:AOSL) 推出一款采用理想二极管运作进行反向电流保护的新型Type-C PD 高压电源输入保护开关。AOZ13984DI-02…

数据库应用:MySQL数据库SQL高级语句与操作

目录 一、理论 1.克隆表与清空表 2.SQL高级语句 3.SQL函数 4.SQL高级操作 5.MySQL中6种常见的约束 二、实验 1.克隆表与清空表 2.SQL高级语句 3.SQL函数 4.SQL高级操作 5.主键表和外键表 三、总结 一、理论 1.克隆表与清空表 克隆表&#xff1a;将数据表的数据记录…

【技巧】Maven重复依赖分析查找

【技巧】Maven重复依赖分析查找 遇到奇葩的错误可以考虑是不是依赖冲突了 比如同一段代码 再这个项目中好好的 另一个项目中不能用等 idea安装插件 maven helper 打开pom文件 输入要查找的依赖 将不用的排除掉 右键排除即可

lua脚本语言学习笔记

Lua 是一种轻量小巧的脚本语言&#xff0c;用标准C语言编写并以源代码形式开放&#xff0c; 其设计目的是为了嵌入应用程序中&#xff0c;从而为应用程序提供灵活的扩展和定制功能。 因为我们使用redis的时候一般要写lua脚本&#xff0c;这篇文章就介绍一下lua脚本语言的基础用…

前端 mock 数据的几种方式

目录 接口demo Better-mock just mock koa webpack Charles 总结 具体需求开发前&#xff0c;后端往往只提供接口文档&#xff0c;对于前端&#xff0c;最简单的方式就是把想要的数据写死在代码里进行开发&#xff0c;但这样的坏处就是和后端联调前还需要再把写死的数据…

大小端模式

文章目录 一、概念二、举例三、判大小端和交换 一、概念 大端模式&#xff08;Big-endian&#xff09;&#xff0c;是一种数据存储方式&#xff0c;其中较高的字节&#xff08;最高有效字节&#xff09;存储在较低的内存地址&#xff0c;较低的字节&#xff08;最低有效字节&am…

开发跨平台APP,是用Flutter还是React Native开发框架?

随着移动互联网的飞速发展&#xff0c;对于开发人员而言&#xff0c;如何快速地开发出兼容不同平台&#xff08;iOS、Android&#xff09;的应用&#xff0c;成为了一个重要的问题。 跨平台应用程序开发框架的好处&#xff1a; 1. 一个App适用于多个设备&#xff1b; 2. 一个…

数据结构 ~ 树

什么是树 - tree 一种分层数据的抽象模型&#xff1b; 如&#xff1a;DOM、级联选择、树形控件&#xff0c;js 中没有树 可以用 Object 构建树&#xff1a; const tree {val: a,children: [{val: a-1,children: [{val: a-1-1,children: []}]},{val: a-2,children: [{val: a…

chatGPT指令大全可免费使用网站列表chatGPT4试用方案

指令列表 写作助理 &#x1f449; 最常使用的 prompt&#xff0c;用于优化文本的语法、清晰度和简洁度&#xff0c;提高可读性。作为一名中文写作改进助理&#xff0c;你的任务是改进所提供文本的拼写、语法、清晰、简洁和整体可读性&#xff0c;同时分解长句&#xff0c;减少…

剑指offer刷题笔记--Num51-60

1--数组中的逆序对&#xff08;51&#xff09; 主要思路&#xff1a; 基于归并排序&#xff0c;视频讲解参考&#xff1a;数组中的逆序对 #include <iostream> #include <vector>class Solution { public:int reversePairs(std::vector<int>& nums) {if(…

JavaWeb 前后端分离

AJax 1. 前端视图 ajax\src\main\webapp\ajax-register.html <html><head><meta charset"UTF-8"> </head><body><form class"form-horizontal" role"form"><div><tr><td>账号</td&…

6款好用的在线原型图设计工具推荐

在线原型图的核心功能是可视化需求&#xff0c;因此一个易于使用的在线原型图工具对原型图设计至关重要。对于熟悉的Photoshop和iIlustrator来说&#xff0c;虽然它们功能强大&#xff0c;但界面太复杂&#xff0c;初学者很难快速启动&#xff0c;面对批量调整的在线原型图&…

Allegro过孔盖油和过孔开窗设置(部分过孔开窗)

Allegro设置一部分过孔盖油&#xff0c;另一部分过孔开窗。 过孔开窗&#xff1a;过孔部分去除阻焊&#xff0c;便于调试和散热&#xff1b; 过孔盖油&#xff1a;过孔盖上阻焊油墨&#xff0c;防止过孔连锡短路。 总结 使用pad designer设计两种via pad&#xff0c;一种不开…

STM32案例学习 GY-39环境监测传感器模块

STM32案例学习 GY-39环境监测传感器模块 硬件平台 野火STM32F1系列开发板正点STM32F1系列开发板STM32F103ZET6核心板GY-39环境监测传感器模块 GY-39环境监测传感器模块 GY-39 是一款低成本&#xff0c;气压&#xff0c;温湿度&#xff0c;光强度传感器模块。工作电压 3-5v…

【JavaEE】HTTP请求的构造

目录 1、通过form表单构造HTTP请求 2、通过JS的ajax构造HTTP请求 3、Postman的安装和简单使用 常见的构造HTTP请求的方式有一下几种&#xff1a; 直接通过浏览器的地址栏&#xff0c;输入一个URL&#xff0c;就可以构造一个GET请求HTML中的一些特殊标签&#xff0c;也会触发…

网工内推 | 美图秀秀招网工,大专以上,15薪,NP认证优先

01 美图公司 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、美图大厦网络、分公司网络、IT相关项目的网络、办公内网服务器&#xff1b; 2、负责网络的设计、运行、管理和维护等工作&#xff1b; 3、负责远程办公环境的优化、运行、管理和维护工作&#xff1b; 4、…

二级市场负重前行?腾讯音乐的“新伤”与“旧患”

炎炎夏日的7月&#xff0c;于腾讯音乐&#xff08;NYSE:TME、HK:01698&#xff09;而言并不太平。 先是&#xff0c;在7月5日&#xff0c;企鹅FM发布官方公告称由于业务调整&#xff0c;将于9月6日正式停止运营。 仅过十二天&#xff0c;7月17日&#xff0c;腾讯音乐发布公告&…