【问题标题】:Python OpenCV Contour Tree Hierarchy StructurePython OpenCV 轮廓树层次结构
【发布时间】:2022-04-17 11:45:09
【问题描述】:

我正在尝试使用 OpenCV 实现在 python 中找到的 here 算法。

我正在尝试实现算法的一部分,根据它们拥有的内部边界的数量来删除不相关的边缘边界。

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

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

我目前的来源:

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()

我认为使用cv2.RETR_TREE 会给我一个很好的轮廓嵌套数组,但似乎并非如此。如何检索轮廓的树结构?

【问题讨论】:

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


【解决方案1】:

这里的主要混淆可能是返回的层次结构是一个 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()

【讨论】:

  • 感谢您的回答。我也遇到了获取重复轮廓的相同问题。我发到 OpenCV 组但没有听到任何消息:tech.groups.yahoo.com/group/OpenCV/message/88940
  • 我已经实现了算法,并且我知道如何实现。它似乎工作得很好。查看github.com/jasonlfunk/ocr-text-extraction
  • 看起来不错。您可能要检查的另一件事是“笔画宽度变换”。谷歌它...第一个点击(Epshtein 的论文)链接现在已损坏,您可以在“快速查看”中看到它
  • @Jasonlfunk,您的脚本仍然很棒,尽管已经过去了很多年。你有脚本的更新版本吗?
  • 哦哇刚刚意识到这个帖子有多老了..
【解决方案2】:

了解等高线层次结构

使用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 下方。这两个轮廓代表最里面的轮廓,可能是其中一个点中的噪声或一些丢失的油漆。


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

【讨论】:

  • 关于轮廓层次结构的精彩讨论。感谢您提供所有详细信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-23
  • 1970-01-01
  • 2016-09-21
  • 2018-10-02
  • 1970-01-01
相关资源
最近更新 更多