Opencv 找不到所有轮廓。

3
我正在尝试找到这张图像的轮廓,但是方法findContours只返回1个轮廓,该轮廓在图像2中突出显示。我想找到所有外部轮廓,例如这些数字位于内部的圆形。我做错了什么?我应该怎么做才能实现它?

图像1:

enter image description here

图片2:

enter image description here

以下是我代码的相关部分。
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所示再次被检测到。 我正在尝试仅查找这些圆的外部边框。 我该如何实现? enter image description here 图像3 enter image description here 图像4
4个回答

5

我不确定这是否符合您的期望,但在这种情况下有许多方法可以帮助findContours完成其工作。以下是我经常使用的一种方式。

  1. 将图像转换为灰度

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

gray image

  1. 阈值化

背景和前景的颜色看起来相当均匀,但在局部不是这样的,因此我使用基于大津法的阈值化方法来将强度二值化。

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

image thresh

  1. Sobel算子的幅度

为了仅提取轮廓,我处理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)

sobel

  1. 细化(可选)

我使用实现在ximgproc中的细化函数。

细化的目的是将轮廓线条粗细尽可能减少到最少像素。

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

thinned_image

  1. Final Step 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))    
    

ctrs

希望有所帮助。
我认为基于SVM或CNN的方法可能更加稳健。您可以在这里找到一个示例。这个也可能很有趣。
-编辑-
我发现一种更简单的方法来实现您的目标。
与之前一样,在加载图像后应用阈值,确保图像是二进制的。通过使用位非操作反转图像,轮廓变成了黑色背景上的白色。应用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)  

external_boundaries

P.S. 在函数 findContours 返回的输出中,有一个层次矩阵。 通过分析该矩阵可能会得到相同的结果,但这要稍微复杂一些,可以在 这里 找到解释。


感谢分享这些有趣的链接和cv2.ximgproc模块。直到今天我才知道它的存在!!!+1 - Jeru Luke

5
问题出在你在函数调用时使用了标志 cv2.RETR_EXTERNAL。正如OpenCV文档中所述,它只返回外轮廓。
如果您想检测圆环,可以使用标志 cv2.RETR_LIST 来获取图像中的所有轮廓,这个列表将包含这些圆环的内部轮廓和外部轮廓。
为了筛选圆的外边界,您可以使用 cv2.contourArea() 来寻找两个重叠轮廓中的大轮廓。

1
当我尝试使用cv2.RETR_LIST时,它能够正常运行,但它似乎返回了相同的轮廓两次。我正在尝试仅找到这些圆的外边界。 - Bruno Brito

2

建议使用适当的参数应用Hough圆变换来代替查找轮廓。

查找轮廓存在挑战。一旦反转二进制图像,圆就会变成白色。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)

输入图片描述

要了解轮廓及其层次关系的更多信息,请参阅此处

更新

我有一种相当粗糙的方法来忽略不想要的轮廓。找到列表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)

enter image description here


0

你可以通过这个函数仅选择外边框:

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来获取contourshierarchy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接