2

我正在尝试查找此图像的轮廓,但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_EXTERNALcv2.RETR_LIST它似乎检测到相同的轮廓两次或类似的东西。图 3 显示了第一次检测到圆的边界,然后再次检测到它,如图 4 所示。我试图只找到这些圆的外边界。我怎样才能做到这一点?

在此处输入图像描述图 3

在此处输入图像描述图 4

4

4 回答 4

4

cv2.RETR_EXTERNAL问题是您在函数调用中使用的标志。如OpenCV 文档中所述,这仅返回外部轮廓。

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

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

于 2018-08-08T14:55:14.037 回答
4

我不确定这是否真的是你所期望的,但如果这样的话,有很多方法可以帮助 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 的方法可能更稳健。你可以在这里找到一个例子。 这个也可能很有趣。

-编辑-

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

像以前一样,在加载图像后应用阈值确保图像是二进制的。通过使用按位非运算反转图像,轮廓在黑色背景上变为白色。应用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)  

外部边界

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

于 2018-08-08T18:37:46.557 回答
2

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

寻找轮廓是一项挑战。反转二进制图像后,圆圈为白色。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)

在此处输入图像描述

于 2018-08-08T18:27:11.323 回答
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来获取轮廓层次结构

于 2021-10-23T03:43:11.313 回答