Python OpenCV轮廓树层次结构

jas*_*unk 10 python opencv

我想实现发现该算法在这里的蟒蛇与OpenCV的.

我正在尝试实现算法的一部分,该部分基于它们具有的内部边界的数量来移除不相关的边界.

  • 如果当前边缘边界恰好具有一个或两个内边缘边界,则可以忽略内部边界
  • 如果当前边缘边界具有两个以上的内边缘边界,则可以忽略它

我无法确定从图像中提取的轮廓的树形结构.

我目前的来源:

import cv2

# Load the image
img = cv2.imread('test.png')
cv2.copyMakeBorder(img, 50,50,50,50,cv2.BORDER_CONSTANT, img, (255,255,255))

# Split out each channel
blue = cv2.split(img)[0]
green = cv2.split(img)[1]
red = cv2.split(img)[2]

# Run canny edge detection on each channel
blue_edges = cv2.Canny(blue, 1, 255)
green_edges = cv2.Canny(green, 1, 255)
red_edges = cv2.Canny(red, 1, 255)

# Join edges back into image
edges = blue_edges | green_edges | red_edges

# Find the contours
contours,hierarchy = cv2.findContours(edges.copy(),cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

# For each contour, find the bounding rectangle and draw it
for cnt in contours:
    x,y,w,h = cv2.boundingRect(cnt)
    cv2.rectangle(edges,(x,y),(x+w,y+h),(200,200,200),2)

# Finally show the image
cv2.imshow('img',edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
Run Code Online (Sandbox Code Playgroud)

我假设使用RETR_TREE会给我一个漂亮的嵌套轮廓数组,但似乎并非如此.如何检索轮廓的树形结构?

nat*_*ncy 17

了解轮廓层次结构

使用 查找二值图像中的轮廓时cv2.findContours(),您可以使用轮廓层次结构来选择和提取图像中的特定轮廓。具体来说,您可以选择轮廓检索模式来选择性地返回包含有关图像拓扑信息的输出向量。有四种可能的模式:

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

轮廓树结构

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

在此输入图像描述

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

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

(_, contours, hierarchy) = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
Run Code Online (Sandbox Code Playgroud)

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

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

[next, previous, first child, parent]
Run Code Online (Sandbox Code Playgroud)

下一个索引指的是该轮廓的层次级别中的下一个轮廓,而前一个索引指的是该轮廓的层次级别中的上一个轮廓。第一个子索引指的是包含在该轮廓内的第一个轮廓。父索引指的是包含该轮廓的轮廓。在所有情况下,值-1表示不存在下一个一个、第一个子轮廓或轮廓(视情况而定)。对于更具体的示例,这里有一些示例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]
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

七个最外面的轮廓都是没有父轮廓的轮廓,即-1在其条目的第四个字段中具有值的轮廓hierarchy。“根”之一下方的每个子节点代表最外轮廓内的轮廓。请注意图中轮廓 13 和 14 位于轮廓 12 下方。这两个轮廓代表最里面的轮廓,可能是其中一个点中的噪声或一些丢失的油漆。


一旦我们了解了轮廓如何排列成层次结构,我们就可以执行更复杂的任务,例如除了图像中的对象数量之外还计算形状内的轮廓数量。根据您选择的检索模式,您将可以完全访问图像的拓扑并能够控制轮廓的树结构。

  • 关于轮廓层次结构的精彩讨论。感谢您提供所有详细信息。 (2认同)

bel*_*kev 14

这里的主要困惑可能是返回的层次结构是一个numpy数组,其维数超过了必要的数量.最重要的是,它看起来像Python FindContours函数返回一个元素列表的元组,以及层次结构的NDARRAY ...

通过采用层次结构[0],您可以获得更合理的层次结构信息,这些信息更符合C文档.然后,例如,利用轮廓拉链将是合适的形状.

下面是一个示例,将在此图像上绘制绿色的最外面的矩形和红色的最里面的矩形:

在此输入图像描述

输出:

在此输入图像描述

顺便提一下,请注意OpenCV文档中的措辞有点含糊不清,但hierarchyDataOfAContour[2]描述了该轮廓的子项(如果它是负的则是内部轮廓),并hierarchyDataOfAContour[3]描述该轮廓的父项(如果它是负的)那就是外形轮廓).

还要注意:我考虑实现你在OCR文件中提到的算法,我看到FindContours给了我很多重复的几乎相同的轮廓.如本文所述,这将使"边缘盒"的发现变得复杂.这可能是因为Canny阈值太低(请注意我正如本文所描述的那样玩弄它们),但可能有某种方法可以减少这种影响,或者只看一下所有角落的平均偏差盒子并消除重复......

import cv2
import numpy

# Load the image
img = cv2.imread("/ContourTest.PNG")

# Split out each channel
blue, green, red = cv2.split(img)

def medianCanny(img, thresh1, thresh2):
    median = numpy.median(img)
    img = cv2.Canny(img, int(thresh1 * median), int(thresh2 * median))
    return img

# Run canny edge detection on each channel
blue_edges = medianCanny(blue, 0.2, 0.3)
green_edges = medianCanny(green, 0.2, 0.3)
red_edges = medianCanny(red, 0.2, 0.3)

# Join edges back into image
edges = blue_edges | green_edges | red_edges

# Find the contours
contours,hierarchy = cv2.findContours(edges, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

hierarchy = hierarchy[0] # get the actual inner list of hierarchy descriptions

# For each contour, find the bounding rectangle and draw it
for component in zip(contours, hierarchy):
    currentContour = component[0]
    currentHierarchy = component[1]
    x,y,w,h = cv2.boundingRect(currentContour)
    if currentHierarchy[2] < 0:
        # these are the innermost child components
        cv2.rectangle(img,(x,y),(x+w,y+h),(0,0,255),3)
    elif currentHierarchy[3] < 0:
        # these are the outermost parent components
        cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),3)

# Finally show the image
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Run Code Online (Sandbox Code Playgroud)