• 用实际例子详细探究OpenCV的轮廓检测函数findContours(),彻底搞清每个参数、每种模式的真正作用与含义


    关于OpenCV的轮廓检测函数findContours()各参数的大概意义,已在博文 https://blog.csdn.net/wenhao_ir/article/details/51798533中进行了介绍。

    这篇博文以实际例子来进一步认识函数findContours()各参数的意义,这篇博文实际上是上篇博文的延伸和深入。

    我们用下边这张简单的图像为例进行测试和说明:
    请添加图片描述
    上面这张图片的名字为“ring.bmp”百度网盘下载链接:https://pan.baidu.com/s/1SPxGouOli-XbBYfr-IJ9nw?pwd=dwch
    我们把上面这张图的轮廓利用轮廓检测函数findContours()检测出来,代码如下:

    # 博主微信/QQ 2487872782
    # 有问题可以联系博主交流
    # 有图像处理需求也请联系博主
    # 图像处理技术交流QQ群 271891601
    
    # !/usr/bin/env python
    # -*- coding: utf-8 -*-
    # OpenCV的版本为4.1
    
    import numpy as np
    import cv2 as cv
    import sys
    
    
    image = cv.imread('F:/material/images/2022/2022-06/ring.bmp')
    # image = cv.imread('F:/material/images/P0044-hand-02.jpg')
    if image is None:
        print('Error: Could not load image')
        sys.exit()
    
    # cv.imshow('Source Image', image)
    
    # 原图像转化为灰度图
    img_gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
    # cv.imshow('img_gray', img_gray)
    
    # 灰度图进行二值化处理,并不是函数findContours要求输入图像为二值图像,
    # 而是函数findContours在进行轮廓提取前会把原图中的非0值全部当成1处理。
    _, img_B = cv.threshold(img_gray, 71, 255, cv.THRESH_BINARY)
    # cv.imshow('img_B', img_B)
    
    cnts, harch = cv.findContours(img_B, mode=cv.RETR_TREE, method=cv.CHAIN_APPROX_SIMPLE)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    运行结果如下:
    在这里插入图片描述

    接下来开始分析运行结果:

    01-轮廓是以怎样的数据结构进行存储的?

    从上面的运行结果可以看出,检测出了两个轮廓,存放在列表型对象cnts中的,如下图所示:
    在这里插入图片描述
    从上图我们可以看出,每个轮廓由一个三维的ndarray数据对象存储,假设某个轮廓有N个点,则由N个一行二列的二维矩阵组成这个三维的ndarray数据对象,每个一行二列的二维矩阵中存储了各个轮廓点的坐标。
    每个轮廓在列表中的索引值就是轮廓的索引值,即轮廓被依次编号为0,1,2…

    02-轮廓检测模式(参数mode)对轮廓检测结果及轮廓拓扑结构(hiararchy)的影响

    02-1-轮廓检测模式为RETR_TREE时的结果及分析

    上面的示例代码便是轮廓检测模式为RETR_TREE时的结果,RETR_TREE模式表示返回所有的轮廓,并且建立完整的拓扑结构。
    此种情况下的轮廓检测结果上面已经分析说明了,这里就不再赘述了。
    我们来看下此时的轮廓拓扑结构(hiararchy)是一种怎样的数据结构。
    在这里插入图片描述
    从上面的截图中可以看出,轮廓拓扑结构(hiararchy)以ndarray的数据对象形式存储,每个ndarray数据对象是三维的。为了明白三个维度分别代表什么,我们对下面这幅图再运行一次上面的程序,
    请添加图片描述
    上面这幅图的名字为P0044-hand-02.jpg,百度网盘下载链接:https://pan.baidu.com/s/1uAClJ3xklpSGE1iA-7py5g?pwd=h237
    结果如下:
    在这里插入图片描述
    将两次运行结果一对比,我们发现拓扑结构hiararchy-ndarray对象的第一个维度的尺寸应该是永远为1的,第二个维度的尺寸应该是代表轮廓数,第三个维度的尺寸应该永远为4,4个值依次代表某个轮廓的前一个轮廓的索引值(编号)、后一个轮廓的索引值(编号)、子轮廓(嵌套轮廓)的索引值(编号)、父轮廓的索引值(编号)。

    再回到对图片ring.bmp的轮廓检测结果,我们来看下其拓扑结构的具体数值:
    在这里插入图片描述
    在这里插入图片描述
    上面的截图中每一行代表一个轮廓的拓扑结构,
    先来看第0行:
    第0行的第0个值,代表第0个轮廓的前一个轮廓的索引值,其值为-1,说明其前一个轮廓不存在;
    第0行的第1个值,代表第0个轮廓的后一个轮廓的索引值,其值为-1,说明其后一个轮廓不存在;
    第0行的第2个值,代表第0个轮廓的子轮廓的索引值,其值为1,说明索引值为1的轮廓为其子轮廓;
    第0行的第3个值,代表第0个轮廓的父轮廓的索引值,其值为-1,说明其父轮廓不存在。

    再来看第1行:
    第1行的第0个值,代表第1个轮廓的前一个轮廓的索引值,其值为-1,说明其前一个轮廓不存在;
    第1行的第1个值,代表第1个轮廓的后一个轮廓的索引值,其值为-1,说明其后一个轮廓不存在;
    第1行的第2个值,代表第1个轮廓的子轮廓的索引值,其值为-1,说明其子轮廓不存在;
    第1行的第3个值,代表第1个轮廓的父轮廓的索引值,其值为1,说明索引值为1的轮廓为其父轮廓;

    由上面的叙述我们可以看出,从拓扑结构上来看,一个轮廓虽然说可以有多个父轮廓,也可以有多个子轮廓,但在hiararchy数据结构中,只能为其填写一个父轮廓和一个子轮廓,通常就填写索引值最邻近它的子轮廓或父轮廓。比如下面的例子:
    在这里插入图片描述
    上面是某张图(实际上就是本文后面02-03点提到的小白兔手绘图片img_300_320.jpg)的轮廓拓扑结构,我们可以看到,索引值为12的轮廓是索引值为13、15、16、17、18、19、20、21、22、24的轮廓的父轮廓,但是在hiararchy数据结构中,只把轮廓索引值为13的轮廓作为了其子轮廓,但事实上它有10个子轮廓。

    02-2-轮廓检测模式为RETR_EXTERNAL时的结果及分析

    RETR_EXTERNAL轮廓检测模式代表只检测最外层轮廓,对所有轮廓设置hierarchy[i][2]= hierarchy[i][3]=-1,即没有子轮廓与父轮廓。
    我们设置成这种模式再对图片ring.bmp进行轮廓检测。
    时只需要把下面这句语句:

    cnts, harch = cv.findContours(img_B, mode=cv.RETR_TREE, method=cv.CHAIN_APPROX_SIMPLE)
    
    • 1

    修改为:

    cnts, harch = cv.findContours(img_B, mode=cv.RETR_EXTERNAL, method=cv.CHAIN_APPROX_SIMPLE)
    
    • 1

    即可修改为RETR_EXTERNAL轮廓检测模式。
    运行结果如下:
    在这里插入图片描述
    从上面的截图中我们可以看出,只检测到一个轮廓了,这个轮廓的子轮廓被忽略了。
    我们再看下此时的拓扑结构信息:
    在这里插入图片描述
    这个拓扑结构信息是符号原图、轮廓检测模式及轮廓检测结果的。

    02-3-轮廓检测模式为CV_RETR_CCOMP时的结果及分析

    CV_RETR_CCOMP轮廓检测模式提取所有轮廓,并且将其组织为双层结构。顶层(the top levell)为连通域的外围边界,次层(the second level)为孔(hole)的内层边界,如果孔(hole)中还有其它轮廓,那么又依次被组织为顶层和次层。
    只看上面这段话,相信诸君还是对这种轮廓检测模式具体是怎么操作的不是很清晰,没关系,我们看一个实际例子就清楚明白了。

    将轮廓检测语句修改为:

    cnts, harch = cv.findContours(img_B, mode=cv.CV_RETR_CCOMP, method=cv.CHAIN_APPROX_SIMPLE)
    
    • 1

    运行结果如下:
    在这里插入图片描述
    在这里插入图片描述
    可见,与RETR_TREE的结果没有区别。难道RETR_CCOMP模式与RETR_TREE模式没有区别?不是的哈,其实是有区别的。
    之所以上面的结果没有区别,是因为用的图像太简单了,我们把图像换成下面这幅图像就能看出差别了。
    请添加图片描述
    上面这幅小白兔手绘图片的名字为“img_300_320.jpg”,
    其RETR_TREE模式的轮廓检测拓扑结果如下:
    在这里插入图片描述
    其RETR_CCOMP模式的轮廓检测拓扑结果如下:
    在这里插入图片描述
    这两个检测结果就有明显区别了。我们看到在RETR_CCOMP模式下,索引值为12至22的轮廓都是索引值为11的子轮廓,因为只组织为两层结构,所以索引值为12至22的轮廓再也没有成为别的轮廓的父轮廓。
    而在RETR_TREE模式下,索引值为13的轮廓是14的父轮廓,但它同时又是12的子轮廓,所以它既有父亲的身份也有儿子的身份。
    至此,我们可以总结一下:在RETR_TREE模式下,同一个轮廓既可以作为别的轮廓的父轮廓,也可作为别的轮廓的子轮廓;在RETR_CCOMP模式下,由于只将轮廓组织为两层,所以同一个轮廓只要作了某个轮廓的父轮廓或子轮廓,那么就再也不能做别的轮廓的子轮廓或父轮廓了,再说清楚点,假如一个轮廓作了某个轮廓的父轮廓,那么它就再不能做别的轮廓的子轮廓;假如一个轮廓作了某个轮廓的子轮廓,那么它就再不能做别的轮廓的父轮廓。

    02-04-轮廓检测模式为CV_RETR_LIST时的结果及分析

    CV_RETR_LIST轮廓检测模式下,返回所有的轮廓,但是不建立轮廓的拓扑关系,我们还是以图片小白兔手绘图片“img_300_320.jpg”为例来看下这种情况下的运行结果。

    cnts, harch = cv.findContours(img_B, mode=cv.RETR_LIST, method=cv.CHAIN_APPROX_SIMPLE)
    
    • 1

    运行结果如下:
    在这里插入图片描述
    在这里插入图片描述
    从上面的运行结果我们可以看出,所有的子轮廓索引值和父轮廓索引值都为-1.说明的确没有建立轮廓的拓扑结构关系。

    03-轮廓近似方法参数method的检测结果的影响

    参数method表示算法采用的轮廓近似方法,有以下可选参数:
    CV_CHAIN_APPROX_NONE:存储所有的轮廓点。这种方法下,两个连续的轮廓点,要么是水平相邻的,要么是垂直相邻的, 要么是对角相邻的,即满足max(abs(x1-x2),abs(y2-y1))==1.
    CV_CHAIN_APPROX_SIMPLE: 压缩水平方向、垂直方向和对角线方向的中间点,只保留某个方向的终点坐标,例如一个矩形轮廓只需4个点来保持轮廓信息。
    CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS:使用The-Chinl链逼近算法中的一个。
    这个参数大家看了上面的解释叙述应该就比较清楚了,显然CV_CHAIN_APPROX_SIMPLE应该是比CV_CHAIN_APPROX_NONE检测到的轮廓的点要少的,我们看下是这不是这样。
    我们以图片ring.bmp为例,CV_CHAIN_APPROX_SIMPLE下的检测结果如下:

    cnts, harch = cv.findContours(img_B, mode=cv.RETR_LIST, method=cv.CHAIN_APPROX_SIMPLE)
    
    • 1

    在这里插入图片描述
    从上图的结果我们可以看到,CV_CHAIN_APPROX_SIMPLE下检测到的两个轮廓点的个数分别为156个和166个。

    CV_CHAIN_APPROX_NONE下的检测结果如下:

    cnts, harch = cv.findContours(img_B, mode=cv.RETR_LIST, method=cv.CHAIN_APPROX_NONE)
    
    • 1

    在这里插入图片描述
    从上图的结果我们可以看到,CV_CHAIN_APPROX_NONE下检测到的两个轮廓点的个数分别为322个和331个。

    至此,我们便对轮廓检测函数findContours()的四个最难理解的参数contours、hiararchy、mode、method有了深入细致的认识,在图像处理的任务中使用轮廓检测函数findContours()时便会更加有的放矢了。

  • 相关阅读:
    FFplay文档解读-43-视频过滤器十八
    UDP编程
    爆字段名中select * from users as a join users解释
    Mathorcup数学建模竞赛第二届-【妈妈杯】C题:地质灾害评价的数学模型分析(附带赛题解析&获奖论文及MATLAB代码)
    【案例】星环科技×某能源企业:数据中台实践
    DolphinScheduler 集群部署
    新概念英语(第二册)复习——Lesson 6 - Lesson10
    移动语义和完美转发浅析
    Web跨域问题
    上市企业管理层短视,新的视角,整理好的面板数据,stata或excel版本
  • 原文地址:https://blog.csdn.net/wenhao_ir/article/details/125537919