如何在Opencv Python中获取轮廓内部的区域?

18

我使用了自适应阈值技术来创建类似下面的图片:

enter image description here

我所使用的代码是:

image = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 45, 0)

然后,我使用这段代码获取轮廓:

cnt = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]

我的目标是使用外轮廓内的所有像素生成掩膜,所以我希望填充物体内的所有像素为白色。我该怎么做?

我尝试使用下面的代码创建掩膜,但生成的掩膜似乎与应用自适应阈值后的图像没有任何区别。

mask = np.zeros(image.shape[:2], np.uint8)
cv2.drawContours(mask, cnt, -1, 255, -1)
2个回答

22
你所拥有的几乎是正确的。如果你查看阈值图像,它无法工作的原因是你的鞋物体在图像中存在间隙。具体而言,你需要的是期望鞋子的周长是全部相连的。如果这种情况发生,那么如果你提取最外部的轮廓(这就是你的代码正在执行的操作),你应该只有一个轮廓,代表对象的外周。一旦你填充了轮廓,那么你的鞋子应该完全实心。
由于你的鞋子周长不完整且断裂,这导致白色区域不连续。如果您使用findContours查找所有轮廓,它将仅找到每个白色形状的轮廓,而不是最外面的周长。因此,如果您尝试使用findContours,它将给出与原始图像相同的结果,因为您只是找到图像内每个白色区域的周长,然后使用findContours填充这些区域。
你需要做的是确保图像完全闭合。我建议你使用形态学将所有断开的区域连接在一起,然后在这张新图像上运行findContours。具体而言,执行二值形态学闭合。它将靠近一起的不连续白色区域连接起来。使用形态学闭合,也许使用类似于7 x 7的方形结构元素来闭合鞋子。你可以把这个结构元素看作是认为它们相连的白色区域之间的最小分离。
因此,做如下操作:
import numpy as np
import cv2 
image = cv2.imread('...') # Load your image in here
# Your code to threshold
image = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 45, 0)    

# Perform morphology
se = np.ones((7,7), dtype='uint8')
image_close = cv2.morphologyEx(image, cv2.MORPH_CLOSE, se)

# Your code now applied to the closed image
cnt = cv2.findContours(image_close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
mask = np.zeros(image.shape[:2], np.uint8)
cv2.drawContours(mask, cnt, -1, 255, -1)

这段代码基本上是将您的二值图像进行形态学闭运算。然后,我们找到该图像的外部轮廓,并用白色填充。顺便说一下,我下载了您的二值图像,并在自己的计算机上尝试了这个操作。以下是我使用您的图像得到的结果:

enter image description here


@rayryeng 我尝试了这段代码,但是在最后一行抛出以下错误 drawing.cpp:2380: error: (-215) npoints > 0 in function cv::drawContours - 我正在使用OpenCV 3.1和Python 2.7。你有什么建议吗? - g491
@g491。这意味着没有轮廓可绘制。 - rayryeng
@rayryeng 当我使用你发布的代码在上面运行的图像上运行时,出现了错误。也许最新的OpenCV发生了一些变化,所以它不再起作用了? - g491
确保图像转换为二进制,并将白色设置为255。还要确保图像是单通道的。我编写的代码假定任何一般图像,但发布的那个已经进行了阈值处理。除此之外,新版本可能会出现问题。我不能确定。这段代码在OpenCV 2.4上运行良好。 - rayryeng
1
哇,好棒又简单!然而,今天(2020年)当你已经分离出单个轮廓时,“thickness=cv2.FILLED”就不能像那样工作了。在这种情况下,我们必须将该轮廓作为“...drawContours(..., [cnt]...)”再次传递为数组。请参见此答案 - Janos
显示剩余3条评论

3
一个简单的方法是使用cv2.morphologyEx()cv2.MORPH_CLOSE来关闭前景中的孔洞,形成一个单一的轮廓。

enter image description here

现在外轮廓已经填充完成,我们可以使用cv2.findContours()找到外轮廓,并使用cv2.fillPoly()将所有像素填充为白色。

enter image description here

import cv2

# Load in image, convert to grayscale, and threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

# Close contour
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7,7))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=1)

# Find outer contour and fill with white
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cv2.fillPoly(close, cnts, [255,255,255])

cv2.imshow('close', close)
cv2.waitKey()

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