【问题标题】:Opencv not finding all contoursOpencv没有找到所有轮廓
【发布时间】:2019-01-15 21:28:35
【问题描述】:

我正在尝试查找此图像的轮廓,但 findContours 方法仅返回 1 轮廓,轮廓在 图像 2 中突出显示强>。我正在尝试找到 所有 外部轮廓,例如数字在里面的这些圆圈。我究竟做错了什么?我能做些什么来完成它?

图 1

图 2

下面是我的代码的相关部分。

thresh = cv2.threshold(image, 0, 255,
                           cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]

cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
                            cv2.CHAIN_APPROX_SIMPLE)

当我将cv2.RETR_EXTERNAL 更改为cv2.RETR_LIST 时,它似乎两次检测到相同的轮廓或类似的东西。图 3 显示了第一次检测到圆的边界,然后再次检测到它,如图 4 所示。我试图只找到这些圆的外边界。我怎样才能做到这一点?

图片3

图片4

【问题讨论】:

    标签: python opencv opencv-contour optical-mark-recognition


    【解决方案1】:

    问题在于您在函数调用中使用的标志cv2.RETR_EXTERNAL。如the OpenCV documentation 中所述,这只返回外部轮廓。

    使用标志cv2.RETR_LIST 可以得到图像中的所有轮廓。由于您尝试检测环,因此此列表将包含这些环的内部和外部轮廓。

    要过滤圆的外边界,您可以使用cv2.contourArea() 找到两个重叠轮廓中较大的一个。

    【讨论】:

    • 当我尝试 cv2.RETR_LIST 时它可以工作,但它似乎两次返回相同的轮廓。我试图只找到这些圆圈的外边界。
    【解决方案2】:

    我不确定这是否真的是你所期望的,但如果这样的话,有很多方法可以帮助 findContours 完成它的工作。 这是我经常使用的一种方式。

    1. 将您的图像转换为灰色

      Ig = cv2.cvtColor(I,cv2.COLOR_BGR2GRAY)

    1. 阈值

    背景和前景值在颜色方面看起来很均匀,但在局部它们不是,所以我应用了基于 Otsu 方法的阈值以对强度进行二值化。

     _,It = cv2.threshold(Ig,0,255,cv2.THRESH_OTSU)
    

    1. 索贝尔震级

    为了只提取轮廓,我处理了 Sobel 边缘检测器的幅度。

    sx = cv2.Sobel(It,cv2.CV_32F,1,0)
    
    sy = cv2.Sobel(It,cv2.CV_32F,0,1)
    
    m = cv2.magnitude(sx,sy)
    
    m = cv2.normalize(m,None,0.,255.,cv2.NORM_MINMAX,cv2.CV_8U)
    

    1. 细化(可选)

    我使用ximgproc中实现的细化功能。

    细化的目的是将轮廓厚度减少到尽可能少的像素。

     m = cv2.ximgproc.thinning(m,None,cv2.ximgproc.THINNING_GUOHALL)
    

    1. 最后一步findContours

      _,contours,hierarchy = cv2.findContours(m,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
      disp = cv2.merge((m,m,m)
      disp = cv2.drawContours(disp,contours,-1,hierarchy=hierarchy,color=(255,0,0))    
      

    希望对您有所帮助。

    我认为基于 SVM 或 CNN 的方法可能更稳健。 你可以找到一个例子hereThis one 也可能很有趣。

    -编辑-

    我找到了一种更简单的方法来实现您的目标。

    像以前一样,在加载图像后应用阈值确保图像是二进制的。 通过使用按位非运算反转图像,轮廓在黑色背景上变为白色。 应用cv2.connectedComponentsWithStats 返回(除其他外)一个标签矩阵,其中源中的每个连接的白色区域都被分配了一个唯一的标签。 然后根据标签应用findContours,就可以给出每个区域的外部轮廓。

    import numpy as np
    import cv2
    from matplotlib import pyplot as plt
    
    
    
    
    I = cv2.imread('/home/smile/Downloads/ext_contours.png',cv2.IMREAD_GRAYSCALE)
    
    _,I = cv2.threshold(I,0.,255.,cv2.THRESH_OTSU)
    I = cv2.bitwise_not(I)
    
    _,labels,stats,centroid = cv2.connectedComponentsWithStats(I)
    
    result = np.zeros((I.shape[0],I.shape[1],3),np.uint8)
    
    for i in range(0,labels.max()+1):
        mask = cv2.compare(labels,i,cv2.CMP_EQ)
    
        _,ctrs,_ = cv2.findContours(mask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    
        result = cv2.drawContours(result,ctrs,-1,(0xFF,0,0))
    
    plt.figure()
    plt.imshow(result)  
    

    附:在函数findContours 返回的输出中,有一个层次矩阵。 通过分析该矩阵可以达到相同的结果,但它有点复杂,如 here 的解释。

    【讨论】:

    • 感谢有趣的链接和分享cv2.ximgproc 模块。直到今天才知道! +1
    【解决方案3】:

    我建议使用适当的参数应用霍夫圆变换,而不是寻找轮廓。

    寻找轮廓是一项挑战。反转二进制图像后,圆圈为白色。 OpenCV 沿着圆的外侧找到轮廓。此外,由于存在诸如“A”和“B”之类的字母,因此将再次沿着字母的外侧和孔内找到轮廓。您可以使用适当的层次标准找到轮廓,但这仍然很乏味。

    这是我通过寻找轮廓并使用层次结构所尝试的:

    代码:

    #--- read the image, convert to gray and obtain inverse binary image ---
    img = cv2.imread('keypad.png', 1)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)
    
    #--- find contours ---
    _, contours, hierarchy = cv2.findContours(binary, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
    
    #--- copy of original image ---
    img2 = img.copy()
    
    #--- select contours having a parent contour and append them to a list ---
    l = []
    for h in hierarchy[0]:
        if h[0] > -1 and h[2] > -1:
            l.append(h[2])
    
    #--- draw those contours ---
    for cnt in l:
        if cnt > 0:
            cv2.drawContours(img2, [contours[cnt]], 0, (0,255,0), 2)
    
    cv2.imshow('img2', img2)
    

    更多关于轮廓及其层级关系的信息please refer this

    更新

    我有一个相当粗略的方法来忽略不需要的轮廓。找出列表l中所有轮廓的平均面积,并画出高于平均值的那些:

    代码:

    img3 = img.copy()
    a = 0
    for j, i in enumerate(l):
        a = a + cv2.contourArea(contours[i])
    mean_area = int(a/len(l))
    
    for cnt in l:
        if (cnt > 0) & (cv2.contourArea(contours[cnt]) > mean_area):
            cv2.drawContours(img3, [contours[cnt]], 0, (0,255,0), 2)
    
    cv2.imshow('img3', img3)
    

    【讨论】:

      【解决方案4】:

      您可以通过此功能仅选择外边框:

      def _select_contours(contours, hierarchy):
          """select contours of the second level"""
          # find the border of the image, which has no father
          father_i = None
          for i, h in enumerate(hierarchy):
              if h[3] == -1:
                  father_i = i
                  break
          # collect its sons
          new_contours = []
          for c, h in zip(contours, hierarchy):
              if h[3] == father_i:
                  new_contours.append(c)
          return new_contours 
      

      请注意,您应该在 cv2.findContours() 中使用 cv2.RETR_TREE 来获取 轮廓层次结构

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-02-23
        • 2011-11-27
        • 1970-01-01
        • 1970-01-01
        • 2012-11-06
        • 1970-01-01
        相关资源
        最近更新 更多