位置: IT常识 - 正文

OpenCV实战(15)——轮廓检测详解(opencv.)

编辑:rootadmin
OpenCV实战(15)——轮廓检测详解 OpenCV实战(15)——轮廓检测详解0. 前言1. 提取区域轮廓1.1 轮廓提取1.2 复杂轮廓分析2. 计算区域形状描述符2.1 四边形检测3. 完整代码小结系列链接0. 前言

推荐整理分享OpenCV实战(15)——轮廓检测详解(opencv.),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:opencv.,opencv教程,opencv实战项目实现所有功能,opencv实战项目,opencv实战项目教程,opencv实战项目教程,opencv实战口罩识别,opencv实战项目,内容如对您有帮助,希望把文章链接给更多的朋友!

在计算机视觉领域,轮廓通常指图像中对象边界的一系列点。因此,轮廓通常描述了对象边界的关键信息,包含了有关对象形状的主要信息,该信息可用于形状分析与对象检测和识别。本节中,我们首先介绍如何提取图像中轮廓,然后讲解如何计算轮廓的形状描述符。

1. 提取区域轮廓1.1 轮廓提取

图像通常包含目标对象的表示,图像分析的目标之一是识别和提取这些对象。在目标检测/识别应用中,通常需要生成一个二值图像,显示目标物体的位置,提取包含在二值图像中的对象。例如,使用如下二值图像:

我们可以通过简单的阈值操作获得此图像,然后应用开/闭形态滤波器。本节将介绍如何提取图像中的目标对象,更具体地说,我们将提取图像中的连接部分,即由二值图像中的一组连接像素组成的形状。OpenCV 提供了一个简单的函数来提取图像的连接部分的轮廓,即 cv::findContours 函数。

(1) 要使用 cv::findContours 函数,我们需要一个点向量存储所有输出轮廓:

std::vector<std::vector<cv::Point> > contours;

(2) 使用 cv::findContours 函数检测图像的所有轮廓并将它们保存在轮廓向量中:

cv::findContours(image, contours, // 轮廓向量 cv::RETR_EXTERNAL, // 检索外部轮廓 cv::CHAIN_APPROX_NONE); // 检索每个轮廓的所有像素

cv::findContours 函数的输入是二值图像,输出是一个轮廓向量,每个轮廓由一个 cv::Point 对象向量表示,因此输出参数定义为 std::vector 对象。此外,还指定了两个标志,第一个表示只需要外部轮廓,即忽略对象中的孔;第二个标志用于指定轮廓的格式,使用 CV_CHAIN_APPROX_NONE 选项,向量将列出轮廓中的所有点,使用 CV_CHAIN_APPROX_SIMPLE 标志,将仅包含水平、垂直或对角线轮廓的端点,也可以使用其他标志获取更复杂的轮廓链近似表示。使用上示图像,可以得到 10 个连通分量。

(3) 使用 OpenCV 可以非常方便地在一张图片上绘制出连接部分的轮廓:

cv::Mat result(image.size(), CV_8U, cv::Scalar(255));cv::drawContours(result, contours, -1, // 绘制所有轮廓 cv::Scalar(0), // 颜色 2); // 线宽为2

如果此函数的第 3 个参数为负值,则绘制所有轮廓,也可以使用正值指定要绘制的轮廓的索引,如下图所示:

轮廓是通过系统地扫描图像直到检测出所有的目标部分,从连接部分上的起点开始,沿着它的轮廓,在其边框上标记像素;完成标记后,在最后一个位置继续扫描,直到找到新的连接部分。

(4) 然后可以单独分析识别的连接部分。例如,我们可以通过预估目标对象的预期大小消除一些无效部分,可以使用连接部分周长的最小值和最大值消除无效连接:

// 消除所有过短或过长的轮廓int cmin = 50;int cmax = 500;std::vector<std::vector<cv::Point> >::iterator itc = contours.begin();while (itc!=contours.end()) { if (itc!=contours.end()) { if (itc->size()<cmin || itc->size()>cmax) { itc = contours.erase(itc); } else { ++itc; } }}

由于 std::vector 中的每个消除操作的时间复杂度都是 O(N)O(N)O(N),因此该循环可以进一步进行优化。在原图上绘制轮廓,结果如下图所示:

1.2 复杂轮廓分析

使用简单的标准就能够帮助我们识别图像中所有感兴趣的对象,在更复杂情况下,我们需要对连接部分的属性进行更精细的分析。 使用 cv::findContours 函数,还可以通过在函数调用中指定 CV_RETR_LIST 标志检测二值图中所有闭合轮廓(包括对象中的孔形轮廓):

cv::findContours(image, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);

使用以上函数调用,可以得到以下轮廓:

OpenCV实战(15)——轮廓检测详解(opencv.)

可以看到上图中增加了额外轮廓。也可以将这些轮廓组织成层次结构,主要部分是父组件,其中的孔是其子组件,如果这些孔内还有组件,它们将成为之前子组件的子组件,依此类推,该层次结构可以通过使用 CV_RETR_TREE 标志获得:

std::vector<cv::Vec4i> hierarchy;cv::findContours(image, contours, // 轮廓向量 hierarchy, // 分层表示 CV_RETR_TREE, // 使用树结构检索所有轮廓 CV_CHAIN_APPROX_NONE); // 每一轮廓的所有像素

在这种情况下,每个轮廓在相同的索引处都有一个对应的层次元素,由四个整数组成。前两个整数提供了同一级别的下一个和前一个轮廓的索引,后两个整数提供该轮廓的第一个子项和父项的索引,负索引表示轮廓列表的结尾。CV_RETR_CCOMP 标志类似,但层次结构仅包括两个级别。

2. 计算区域形状描述符

连接部分通常对应于图片场景的中某个目标对象,为了识别此对象,或将其与其他图像元素进行比较,我们可能需要进行测量以提取所需特征。在本节中,我们介绍 OpenCV 中可用的形状描述符,用于描述轮廓形状。 有多个 OpenCV 函数可用作形状描述符,应用这些函数可以提取连接部分。我们使用目标对象相对应的轮廓向量,计算轮廓上( contours[0] 到 contours[3] )的形状描述符并在轮廓图像(线宽为 1 )上绘制结果(线宽为 2)。

(1) boundingRect 函数用于计算矩形边框:

// 矩形cv::Rect r0 = cv::boundingRect(contours[0]);cv::rectangle(result, r0, 0, 2);

(2) minEnclosingCircle 函数用于近似最小包围圆:

// 圆形float radius;cv::Point2f center;cv::minEnclosingCircle(contours[1], center, radius);cv::circle(result, center, static_cast<int>(radius), 0, 2);

(3) 区域轮廓的多边形近似使用 approxPolyDP 函数:

// 近似多边形std::vector<cv::Point> poly;cv::approxPolyDP(contours[2], poly, 5, true);cv::polylines(result, poly, true, 0, 2);std::cout << "Polygon size: " << poly.size() << std::endl;

多边形绘制函数 cv::polylines 与其他绘图函数类似,第 3 个参数为布尔类型用于指示轮廓是否闭合,如果为true,则将最后一个点连接到第一个点。

(4) 凸包函数 convexHull 是多边形近似的另一种形式:

// 凸包std::vector<cv::Point> hull;cv::convexHull(contours[3], hull);cv::polylines(result, hull, true, 0, 2);

(5) 矩是另一个强大的描述符,可以计算区域内的质心:

// 矩itc = contours.begin();while (itc!=contours.end()) { cv::Moments mom = cv::moments(*itc++); cv::circle(result, cv::Point(mom.m10/mom.m00, mom.m01/mom.m00), 2, cv::Scalar(0), 2);}

结果图像如下:

边界框大多数情况下是表示和定位图像中目标对象的最紧凑的方式,其定义为完全包含对象形状的最小尺寸的矩形。边界框的高度和宽度可以指示对象的垂直或水平尺寸,例如,可以使用高宽比来区分汽车和行人;当只需要目标的近似尺寸和位置时,通常使用最小包围圆。 当想要与目标对象形状相似的紧凑的表示时,可以采用多边形近似,通过指定精度参数( cv::approxPolyDP 函数中的第 4 个参数)指定目标对象形状与近似多边形之间的最大可接受距离,函数返回的 cv::Point 的向量对应于多边形的顶点。为了绘制这个多边形,我们需要遍历向量并在它们之间线段将相邻点连接起来。 形状的凸包或凸包络是包含形状的最小凸多边形,可以将其想象为弹性皮筋围在目标对象周围时的形状,凸包轮廓将在对象形状轮廓的凹面位置偏离原始轮廓,这些位置通常称为凸面缺陷,并且可以使用 OpenCV 函数 cv::convexityDefects 识别这些缺陷,调用方式如下所示:

std::vector<cv::Vec4i> defects;cv::convexityDefects(contours[3], hull, defects);

contour 和 hull 参数分别是原始轮廓和凸包轮廓(均为 std::vector<cv::Point> 实例)。输出是由四个整数元素组成的向量,前两个整数是轮廓上的点索引,用于界定缺陷;第三个整数对应凹面内最远的点,最后一个整数对应这个最远点到凸包的距离。 矩是形状结构分析中常用的数学工具,OpenCV 定义了一个封装了形状所有计算矩的数据结构,cv::moments 函数的返回值就使用这种数据结构,这些矩构成了对物体形状的简洁描述。我们可以使用这个结构中前3个空间矩来获得形状的质心。 也可以使用 OpenCV 函数计算结构属性,cv::minAreaRect 函数计算最小的封闭旋转矩形;cv::contourArea 函数估计轮廓(内部像素数)的面积;cv::pointPolygonTest 函数用于确定一个点是在轮廓内部还是外部,而 cv::matchShapes 可以测量两个轮廓之间的相似性。我们可以通过组合所有这些属性进行更高级的图像结构分析。

2.1 四边形检测

我们可以利用形态学操作转换后获得的图像提取图像形状,假设,我们使用形态学操作转换图像获得的 MSER 结果,然后构建算法检测图像中的四边形分量。假设我们检测以下使用 MSER 算法得到的二值图像,检测四边形分量能够帮助我们识别建筑物上的窗户等,为了减少图像中的噪音,我们使用了一些形态滤波器对图像进行预处理:

// 创建二值图像components = components==255;// 图像开操作cv::morphologyEx(components, components, cv::MORPH_OPEN, cv::Mat(), cv::Point(-1, -1), 3);

接下来,获取轮廓:

// 反转图像cv::Mat componentsInv = 255 - components;// 获取轮廓和连接部分cv::findContours(componentsInv, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);

最后,遍历所有轮廓并用多边形近似:

cv::Mat quadri(components.size(), CV_8U, 255);std::vector<std::vector<cv::Point> >::iterator it = contours.begin();while (it!= contours.end()) { poly.clear(); // 使用多边形近似轮廓 cv::approxPolyDP(*it,poly,5,true); // 检测轮廓是否为四边形 if (poly.size()==4) { cv::polylines(quadri, poly, true, 0, 2); } ++it;}

检测结果如下所示:

如果想要检测矩形,我们可以测量相邻边之间的角度并消除掉偏差过大(与 90 度相比)的四边形。

3. 完整代码

完整代码文件 blobs.cpp 如下所示:

#include <iostream>#include <vector>#include <opencv2/core/core.hpp>#include <opencv2/imgproc/imgproc.hpp>#include <opencv2/highgui/highgui.hpp>int main() { // 读取二进制图像 cv::Mat image = cv::imread("binary.png", 0); if (!image.data) return 0; cv::namedWindow("Binary Image"); cv::imshow("Binary Image", image); // 获取轮廓和连接部分 std::vector<std::vector<cv::Point> > contours; cv::findContours(image, contours, // 轮廓向量 cv::RETR_EXTERNAL, // 检索外部轮廓 cv::CHAIN_APPROX_NONE); // 检索每个轮廓的所有像素 std::cout << "Contours: " << contours.size() << std::endl; std::vector<std::vector<cv::Point> >::const_iterator itContours = contours.begin(); for (; itContours!=contours.end(); ++itContours) { std::cout << "Size: " << itContours->size() << std::endl; } // 绘制轮廓 cv::Mat result(image.size(), CV_8U, cv::Scalar(255)); cv::drawContours(result, contours, -1, // 绘制所有轮廓 cv::Scalar(0), // 颜色 2); // 线宽为2 cv::namedWindow("Contours"); cv::imshow("Contours", result); // 消除所有过短或过长的轮廓 int cmin = 50; int cmax = 500; std::vector<std::vector<cv::Point> >::iterator itc = contours.begin(); while (itc!=contours.end()) { if (itc!=contours.end()) { if (itc->size()<cmin || itc->size()>cmax) { itc = contours.erase(itc); } else { ++itc; } } } // 绘制轮廓 cv::Mat original = cv::imread("2.png"); cv::drawContours(original, contours, -1, cv::Scalar(0, 0, 255), 2); cv::namedWindow("Contours on Animals"); cv::imshow("Contours on Animals",original); result.setTo(cv::Scalar(255)); cv::drawContours(result, contours, -1, 0, 1); image = cv::imread("binary.png", 0); // 矩形 cv::Rect r0 = cv::boundingRect(contours[0]); cv::rectangle(result, r0, 0, 2); // 圆形 float radius; cv::Point2f center; cv::minEnclosingCircle(contours[1], center, radius); cv::circle(result, center, static_cast<int>(radius), 0, 2); // 近似多边形 std::vector<cv::Point> poly; cv::approxPolyDP(contours[2], poly, 5, true); cv::polylines(result, poly, true, 0, 2); std::cout << "Polygon size: " << poly.size() << std::endl; // 凸包 std::vector<cv::Point> hull; cv::convexHull(contours[3], hull); cv::polylines(result, hull, true, 0, 2); // std::vector<cv::Vec4i> defects; // cv::convexityDefects(contours[3], hull, defects); // 矩 itc = contours.begin(); while (itc!=contours.end()) { cv::Moments mom = cv::moments(*itc++); cv::circle(result, cv::Point(mom.m10/mom.m00, mom.m01/mom.m00), 2, cv::Scalar(0), 2); } cv::namedWindow("Some Shape descriptors"); cv::imshow("Some Shape descriptors", result); image = cv::imread("binary.png", 0); cv::findContours(image, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE); result.setTo(255); cv::drawContours(result, contours, -1, 0, 2); cv::namedWindow("All Contours"); cv::imshow("All Contours", result); // MSER 图像 cv::Mat components; components = cv::imread("mser.png",0); // 创建二值图像 components = components==255; // 图像开操作 cv::morphologyEx(components, components, cv::MORPH_OPEN, cv::Mat(), cv::Point(-1, -1), 3); cv::namedWindow("MSER image"); cv::imshow("MSER image", components); contours.clear(); // 反转图像 cv::Mat componentsInv = 255 - components; // 获取轮廓和连接部分 cv::findContours(componentsInv, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE); cv::Mat quadri(components.size(), CV_8U, 255); std::vector<std::vector<cv::Point> >::iterator it = contours.begin(); while (it!= contours.end()) { poly.clear(); // 使用多边形近似轮廓 cv::approxPolyDP(*it,poly,5,true); // 检测轮廓是否为四边形 if (poly.size()==4) { cv::polylines(quadri, poly, true, 0, 2); } ++it; } cv::namedWindow("MSER quadrilateral"); cv::imshow("MSER quadrilateral", quadri); cv::waitKey(); return 0;}小结

在本文中,首先介绍了轮廓的相关概念,然后了解利用 cv::findContours() 检测轮廓、cv::drawContours() 绘制轮廓,在获取轮廓后,我们可以计算轮廓的形状描述符。

系列链接

OpenCV实战(1)——OpenCV与图像处理基础 OpenCV实战(2)——OpenCV核心数据结构 OpenCV实战(3)——图像感兴趣区域 OpenCV实战(4)——像素操作 OpenCV实战(5)——图像运算详解 OpenCV实战(6)——OpenCV策略设计模式 OpenCV实战(7)——OpenCV色彩空间转换 OpenCV实战(8)——直方图详解 OpenCV实战(9)——基于反向投影直方图检测图像内容 OpenCV实战(10)——积分图像详解 OpenCV实战(11)——形态学变换详解 OpenCV实战(12)——图像滤波详解 OpenCV实战(13)——高通滤波器及其应用 OpenCV实战(14)——图像线条提取

本文链接地址:https://www.jiuchutong.com/zhishi/296195.html 转载请保留说明!

上一篇:Python基于YOLOv5的交通标志识别系统[源码](yolo算法python代码)

下一篇:CLIP论文详解(ldpc论文)

  • 小米牙刷怎么连接米家app(小米牙刷怎么连接蓝牙网关)

    小米牙刷怎么连接米家app(小米牙刷怎么连接蓝牙网关)

  • 卡贴机能拔卡再插吗(卡贴机能拔卡再插卡吗)

    卡贴机能拔卡再插吗(卡贴机能拔卡再插卡吗)

  • 小米手机桌面小工具加载中(小米手机桌面小宠物怎么设置)

    小米手机桌面小工具加载中(小米手机桌面小宠物怎么设置)

  • ai卡了不动怎么办(ai卡了不动怎么保存)

    ai卡了不动怎么办(ai卡了不动怎么保存)

  • 苹果8手机小圆点怎么设置(苹果8手机小圆点)

    苹果8手机小圆点怎么设置(苹果8手机小圆点)

  • 华为nova2有nfc功能吗(华为nova2snfc功能怎么开启)

    华为nova2有nfc功能吗(华为nova2snfc功能怎么开启)

  • 淘宝退款时间怎么规定的(淘宝退款怎么改退款路径)

    淘宝退款时间怎么规定的(淘宝退款怎么改退款路径)

  • 二极管符号怎么看方向(二极管符号怎么看导通)

    二极管符号怎么看方向(二极管符号怎么看导通)

  • 安装程序正在启动服务需要多久(安装程序正在启动服务就进不去了)

    安装程序正在启动服务需要多久(安装程序正在启动服务就进不去了)

  • 微软型号1724是几代(微软型号1873)

    微软型号1724是几代(微软型号1873)

  • 微信怎么设置定时发送(微信怎么设置定位店铺地址)

    微信怎么设置定时发送(微信怎么设置定位店铺地址)

  • 小米6未知来源在哪里(小米未知来源权限在哪里关闭)

    小米6未知来源在哪里(小米未知来源权限在哪里关闭)

  • 荣耀20能不能插耳机(荣耀20能不能插u盘)

    荣耀20能不能插耳机(荣耀20能不能插u盘)

  • 抖音看直播怎么横屏(抖音看直播怎么定时关闭)

    抖音看直播怎么横屏(抖音看直播怎么定时关闭)

  • 如何选购银行的理财产品?(怎样选择一款好的银行理财)

    如何选购银行的理财产品?(怎样选择一款好的银行理财)

  • kk键盘怎么发语音包(Kk键盘怎么发语音到快手)

    kk键盘怎么发语音包(Kk键盘怎么发语音到快手)

  • 锡箔纸可以放烤箱吗(锡箔纸可以放烤箱为什么不能放微波炉)

    锡箔纸可以放烤箱吗(锡箔纸可以放烤箱为什么不能放微波炉)

  • 华为p30和p30pro区别(华为p30和p30pro区别屏幕)

    华为p30和p30pro区别(华为p30和p30pro区别屏幕)

  • 什么是BIOS 怎么进入bios,bios设置图解教程(BIOS和CMOS的区别)(bios是什么请简要介绍一下)

    什么是BIOS 怎么进入bios,bios设置图解教程(BIOS和CMOS的区别)(bios是什么请简要介绍一下)

  • Linux多个网卡怎么添加永久路由?(linux多网卡聚合)

    Linux多个网卡怎么添加永久路由?(linux多网卡聚合)

  • 学会iframe并用其解决跨域问题(iframe vh)

    学会iframe并用其解决跨域问题(iframe vh)

  • 莱达尔湖, 英格兰坎布里亚郡湖区 (© Tranquillian1/iStock/Getty Images Plus)

    莱达尔湖, 英格兰坎布里亚郡湖区 (© Tranquillian1/iStock/Getty Images Plus)

  • 被白雪覆盖的彩虹山,秘鲁 (© Jude Newkirk/Amazing Aerial Agency)(被白雪覆盖的彩虹歌词)

    被白雪覆盖的彩虹山,秘鲁 (© Jude Newkirk/Amazing Aerial Agency)(被白雪覆盖的彩虹歌词)

  • 因果推断1--基本方法介绍(个人笔记)(因果推断的常用标准)

    因果推断1--基本方法介绍(个人笔记)(因果推断的常用标准)

  • 附列资料第8栏
  • 扫码开票开错了怎么改
  • 档案室图书应该做哪个会计科目
  • 如何理解合并报表中少数股东损益的抵消
  • 定额怎么确定
  • 进项税发票认证后能退票吗
  • 实际缴纳的税款怎么算
  • 固定资产净残值率是多少
  • 税款多交一分钱怎么做分录
  • 为安装设备所发生的差旅费入什么科目
  • 应收账款已收回但是账面还有余额怎么处理
  • 办理异地租房提取公积金流程
  • 销售包装物的会计处理
  • 固定资产出租后还要折旧吗
  • 白蚁防治费怎么计算
  • 什么是差额征税,什么情况下适用差额征税
  • 当月发的奖金怎么扣个税
  • 税务领取发票后怎么操作
  • 一般纳税人印花税减半征收吗
  • 其他应付款坏账怎么处理
  • 转售水电收入
  • 一般空调安装费多少钱
  • 月末结转增值税凭证
  • 工业产值怎么计算的 统计
  • 增值税小规模纳税人减免增值税
  • 专用发票购货清单怎么写
  • 软件工程外包服务
  • 上市公司现金分红怎么派发
  • mac如何登陆两个微信
  • 一键ghost有用吗
  • 合同应收账款减免
  • 原材料盘亏计入
  • 美元汇户和钞户的区别
  • 会计分录错误用什么方法更正
  • 小规模企业没有成本票怎么办
  • php数组函数输出《咏雪》里有多少"片"字
  • .exe是指什么文件
  • php bi
  • 以前年度企业所得税分录
  • 完美解决战网已休眠正在唤醒它
  • php curl命令详解
  • 律师异地办案家属要跟着吗
  • 注销公司需要缴纳注册资金吗
  • 工会经费零申报怎么填
  • cvpr2023最佳论文
  • phpcms是什么框架
  • mysql 长事务
  • 加工行业增值税负率一般控制在多少?
  • 免征的教育费附加怎么做账
  • 职工食堂的费用怎么入账
  • 母公司借款给子公司如何做账
  • 新准则下其他应收坏账
  • 长期借款计提的利息可能借记
  • 什么是调表不调账
  • 装订凭证需要注意事项
  • 股份有限公司向股东借款
  • 减值损失账务处理
  • MySQL下载安装步骤详解
  • SQL Transcation的一些总结分享
  • Navicat连接MySQL报错
  • winole.exe - winole是什么进程
  • explorer.exe进程文件
  • win7系统如何安装蓝牙驱动
  • centos进入指定目录
  • win7的系统升级win10
  • windows8使用技巧
  • window10蓝瓶
  • cocos2dx环境搭建
  • nodejs word
  • 深入解读我本是高山
  • shell脚本读取输入使用什么命令
  • unity接入第三方sdk
  • jquery给下拉框添加选项
  • 开票怎么查发票总额
  • 电子发票和普通发票哪个好
  • 个体非正常户怎么解除
  • 四川省国家税务局官网
  • 新疆税务局网站官网首页
  • 商铺收税多少
  • 徐州国税局班子成员名单
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设