OPENCV轮廓提取findContours和drawContours

OPENCV轮廓提取findContours和drawContours关于opencv中的轮廓提取函数findcontours()和轮廓绘制函数drawContours()中参数的说明和实验结果。

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

    最近用OPENCV的轮廓提取函数,总结一下。

void findContours//提取轮廓,用于提取图像的轮廓
(
InputOutputArray image,//输入图像,必须是8位单通道图像,并且应该转化成二值的
OutputArrayOfArrays contours,//检测到的轮廓,每个轮廓被表示成一个point向量
OutputArray hierarchy,//可选的输出向量,包含图像的拓扑信息。其中元素的个数和检测到的轮廓的数量相等
int mode,//说明需要的轮廓类型和希望的返回值方式
int method,//轮廓近似方法
Point offset = Point()
)

void drawContours//绘制轮廓,用于绘制找到的图像轮廓
(
 InputOutputArray image,//要绘制轮廓的图像
 InputArrayOfArrays contours,//所有输入的轮廓,每个轮廓被保存成一个point向量
 int contourIdx,//指定要绘制轮廓的编号,如果是负数,则绘制所有的轮廓
 const Scalar& color,//绘制轮廓所用的颜色
 int thickness = 1, //绘制轮廓的线的粗细,如果是负数,则轮廓内部被填充
 int lineType = 8, /绘制轮廓的线的连通性
 InputArray hierarchy = noArray(),//关于层级的可选参数,只有绘制部分轮廓时才会用到
 int maxLevel = INT_MAX,//绘制轮廓的最高级别,这个参数只有hierarchy有效的时候才有效
                                          //maxLevel=0,绘制与输入轮廓属于同一等级的所有轮廓即输入轮廓和与其相邻的轮廓
                                          //maxLevel=1, 绘制与输入轮廓同一等级的所有轮廓与其子节点。
                                          //maxLevel=2,绘制与输入轮廓同一等级的所有轮廓与其子节点以及子节点的子节点
 Point offset = Point()
)

   注意:findContours()运行的时候,这个图像会被直接涂改,因此如果是将来还有用的图像,应该复制之后再传给findContours()。

   接下来总结一下我在书上看到的解释和自己实验的结果。

一《学习opencv》中给的例子。

OPENCV轮廓提取findContours和drawContours

                                   图1 输入的测试图(上图)和得到的轮廓图(下图)

    传递给findContours()测试图(上图),得到轮廓图(下图)。得到的轮廓只有两种,外部轮廓(橙色虚线)或者孔(蓝色点线)。

1.参数mode的意义

   mode的值决定把找到的轮廓如何挂到轮廓树节点变量(h_prev, h_next, v_prev, v_next)上。图2展示了四种可能的mode值所得到的结果的拓扑结构。

OPENCV轮廓提取findContours和drawContours

                                                               图2 轮廓连接方法

    每种情况下,结构都可以看成是被横向连接(h_prev, h_next)联系和被纵向连接(v_prev, v_next)不同层次。

    CV_RETR_EXTERNAL 只检测出最外轮廓即c0。图2中第一个轮廓指向最外的序列,除此之外没有别的连接。

    CV_RETR_LIST 检测出所有的轮廓并将他们保存到表(list)中,图2中描绘了这个表,被找到的9条轮廓相互之间由h_prev和h_next连接。这里并没有表达出纵向的连接关系,没有使用v_prev和v_next.

    CV_RETR_COMP 检测出所有的轮廓并将他们组织成双层的结构,第一层是外部轮廓边界,第二层边界是孔的边界。从图2可以看到5个轮廓的边界,其中3个包含孔。最外层边界c0有两个孔,c0之间的所有孔相互间由h_prev和h_next指针连接。

    CV_RETR_TREE 检测出所有轮廓并且重新建立网状的轮廓结构。图2中,根节点是最外层的边界c0,c0之下是孔h00,在同一层中与另一个孔h01相连接。同理,每个孔都有子节点(相对于c000和c010),这些子节点和父节点被垂直连接起来。这个步骤一直持续到图像最内层的轮廓,这些轮廓会成为树叶节点。

2. method的五个值

    CV_CHAIN_CODE 用freeman链码输出轮廓,其他方法输出多边形(顶点的序列)。

    CV_CHAIN_APPROX_NONE将链码编码中的所有点转换为点。

    CV_CHAIN_APPROX_SIMPLE压缩水平,垂直或斜的部分,只保存最后一个点。

    CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_QPPROX_TC89_KCOS使用Teh-Chin链逼近算法中的一个。

    CV_LINK_RUNS与上述的算法完全不同,连接所有的水平层次的轮廓。

二 实验部分

    我用跟上面的例子结构一样的图作为测试图片(这是我照着上面的图自己画的)。

OPENCV轮廓提取findContours和drawContours

                                                      图3 原图

测试代码如下:

#include “opencv/highgui.h”
#include “opencv/cv.h”
using namespace cv;
void main()
{

 Mat image = imread(“test.png”);
 Mat gray;
 cvtColor(image, gray, CV_BGR2GRAY);
 GaussianBlur(gray, gray, Size(3, 3), 3, 3);
 Mat img;
 threshold(gray, img, 0, 255, THRESH_OTSU);
 Mat img1;
 img.copyTo(img1);
 vector<vector<Point>> contours;
 vector<Vec4i> hierarchy;
 findContours(img, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE);
 Mat resultImage = Mat ::zeros(img.size(),CV_8U);
 drawContours(resultImage, contours, -1, Scalar(255, 0, 255));
 return;
}

1. mode的四种方法

    下图是分别用mode的四种方法得到的轮廓的结果

OPENCV轮廓提取findContours和drawContours

                                                                 图4 四种mode方式分别得到的轮廓结果
    从图中可以看出,图4(a)得到的只有一个最外的轮廓,hierarchy的值为{-1,-1,-1,-1},表示除此之外没有别的连接。其他三种方式都可以得到所有的轮廓,而hierarchy的值是不同的,反映了不同的轮廓连接方法即结果的拓扑机构。
2.method的五种方法
     CV_CHAIN_CODE是用Freeman链码输出轮廓,在Freeman链码中,多边形被表示成一系列位移,每一个位移有8个方向,使用整数0~7表示。关于Freeman链码的具体内容《学习opencv》这本书里有详细的介绍。通过试验发现用这种方式表示的轮廓不可以通过drawContours()绘制出检测到的轮廓。
     CV_CHAIN_APPROX_NONE 是将链码编码中的所有点转换为点,也就是参数contours中的每个轮廓是用构成该轮廓的所有像素点表示的。
     CV_CHAIN_APPROX_NONE是输出多边形顶点的序列,也就是说参数contours中的每个轮廓是用该轮廓的所有顶点表示的。
     图5分别画出了这两种方式下的contours中的点,用红色的点表示。
OPENCV轮廓提取findContours和drawContours
                                                  图5 轮廓的表达方式
    其中(a)中由于像素之间没有间隔已经练成线了。
    图6是CV_CHAIN_APPROX_TC89_L1和CV_LINK_RUNS两种方式下contours的结果。
OPENCV轮廓提取findContours和drawContours
                                                                 图6 轮廓的表达方式
     从图中可以看出,CV_CHAIN_APPROX_TC89_L1的结果也是保存的轮廓的顶点,但是仔细看可以看出轮廓线是有断的感觉不是连贯的,这可能是轮廓逼近过程中的误差,具体情况就不清楚了,也有可能这种方法对具有某种特征的图逼近效果比较好。
    书上说CV_LINK_RUNS是和上述算法完全不同的算法,连接所有水平层次的轮廓,但结果画出来的是一条条垂直的线,也不清楚具体怎么回事,不过应该也肯定是有它存在的意义的。另外,在mode=CV_RETR_EXTERNAL的前提下,选用这种方式也是可以画出所有轮廓的,而其他的方式只能画出最外层的轮廓。还有书上说此方法只可与    CV_RETR_LIST搭配使用。我试了其他的方法,也是可以的。
    所以method这个参数决定了轮廓的表达方式,这要根据自己提取轮廓后的应用选择合适的轮廓描述方法来决定用哪一种。
3.drawContours()函数中的参数thinkness
    thinkness=CV_FILLED可以填充轮廓,opencv官网对这个参数的解释原文是“If it is negative (for example,
thickness=CV_FILLED ), the contour interiors are drawn”,图7给出了这个thinkness=CV_FILLED时的绘制结果,可以看出我们得到了跟原图一致的结果。
OPENCV轮廓提取findContours和drawContours
                                                   图7 轮廓绘制结果
关于更复杂的情况就没研究了,我要的就是图7这样的结果。有不对的地方希望大家批评指正!





版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/223413.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • Windows 打开和关闭默认共享方法汇总

    [原文]在使用xcopy上传文件至远程windows服务器时,出现“Invaliddriverspecification”(无效驱动器规格),经过各种排查,最终发现是服务器“关闭默认共享”导致的。Windows启动时都会默认打开admin$ipc$和每个盘符的共享,对于不必要的默认共享,一般都会把它取消掉,可当又需要打开此默认共享时,又该从哪里设置呢。经过自己的验证,汇总出一下方法。一:查看window共享资源运行–>cmd–>输入netshare二:彻底关闭

  • matlab维纳滤波器函数_逆滤波器

    matlab维纳滤波器函数_逆滤波器[Matlab]维纳滤波器设计​ 维纳滤波(wienerfiltering)一种基于最小均方误差准则、对平稳过程的最优估计器。这种滤波器的输出与期望输出之间的均方误差为最小,因此,它是一个最佳滤波系统。它可用于提取被平稳噪声污染的信号。​ 从连续的(或离散的)输入数据中滤除噪声和干扰以提取有用信息的过程称为滤波,这是信号处理中经常采用的主要方法之一,具有十分重要的应用价值,而相应的装置称为…

    2022年10月22日
  • 关于SOAP调用返回对象的写法 wsdl webservice

    关于SOAP调用返回对象的写法 wsdl webservice

  • 局部静态变量的定义_C语言强制转换数据类型

    局部静态变量的定义_C语言强制转换数据类型C语言中定义的静态变量存放在栈区,动态分配的内存空间位于堆区。这题答案为F为大家科普一下知识C语言中局部变量存在栈里,全局变量存静态存储区。局部变量在栈空间上分配,这个局部变量所在的函数被多次调用时,每次调用这个局部变量在栈上的位置都不一定相同。局部变量也可以在堆上动态分配,但是记得使用完这个堆空间后要释放之。全局变量全部存放在静态存储区,在程序开始执行时给全局变量分配存储区,程序行完毕就释放。在程序执行过程中它们占据固定的存储单元,而不动态地进行分配和释放;…

    2022年10月30日
  • ReadProcessMemory函数的分析「建议收藏」

    ReadProcessMemory函数的分析「建议收藏」ReadProcessMemory函数用于读取其他进程的数据。我们知道自远古时代结束后,user模式下的进程都有自己的地址空间,进程与进程间互不干扰,这叫私有财产神圣不可侵犯。但windows里还真就提供了那么一个机制,让你可以合法的获取别人的私有财产,这就是ReadProcessMemory和WriteProcessMemory。为什么一个进程居然可以访问另一个进程的地址空间呢?因为独立的只是低

  • java date当前时间_JAVA中获取当前系统时间

    java date当前时间_JAVA中获取当前系统时间一.获取当前系统时间和日期并格式化输出:importjava.util.Date;importjava.text.SimpleDateFormat;publicclassNowString{publicstaticvoidmain(String[]args){SimpleDateFormatdf=newSimpleDateFormat(“yyyy-MM-ddHH:m…

    2022年10月18日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号