前言
? 接前一篇,最近几天关注的问题是图像的轮廓问题,所以这一篇主要写的是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个轮廓,每个矩形的外框都是两个轮廓,里面一个外面一个,我们可以把这个称作外轮廓和内轮廓,这个在后面的介绍中还要用到。
- 轮廓与轮廓之间是有拓扑结构关系的,我理解是轮廓之间的一个包含关系,比如刚才的外轮廓就是包含内轮廓的,里面小矩形的轮廓就是包含在外轮廓之内的。可以在图形处理中做一些空洞或者其他方面的应用
findContours函数
? 在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是一个<x, y>的坐标点。
- ContourApproximationModes,官网上的介绍如下:
这个参数主要是影响输出参数contours,后面两种用得少,主要是前面两种。这两个参数的主要区别就在用于描述轮廓的点是否进行压缩,如果使用SIMPLE这个参数,那么理论上一个矩形轮廓只需要四个点就可以了,而不压缩的话就会把这个矩形轮廓线上的所有点全部输出。
我们把上面的代码改成
contours, hierarchy = cv2.findContours(bi_img, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
重新看一下输出:
可以看到,这个轮廓从92个点压缩成了8个点,应该是每条线用两个点来表示。这里我的一个疑问是:这个轮廓应该是一个矩形框才对,为什么不是4个点来描述。想了半天想到一种可能,就是我这个里面的轮廓是通过rectangle函数画出来的,可能是矩形框的四个顶点并没有包含在轮廓之内,也就是说这个轮廓是有4条没有连接起来的线段组成,而不是一个完整的矩形框。
- hierarchy是对应contours中的每个元素,每个元素都有四个属性分量:
- 0分量:拓扑中同一级别的下一个
- 1分量:拓扑中同一级别的前一个
- 2分量:拓扑中第一个child
- 3分量:拓扑中的上一级别(parent)
这样看基本上是蒙的,还是针对上面的图,针对每个参数进行输出分析。
参考官网上的描述,RETR_LIST不会构建上下级关系,所有的轮廓都是平铺,从数据中可以看到,基本上就是按照数组的顺序,填充了hierarchy的第0分量和第1分量,其他的都是-1。
利用drawContours(参考后面一个小节)来查看一下:
相当于只找出了最外成的轮廓,从拓扑上来说,这是最外层的。
根据每个的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颜色,第五个是轮廓的粗细。
|