【问题标题】:Understanding contour hierarchies: How to distinguish filled circle/contour and unfilled circle/contour in OpenCV?如何区分 OpenCV 中的实心圆/轮廓和空心圆/轮廓?
【发布时间】:2020-05-22 12:46:00
【问题描述】:

我无法区分以下两个轮廓。 cv2.contourArea 为两者提供相同的值。 Python中有什么函数可以区分它们吗?

【问题讨论】:

  • cv2.findContours 中,您可以将Retrieval Mode 设置为RETR_CCOMP 以获得完整的轮廓层次结构。对于未填充的圆圈,您将获得两个轮廓,一个用于外圈,一个用于内圈。

标签: python image opencv image-processing computer-vision


【解决方案1】:

要区分填充轮廓和未填充轮廓,您可以在使用cv2.findContours() 查找轮廓时使用轮廓层次结构。具体来说,您可以选择contour retrieval mode 以可选地返回包含有关图像拓扑信息的输出向量。有四种可能的模式:

  • cv2.RETR_EXTERNAL - 仅检索极端外部轮廓(无层次结构)
  • cv2.RETR_LIST - 检索所有轮廓而不建立任何层次关系
  • cv2.RETR_CCOMP - 检索所有轮廓并将它们组织成两级层次结构。在顶层,有组件的外部边界。在第二层,有孔的边界。如果连接组件的孔内有另一个轮廓,它仍然放在顶层
  • cv2.RETR_TREE - 检索所有轮廓并重建嵌套轮廓的完整层次结构

了解等高线层次结构

因此,有了这些信息,我们可以使用cv2.RETR_CCOMPcv2.RETR_TREE 来返回层次结构列表。以这张图片为例:

当我们使用cv2.RETR_TREE 参数时,轮廓按层次排列,每个对象的最外层轮廓位于顶部。向下移动层次结构,每个新的轮廓级别代表每个对象的下一个最里面的轮廓。在上图中,图像中的轮廓被着色以表示返回的轮廓数据的层次结构。最外面的轮廓是红色的,它们位于层次结构的顶部。下一个最里面的轮廓——在这种情况下是骰子点——是绿色的。

我们可以通过 cv2.findContours 函数调用中的层次结构数组获取有关轮廓层次结构的信息。假设我们这样调用函数:

(_, contours, hierarchy) = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

第三个返回值,保存在这段代码的hierarchy变量中,是一个三维NumPy数组,有一行X列,“深度”为4。X列对应到函数找到的轮廓数。 cv2.RETR_TREE 参数使函数找到每个对象的内部轮廓以及最外层轮廓。第 0 列对应于第一个轮廓,第 1 列对应于第二个等高线。

根据此方案,每一列都有一个四元素整数数组,表示其他轮廓的索引:

[next, previous, first child, parent]

next 索引引用此轮廓层次级别中的下一个轮廓,而 previous 索引引用此轮廓层次级别中的前一个轮廓。 first child 索引是指包含在此轮廓内的第一个轮廓。 parent 索引是指包含此轮廓的轮廓。在所有情况下,-1 的值表示没有 nextpreviousfirst childparent em> 轮廓,视情况而定。对于更具体的示例,这里有一些示例 hierarchy 值。这些值在方括号中,轮廓的索引在每个条目之前。 如果你打印出层次结构数组,你会得到这样的东西

0:  [ 6 -1  1 -1]   18: [19 -1 -1 17]
1:  [ 2 -1 -1  0]   19: [20 18 -1 17]
2:  [ 3  1 -1  0]   20: [21 19 -1 17]
3:  [ 4  2 -1  0]   21: [22 20 -1 17]
4:  [ 5  3 -1  0]   22: [-1 21 -1 17]
5:  [-1  4 -1  0]   23: [27 17 24 -1]
6:  [11  0  7 -1]   24: [25 -1 -1 23]
7:  [ 8 -1 -1  6]   25: [26 24 -1 23]
8:  [ 9  7 -1  6]   26: [-1 25 -1 23]
9:  [10  8 -1  6]   27: [32 23 28 -1]
10: [-1  9 -1  6]   28: [29 -1 -1 27]
11: [17  6 12 -1]   29: [30 28 -1 27]
12: [15 -1 13 11]   30: [31 29 -1 27]
13: [14 -1 -1 12]   31: [-1 30 -1 27]
14: [-1 13 -1 12]   32: [-1 27 33 -1]
15: [16 12 -1 11]   33: [34 -1 -1 32]
16: [-1 15 -1 11]   34: [35 33 -1 32]
17: [23 11 18 -1]   35: [-1 34 -1 32]

第一个轮廓的条目是[6, -1, 1, -1]。这代表最外层轮廓中的第一个;请注意,轮廓没有特定的顺序,例如,默认情况下它们不会从左到右存储。该条目告诉我们下一个骰子轮廓是索引为 6 的轮廓,列表中没有前一个轮廓,该轮廓内的第一个轮廓具有索引 1,并且该轮廓没有父轮廓(没有包含这个)。我们可以将hierarchy 数组中的信息可视化为七棵树,每棵树对应图像中的骰子。

最外面的七个轮廓都是那些没有父轮廓的轮廓,即在其hierarchy 条目的第四个字段中具有-1 值的轮廓。 “根”之一下方的每个子节点代表最外轮廓内的轮廓。请注意等高线 13 和 14 如何位于图中的等高线 12 下方。这两个轮廓代表最里面的轮廓,可能是其中一个点中的噪声或一些丢失的油漆。一旦我们了解了轮廓是如何排列成层次结构的,我们就可以执行更复杂的任务,例如除了计算图像中对象的数量之外,还计算形状中轮廓的数量。


回到您的问题,我们可以使用层次结构来区分内轮廓和外轮廓,以确定轮廓是填充还是未填充。我们可以将填充轮廓定义为没有子元素的轮廓,而将未填充轮廓定义为至少一个子元素。因此,使用此输入图像的屏幕截图(删除框):

结果

代码

import cv2
import numpy as np

# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Filter using contour hierarchy
cnts, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
hierarchy = hierarchy[0]
for component in zip(cnts, hierarchy):
    currentContour = component[0]
    currentHierarchy = component[1]
    x,y,w,h = cv2.boundingRect(currentContour)
    # Has inner contours which means it is unfilled
    if currentHierarchy[3] > 0:
        cv2.putText(image, 'Unfilled', (x,y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (36,255,12), 2)
    # No child which means it is filled
    elif currentHierarchy[2] == -1:
        cv2.putText(image, 'Filled', (x,y-5), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (36,255,12), 2)

cv2.imshow('image', image)
cv2.waitKey()

【讨论】:

  • 很好的解释!
  • 如此详细的答案以及带注释的代码 sn-ps 和图像是其他人应该努力争取的 ?(+1)
  • 这个答案是金!感谢您的所有努力@nathancy
猜你喜欢
  • 1970-01-01
  • 2014-07-27
  • 2018-09-09
  • 1970-01-01
  • 2022-12-04
  • 2019-05-07
  • 2010-11-01
  • 2019-07-08
  • 2013-08-23
相关资源
最近更新 更多