cv2中二值图轮廓与轮廓层级参数cv2.RETR_TREE

1. 二值图的轮廓

  在使用cv2.findContours时,黑白二值图(像素值只有0或255)的轮廓都是以白色像素作为前景,黑色像素作为背景。看下面两个图(左图与右图同样大小都是200x200,左图是四周为黑色,中间为白色,右图为四周为白色,中间为黑色)。

                                                               

  在使用cv2.findContours查找轮廓时,其得到的轮廓其轮廓像素均是白色255.这里把像素弄成格子更直观。

                                                                                      

                                                                                                 原始黑色区域                                                                                        轮廓为黑色区域外周围的像素

                                                                                            

                                                                                                    原始白色区域                                                                                          轮廓为最边缘白色像素

2. cv2.RETR_TREE参数解释

  cv2.findContours 函数有几个参数,下面是一些主要的参数:

  1. image: 输入图像,必须是单通道的二值图像。
  2. mode: 轮廓检索模式,常用的有:
    • cv2.RETR_EXTERNAL: 只检索最外层的轮廓。
    • cv2.RETR_LIST: 检索所有轮廓,但每个轮廓是独立的,不建立层次关系。
    • cv2.RETR_CCOMP: 检索所有轮廓,并建立层次结构。
    • cv2.RETR_TREE: 检索所有轮廓,并重建完整的轮廓树。
  3. 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_TREE得到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保留所有轮廓像素点,前者只是在后者基础上像素点有所减少,没有增加新的像素点。

 

  若存在不足或错误之处,欢迎指出与评论。

posted @ 2024-07-07 14:13  wancy  阅读(34)  评论(0编辑  收藏  举报