cv2中二值图轮廓与轮廓层级参数cv2.RETR_TREE
1. 二值图的轮廓
在使用cv2.findContours时,黑白二值图(像素值只有0或255)的轮廓都是以白色像素作为前景,黑色像素作为背景。看下面两个图(左图与右图同样大小都是200x200,左图是四周为黑色,中间为白色,右图为四周为白色,中间为黑色)。
在使用cv2.findContours查找轮廓时,其得到的轮廓其轮廓像素均是白色255.这里把像素弄成格子更直观。
原始黑色区域 轮廓为黑色区域外周围的像素
原始白色区域 轮廓为最边缘白色像素
2. cv2.RETR_TREE参数解释
cv2.findContours
函数有几个参数,下面是一些主要的参数:
- image: 输入图像,必须是单通道的二值图像。
- mode: 轮廓检索模式,常用的有:
cv2.RETR_EXTERNAL
: 只检索最外层的轮廓。cv2.RETR_LIST
: 检索所有轮廓,但每个轮廓是独立的,不建立层次关系。cv2.RETR_CCOMP
: 检索所有轮廓,并建立层次结构。cv2.RETR_TREE
: 检索所有轮廓,并重建完整的轮廓树。
- method: 轮廓近似方法,常用的有:
cv2.CHAIN_APPROX_SIMPLE
: 只保留轮廓的终点。cv2.CHAIN_APPROX_NONE
: 保留轮廓的所有点。
看下面这个图,只有白色与黑色像素。
使用cv2.drawContours后,得到轮廓contours后,绘制轮廓图如下:
现在按索引一个个绘制单个轮廓,可以发现,对应的索引其轮廓在图中如下位置:
contours[0]~contours[8]轮廓对应图中红色数字0~8的位置。现在通过代码分析各个轮廓之间的关系:
import cv2 image_path=r'test.png' # 读取图像 image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) # 将图像二值化 _, binary = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY) # 检测所有轮廓 contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)#返回所有轮廓及层级关系 # contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # contours, _ = cv2.findContours(binary, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) # print(hierarchy) for i, contour in enumerate(contours): # print(f"Contour {i}: {contour}") print(f"contour {i}: {hierarchy[0][i]}") # [next, previous, first_child, parent] output_image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) # 转换为 BGR 以便显示彩色轮廓 cv2.drawContours(output_image, contours, -1, (0, 255, 0), 2) cv2.imshow('Contours', output_image) cv2.waitKey(0) cv2.destroyAllWindows() cv2.imwrite('contours_detected.png', output_image)
其层级结构打印的如下:
contour 0: [ 4 -1 1 -1]
contour 1: [-1 -1 2 0]
contour 2: [-1 -1 3 1]
contour 3: [-1 -1 -1 2]
contour 4: [ 7 0 5 -1]
contour 5: [-1 -1 6 4]
contour 6: [-1 -1 -1 5]
contour 7: [-1 4 8 -1]
contour 8: [-1 -1 -1 7]
分析:对于cv2.RETR_TREE参数,
返回的层级信息中,每个轮廓层级的信息hierarchy包括四个部分,分别为[next, previous, first_child, parent],其代表意思如下:
- next: 同级别的下一个轮廓的索引。
- previous: 同级别的上一个轮廓的索引。
- first_child: 第一个子轮廓的索引。
- parent: 父轮廓的索引。
其中-1代表无该索引,我们分析几个轮廓(结合图来看)
contour 0为[ 4 -1 1 -1] ,其中next=4代表同级下一个轮廓为4,也就是contour 4,也对应图中的红色数字4对应的轮廓,previous=-1代表其没有同级别的上一个轮廓。first_child=1代表第一个子轮廓的索引为1,parent=-1表示没有父轮廓,与图中是一致的。
contour 3为 [-1 -1 -1 2],其中next=-1代表同级下,没有轮廓了,这里说的同级是同一父轮廓的情况下,后面的-1,-1,-1根据图可以看到是一致的。parent=2,代表其父轮廓为2,看图中,其被轮廓2直接包围。
如果我们现在要寻找纯黑或者纯白的也就是不包括空洞的区域,该如何找?可以利用cv2.RETR_TRE
E得到
hierarchy
层级关系,然后满足first_child=-1的轮廓。代码如下:
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 过滤出没有子轮廓的轮廓 contours_without_children = [] for i in range(len(hierarchy[0])): if hierarchy[0][i][2] == -1: # 检查first_child是否为-1 contours_without_children.append(contours[i])
如果我们想要上面绘制的红色轮廓中纯白色区域,该如何?通过前面,由于白色区域其轮廓所在像素为255,而黑色区域轮廓所在像素区域也为255,可以计算轮廓包围的区域的像素平均值,如果等于255则是白色区域,否则是黑色区域。代码如下:
mask = np.zeros_like(image)#image为单通道灰度图或二值图 filtered_contours=[] for contour in contours_without_children: # 创建一个单通道掩码 cv2.drawContours(mask, [contour], -1, 255, thickness=cv2.FILLED) # 计算轮廓区域的平均像素值 mean_val = cv2.mean(image, mask=mask)[0] # 判断内部是否为纯白色 if mean_val ==255: # 255表示纯白 filtered_contours.append(contour) # 清空掩码 mask.fill(0)
下面给出找出纯白色区域且没有子轮廓的轮廓(上图)的完整代码:
import cv2 import numpy as np image_path=r'test.png' # 读取图像 image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) # 将图像二值化 _, binary = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY) # 检测所有轮廓 contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)#返回所有轮廓及层级关系 output_image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) # 转换为 BGR 以便显示彩色轮廓 contours_without_children = [] for i in range(len(hierarchy[0])): if hierarchy[0][i][2] == -1: # 检查first_child是否为-1 contours_without_children.append(contours[i]) # 创建掩码 mask = np.zeros_like(image)#image为单通道灰度图或二值图 filtered_contours=[] for contour in contours_without_children: # 创建一个单通道掩码 cv2.drawContours(mask, [contour], -1, 255, thickness=cv2.FILLED) # 计算轮廓区域的平均像素值 mean_val = cv2.mean(image, mask=mask)[0] # 判断内部是否为纯白色 if mean_val ==255: # 255表示纯白 filtered_contours.append(contour) # 清空掩码 mask.fill(0) cv2.drawContours(output_image, filtered_contours, -1, (0, 0, 255), 2) cv2.imwrite('result.png', output_image)
代码中mean_val 的值加上打印语句时,会输出28.742, 255.0, 8.813,显然平均值为255的为纯白色区域。
小结:使用层级关系可以找到任意我们需要的轮廓,如果只需要计算外轮廓,可以设置参数为cv2.RETR_EXTERNAL。另外轮廓近似方法中,使用cv2.CHAIN_APPROX_SIMPLE可以减少获取轮廓点的数量,相比于cv2.CHAIN_APPROX_NONE保留所有轮廓像素点,前者只是在后者基础上像素点有所减少,没有增加新的像素点。
若存在不足或错误之处,欢迎指出与评论。