如何在 OpenCV 中区分实心圆/轮廓和未实心圆/轮廓?

ssh*_*rma 4 python opencv image image-processing computer-vision

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

图:示例圆/轮廓

nat*_*ncy 10

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

  • 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)
Run Code Online (Sandbox Code Playgroud)

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

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

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

在接下来的指数指的是在这个轮廓的等级的一个轮廓,而以前的指数是指在这个轮廓的层级上一个轮廓。在第一个孩子的索引是指包含在此轮廓中的第一个轮廓。的父索引是指包含该轮廓的轮廓。在所有情况下,值-1表示没有next、previous、first child或parent轮廓,视情况而定。对于更具体的例子,这里有一些例子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 的下方。这两个轮廓代表最里面的轮廓,可能是噪声或其中一个点中的一些丢失的油漆。一旦我们了解了轮廓如何排列成层次结构,我们就可以执行更复杂的任务,例如除了计算图像中的对象数量外,还计算形状内的轮廓数量。


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

在此处输入图片说明

结果

在此处输入图片说明

代码

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()
Run Code Online (Sandbox Code Playgroud)

  • 很好的解释! (4认同)
  • 带有注释的代码片段和图像的如此详细的答案是其他人应该努力的(+1) (2认同)