如何在Python OpenCV中完成/关闭轮廓?

23

我有一个树莓派相机对着一张白色背景上的卡片,但是局部阴影似乎妨碍了我用于检测卡片轮廓的闭合,这意味着检测整体失败。以下是我的意思的屏幕截图:

开放轮廓的屏幕截图

你可以看到尤其是底部角落周围变得粗糙。这是我用来做到这一步的代码:

gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.blur(gray, (5,5))
gray = cv2.bilateralFilter(gray, 11, 17, 17) #blur. very CPU intensive.
cv2.imshow("Gray map", gray)

edges = cv2.Canny(gray, 30, 120)

cv2.imshow("Edge map", edges)

#find contours in the edged image, keep only the largest
# ones, and initialize our screen contour
# use RETR_EXTERNAL since we know the largest (external) contour will be the card edge.
_, cnts, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:1]
screenCnt = None

# loop over our contours
for c in cnts:
    # approximate the contour
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.3 * peri, True)

    cv2.drawContours(image, [cnts[0]], -1, (0, 255, 0), 2)

    # if our approximated contour has four points, then
    # we can assume that we have found our card
    if len(approx) == 4:
        screenCnt = approx;
    break

有没有一种方法可以强制它关闭特定的轮廓?如果我将图像模糊得更平滑以消除阴影,那也不起作用,因为它会简单地忽略那些角落,认为它们没有边缘。令人恼火的是,它距离闭合轮廓只有几个像素,但它从未这样做...

编辑:我现在有了一个更真实的设置,其中背景是米色的,并且有更多的阴影干扰。米色是必需的,因为有一些带有白色边框的卡片,所以白色行不通。边缘检测在左侧阴影最多的地方失败。

输入图片描述


2
有几种方法可以解决您的问题。1)您可以使用形态学运算符来关闭图像边界(膨胀等)。 2)您可以使用Otsu方法对图像进行阈值处理,然后找到边界。 3)您可能想看一下这个问题:https://dev59.com/aWoy5IYBdhLWcg3wQr5i - Eliezer Bernart
@EliezerBernart 我应该在哪个层上应用形态学运算符,边缘层还是原始层?我尝试了Otsu阈值处理,但它不能自动找到正确的阈值来去除阴影。此外,一些卡片有白色边框(我已经换成了棕色背景),这使得至少简单的阈值处理无效。 - IronWaffleMan
如果您的背景颜色变化很大,而Otsu算法无法帮助您,您可能需要扩张边缘或尝试一下其他运算符及其应用顺序。另外,您可以尝试使用霍夫线检测卡片的边界 :)您能分享您的卡片图片吗?这样我就可以运行您的代码并验证问题所在了。 - Eliezer Bernart
@EliezerBernart,我在帖子中附上了我的当前设置的截图。 - IronWaffleMan
好的,谢谢!我会看一下的 ;) - Eliezer Bernart
2个回答

25

正如我在对你的回答评论中提到的那样,"连接"边界线的最简单方法之一是使用形态学运算符。在下面的代码中,使用椭圆形状膨胀图像的边缘。这种技术使我们能够合并接近的线条并填充部分空白区域。您可以在OpenCV文档中了解更多关于此主题的信息。

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(9,9))
dilated = cv2.dilate(image, kernel)
_, cnts, _ = cv2.findContours(dilated.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

这里您可以看到原始边缘图像、膨胀后的图像以及使用膨胀边缘获取的轮廓(使用原始截屏的裁剪区域得到的图像):

但是,正如您所见和想象的那样,解决更一般的情况会更加复杂,并且需要使用其他方法,并且可能比SO问题更广泛(或者至少在目前的形式下是这样)。

通过查看您更困难的情况,我建议您使用其他图像表示来替换灰度输入图像(例如来自HSV颜色空间的H通道),以便减轻或减弱阴影的影响。您还可以探索问题中的一些限制:卡牌总是具有直线边界并使用能够处理参数形式的方法,例如Hough线检测器。看看这个问题,它可能会给您一些关于如何改善结果的见解:How to identify square or rectangle with variable lengths and width by using javacv?

备注:双边滤波在计算上非常昂贵,特别是如果您使用RPI运行应用程序。我建议投资一些其他替代方案,例如高斯滤波,以减少图片中的噪声(假设您确实需要这样做)。


1
谢谢您的回答,它确实解决了我提出的第一个情况。您说得对,它并没有涵盖第二个更困难的情况,但我会像您建议的那样尝试使用霍夫线。您也是关于双边滤波的正确,我的树莓派运行速度约为2fps,对于我的目的来说已经足够了。我会尝试使用高斯滤波,虽然它会模糊边缘,但我怀疑它会使边缘检测变得更加棘手(保留边缘是我选择双边滤波的原因)。 - IronWaffleMan
没错!如果您计划基于边缘进行卡片检测,通常模糊会让事情变得有些棘手。 - Eliezer Bernart
有点讽刺的是,我的白边卡现在比黑边卡更好用;似乎在部分阴影中,白色边框透过足够的光线来正确检测边缘。是否有一种方法可以增加边缘周围的对比度或类似的东西? - IronWaffleMan
我不确定我是否理解了你的意思,但也许你正在寻找一种图像锐化技术:https://dev59.com/Jmct5IYBdhLWcg3wuPq6 - Eliezer Bernart
霍夫线参数设置相当困难。也许直方图均衡化可以帮助您平衡颜色水平,很难说。阴影是图像处理中常见的问题,最好的解决方法将取决于您想要考虑的约束和所需的自动化水平。 - Eliezer Bernart
显示剩余2条评论

0
最近我遇到了同样的问题,由于光线条件的变化,我的轮廓在多个地方被打断,以至于无法通过形态学变换来填补这些间隙。

Original image Dilated image

我正在寻找一个矩形轮廓的LCD显示屏,正如你所看到的,它在两个地方都破裂了。根据光照条件的不同,这些间隙可能出现在不同的位置。最终,我找到了每个轮廓的凸包,它总是封闭的,然后将其近似为一个四顶点的多边形。
def get_rectangular_contours(contours):
    """Approximates provided contours and returns only those which have 4 vertices"""
    res = []
    for contour in contours:
        hull = cv2.convexHull(contour)
        peri = cv2.arcLength(hull, closed=True)
        approx = cv2.approxPolyDP(hull, 0.04 * peri, closed=True)
        if len(approx) == 4:
            res.append(approx)
    return res

然后,在这些四边形的列表中,我会寻找那个具有特定长宽比的类似矩形的形状。这个方法非常有效。

Found contours Found rectangle

你可以在这里查看完整的算法。

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