如何在 python opencv 中完成/关闭轮廓?

Posted

技术标签:

【中文标题】如何在 python opencv 中完成/关闭轮廓?【英文标题】:How to complete/close a contour in python opencv? 【发布时间】:2017-03-24 22:07:24 【问题描述】:

我有一个 Pi 相机对准一张白色背景上的卡片。然而,局部阴影似乎阻止了我用于卡片检测的轮廓的闭合,这意味着检测总体上失败了。这是我的意思的截图:

您可以看到它在底角处变得参差不齐。这是我用来实现这一目标的代码:

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

有没有办法强制它关闭特定的轮廓?如果我进一步模糊图像以平滑不起作用的阴影,因为它只是忽略了那些没有边缘的角落。令人讨厌的是它距离关闭轮廓只有几个像素,但它从来没有......

编辑:我现在有一个更逼真的设置,其中背景是米色,并且有更多的阴影干扰。米色是必要的,因为有些卡片有白色边框,所以白色不起作用。边缘检测主要在阴影所在的左侧失败。

【问题讨论】:

有几种方法可以解决您的问题。 1)您可以使用形态运算符来关闭您的图像边界(扩张等)。 2)您可以使用 Otsu 方法对图像进行阈值处理,然后找到边界。 3)你可能想看看这个问题:***.com/questions/8667818/… @EliezerBernart 我将在什么上应用形态运算符;边缘层还是原始层?我尝试了 Otsu 阈值,但它不会自动找出正确的阈值来去除阴影。此外,有些卡片有白色边框(我已经移到了棕色背景),这至少会使简单的阈值无效。 如果您的背景颜色变化很大并且 Otsu 没有帮助,您可能需要扩大您的边缘或与其他运算符和您应用它们的顺序一起玩一点。您可以尝试的另一种方法是使用霍夫线来检测卡片的边框:) 能否分享您的卡片图片,以便我运行您的代码并验证问题所在? @EliezerBernart 将我当前设置的屏幕截图附加到 OP。 好的,谢谢!我去看看;) 【参考方案1】:

正如我在回复您的评论中提到的,“连接”边界线的最简单方法之一是使用形态运算符。在下面的代码中,图像的边缘使用椭球形状进行了扩张。这种技术允许我们合并靠近的行并填充一些空白区域。您可以在OpenCV Documentation 中获得有关此主题的更多信息。

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 通道),以减少或减弱您对阴影的影响。您还可以探索问题中的一些约束:卡片总是以直线作为边界,并使用能够处理参数形式的方法,例如霍夫线检测器。看看这个问题,它可能会给你一些关于如何改进结果的见解:How to identify square or rectangle with variable lengths and width by using javacv?

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

【讨论】:

感谢您的回答;它确实处理了我提出的第一个场景。你是对的,它没有涵盖第二种更困难的情况,但我会按照你的建议检查霍夫线。您对双边过滤也是正确的;我的 Pi 以大约 2fps 的速度运行,这对于我的目的来说已经足够了。我将尝试使用 Gaussian,但由于它会模糊边缘,我怀疑它会使边缘检测更加棘手(边缘保留是我选择双边的原因)。 没错!如果您打算基于边缘进行卡片检测,通常模糊会使事情变得有点棘手。 有点讽刺的是,我的白边卡片现在比黑边卡片效果更好;看起来,在部分阴影中,白色边框的显示足以检测到正确的边缘。有没有办法增加边缘周围的对比度或类似的东西? 我不知道我是否明白你的意思,但也许你正在寻找一种图像锐化技术:***.com/questions/11878281/… 嗯,我试过了,但它仍然无法在卡的阴影覆盖边缘拾取。我也试过霍夫线,但它只在卡片的右侧画了一条线,没有其他线。我怀疑那是因为它依赖于我已经有问题的边缘图像。

以上是关于如何在 python opencv 中完成/关闭轮廓?的主要内容,如果未能解决你的问题,请参考以下文章

opencv4.5.5怎么查看安装完成

如何在 Apollo 中打开和关闭轮询?

如何使用 OpenCV 从我的磁盘播放视频文件。? [关闭]

如何在 macOS 中关闭 OpenCV 窗口(Python 3)?

Html5如何快速在页面中写出多个轮播图效果

Windows 中 OpenCV-Python 的自动完成功能不起作用