Python 如何在OpenCV中区分填充圆/轮廓和未填充圆/轮廓?

Python 如何在OpenCV中区分填充圆/轮廓和未填充圆/轮廓?,python,image,opencv,image-processing,computer-vision,Python,Image,Opencv,Image Processing,Computer Vision,我无法区分下面两个轮廓。cv2.contourArea为这两个参数提供了相同的值。Python中是否有任何函数来区分它们 要区分填充轮廓和未填充轮廓,可以在查找具有的轮廓时使用轮廓层次。具体地说,您可以选择返回包含图像拓扑信息的输出向量。有四种可能的模式: cv2.RETR_EXTERNAL-仅检索最外层轮廓,无层次结构 cv2.RETR_列表-检索所有轮廓,而不建立任何层次关系 cv2.RETR_CCOMP-检索所有轮廓并将其组织为两级层次结构。在顶层,有组件的外部边界。在第二层,有孔的边界。

我无法区分下面两个轮廓。cv2.contourArea为这两个参数提供了相同的值。Python中是否有任何函数来区分它们


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

cv2.RETR_EXTERNAL-仅检索最外层轮廓,无层次结构 cv2.RETR_列表-检索所有轮廓,而不建立任何层次关系 cv2.RETR_CCOMP-检索所有轮廓并将其组织为两级层次结构。在顶层,有组件的外部边界。在第二层,有孔的边界。如果连接部件的孔内有另一个轮廓,则仍将其放置在顶层 cv2.RETR_树-检索所有轮廓并重建嵌套轮廓的完整层次结构 理解轮廓层次

因此,有了这些信息,我们可以使用cv2.RETR\u CCOMP或cv2.RETR\u TREE返回层次结构列表。以这幅图像为例:

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

我们可以通过cv2.findContours函数调用的hierarchy数组获得轮廓层次的相关信息。假设我们这样调用函数:

(_, contours, hierarchy) = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
第三个返回值保存在代码的hierarchy变量中,它是一个三维NumPy数组,具有一行X列,深度为4。X列对应于函数找到的轮廓数。cv2.RETR_树参数使函数查找每个对象的内部轮廓和最外部轮廓。第0列对应于第一个轮廓,第1列对应于第二个轮廓,依此类推

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

[next, previous, first child, parent]
下一个索引是指此等高线层次级别中的下一个等高线,而上一个索引是指此等高线层次级别中的上一个等高线。第一个子索引指包含在此轮廓内的第一个轮廓。父索引是指包含此等高线的等高线。在所有情况下,值-1表示没有下一个、上一个、第一个子或父轮廓(视情况而定)。对于更具体的示例,以下是一些示例层次结构值。数值在方括号内,等高线索引位于每个条目之前。 如果打印出层次结构数组,将得到如下结果

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,并且该轮廓没有父轮廓没有包含此轮廓的轮廓。我们可以将层次数组中的信息可视化为七棵树,图像中每个骰子对应一棵树

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

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

结果

代码


在中,可以将设置为RETR_CCOMP以获得完整的轮廓层次。对于未填充的圆,您将得到两个轮廓,一个用于外圆,一个用于内圆。非常好的解释!
如此详细的答案,加上注释过的代码片段和图像,是其他人应该努力争取的,非常好的解释!
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()