【发布时间】:2020-05-22 12:46:00
【问题描述】:
【问题讨论】:
-
在cv2.findContours 中,您可以将Retrieval Mode 设置为
RETR_CCOMP以获得完整的轮廓层次结构。对于未填充的圆圈,您将获得两个轮廓,一个用于外圈,一个用于内圈。
标签: python image opencv image-processing computer-vision
【问题讨论】:
RETR_CCOMP 以获得完整的轮廓层次结构。对于未填充的圆圈,您将获得两个轮廓,一个用于外圈,一个用于内圈。
标签: python image opencv image-processing computer-vision
要区分填充轮廓和未填充轮廓,您可以在使用cv2.findContours() 查找轮廓时使用轮廓层次结构。具体来说,您可以选择contour retrieval mode 以可选地返回包含有关图像拓扑信息的输出向量。有四种可能的模式:
cv2.RETR_EXTERNAL - 仅检索极端外部轮廓(无层次结构)cv2.RETR_LIST - 检索所有轮廓而不建立任何层次关系cv2.RETR_CCOMP - 检索所有轮廓并将它们组织成两级层次结构。在顶层,有组件的外部边界。在第二层,有孔的边界。如果连接组件的孔内有另一个轮廓,它仍然放在顶层cv2.RETR_TREE - 检索所有轮廓并重建嵌套轮廓的完整层次结构了解等高线层次结构
因此,有了这些信息,我们可以使用cv2.RETR_CCOMP 或cv2.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 的值表示没有 next、previous、first child 或 parent 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()
【讨论】: