
- 前言
- 文件 *** 作
- os.walk
- 图像轮廓问题
- 轮廓的基本逻辑
- findContours函数
- drawContours函数
接前一篇,最近几天关注的问题是图像的轮廓问题,所以这一篇主要写的是findContours和drawContours两个函数,另外还有和文件 *** 作相关的一个os.walk函数。
文件 *** 作 os.walk-
基本逻辑
walk会以深度优先的方式来遍历整个文件夹,也就是先把某一个文件夹及其子文件夹全部遍历一遍。每次遍历返回一个三元组(parent, dirs, files),分别代表这次遍历过程的父目录、当前目录下的子目录列表、当前目录下的文件列表。
我的代码啊逻辑是将所有最底层的目录(最底层的目录中包含一个特定文件)收集成一个列表,代码如下:
def loadAllFileInfos(src): fileDirs = [] for parent, dirs, fileNames in os.walk(src): for name in fileNames: if name == "img.xml": print(parent) fileDirs.append(parent) -
文件夹中的"/“和”\“的处理,不区分,但是可以考虑使用”\\"的方式
-
文件夹中包含中文
判断:
def is_contains_chinese(strs): for _char in strs: if '\u4e00' <= _char <= '\u9fa5': return True return False修改,注意的两点:
- parent是绝对路径而不是相对路径,本来想根据parent的特征来修改,后来逻辑上行不通
- os.rename是需要绝对路径,所以需要使用os.path.join进行一下拼装
def change(src): for parent, dirs, fileNames in os.walk(src): i = 0 for dir in dirs: if is_contains_chinese(dir): i = i + 1 src = os.path.join(parent, dir) dst = os.path.join(parent, "case_"+str(i)) os.rename(src, dst)
为了更好的理解opencv里处理轮廓的两个函数,我自己构造了一张比较简单的图:
构造代码:
img = np.zeros((500, 500, 3), dtype="uint8")
cv2.rectangle(img, (100, 100), (200, 200), (255, 255, 255), 5)
cv2.rectangle(img, (120, 120), (140, 160), (255, 255, 255), 5)
cv2.rectangle(img, (160, 120), (180, 160), (255, 255, 255), 5)
cv2.rectangle(img, (300, 300), (400, 400), (255, 255, 255), 5)
cv2.rectangle(img, (320, 320), (350, 350), (255, 255, 255), 5)
在图中我画了5个矩形,每个矩形的线宽是5个像素,这里面有两个重要的概念:
- 5个矩形,总共形成了10个轮廓,每个矩形的外框都是两个轮廓,里面一个外面一个,我们可以把这个称作外轮廓和内轮廓,这个在后面的介绍中还要用到。
- 轮廓与轮廓之间是有拓扑结构关系的,我理解是轮廓之间的一个包含关系,比如刚才的外轮廓就是包含内轮廓的,里面小矩形的轮廓就是包含在外轮廓之内的。可以在图形处理中做一些空洞或者其他方面的应用
在python中的基本调用方法:
contours, hierarchy = cv2.findContours(bi_img, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
对应的函数原型就是:
contours, hierarchy = cv2.findContours(bi_img, RetrievalModes, ContourApproximationModes),这个里面最重要的就是理解两个入参和hierarchy这个出参。
- 首先看一下这个函数在上面那个图上的情况(使用的就是上面的代码调用,因为改变参数的话,输出不太一样):
可以看到,函数总共输出了10条轮廓数据,从shape上来看,第一条轮廓是92个Point,每个Point是一个
- ContourApproximationModes,官网上的介绍如下:
这个参数主要是影响输出参数contours,后面两种用得少,主要是前面两种。这两个参数的主要区别就在用于描述轮廓的点是否进行压缩,如果使用SIMPLE这个参数,那么理论上一个矩形轮廓只需要四个点就可以了,而不压缩的话就会把这个矩形轮廓线上的所有点全部输出。
我们把上面的代码改成
contours, hierarchy = cv2.findContours(bi_img, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
重新看一下输出:
可以看到,这个轮廓从92个点压缩成了8个点,应该是每条线用两个点来表示。这里我的一个疑问是:这个轮廓应该是一个矩形框才对,为什么不是4个点来描述。想了半天想到一种可能,就是我这个里面的轮廓是通过rectangle函数画出来的,可能是矩形框的四个顶点并没有包含在轮廓之内,也就是说这个轮廓是有4条没有连接起来的线段组成,而不是一个完整的矩形框。
-
轮廓的拓扑结构:hierarchy出参和入参RetrievalModes
这两个参数可以一起来看,RetrievalModes这个参数就控制了hierarchy的输出。先看看官网的描述
- hierarchy是对应contours中的每个元素,每个元素都有四个属性分量:
- 0分量:拓扑中同一级别的下一个
- 1分量:拓扑中同一级别的前一个
- 2分量:拓扑中第一个child
- 3分量:拓扑中的上一级别(parent)
这样看基本上是蒙的,还是针对上面的图,针对每个参数进行输出分析。
-
先看RETR_LIST:
contours, hierarchy = cv2.findContours(bi_img, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)输出的结构为:
参考官网上的描述,RETR_LIST不会构建上下级关系,所有的轮廓都是平铺,从数据中可以看到,基本上就是按照数组的顺序,填充了hierarchy的第0分量和第1分量,其他的都是-1。
-
RETR_EXTERNAL,从官网上看就是只输出外轮廓。
输出结果,只有两个轮廓了:
利用drawContours(参考后面一个小节)来查看一下:
相当于只找出了最外成的轮廓,从拓扑上来说,这是最外层的。
-
RETR_CCOMP,官网上的描述是:形成一个两层的结构,第一层是最外层的轮廓,然后是这层轮廓里面的轮廓。如果有第三层,那么都放到最外层去。
在我构造的图中,idx=2和idx=8的轮廓是最外层的图,就是RETR_EXTERNAL中的这两个轮廓,再打开hierarchy的输出结果看一下:
根据每个的3号分量,等于2或者8的就是idx=3和idx=9的轮廓,用drawContours画出来就是:
可以看出来,就是刚才最外面轮廓的里面一层。再来看看其他几个,除了idx=2,3,8,9的轮廓除外,还有parent=-1的是idx=0,4,6,画出来之后就是:
相当于也是外层轮廓,这些也是放在了顶层,因为这个参数控制它只能有两层结构,另外的三个内层轮廓的parent就是分别等于0,4,6了。
- RETR_TREE,这个参数就是形成一个从里到外的树形结构:
顺序和上面的顺序是不一样的,这里能看出来:
- 最外层的idx = 0, 4
- 第二层(parent = 0, 4)的是1, 5
- 第三层是2, 6, 8
- 第四层是3, 7, 9
用不同的颜色画一下就是:
展现了明显的层次结构关系。
drawContours函数
上面提到的绘制轮廓的方法,这个函数比较简单。
调用方法:
cv2.drawContours(img, contours, 3, (255, 255, 0), 1)
- img必须是三通道图
- 第三个参数是绘制contours数组中的第几个轮廓
- 第四个是BGR颜色,第五个是轮廓的粗细。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)