如何使用 python opencv 将重叠的卡片彼此分开?

Posted

技术标签:

【中文标题】如何使用 python opencv 将重叠的卡片彼此分开?【英文标题】:How do i separate overlapping cards from each other using python opencv? 【发布时间】:2020-10-22 00:48:51 【问题描述】:

我正在尝试检测扑克牌并将它们转换为使用 python opencv 鸟瞰卡片。我的代码适用于简单的案例,但我并没有停留在简单的案例上,而是想尝试更复杂的案例。我在为卡片找到正确的轮廓时遇到问题。这是我试图检测卡片并绘制轮廓的附加图像:

我的代码:

path1 = "F:\\ComputerVisionPrograms\\images\\cards4.jpeg"
g = cv2.imread(path1,0)
img = cv2.imread(path1)

edge = cv2.Canny(g,50,200)

p,c,h = cv2.findContours(edge, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
rect = []
for i in c:
    p = cv2.arcLength(i, True)
    ap = cv2.approxPolyDP(i, 0.02 * p, True)
    if len(ap)==4:
        rect.append(i)
cv2.drawContours(img,rect, -1, (0, 255, 0), 3)

plt.imshow(img)
plt.show()

结果:

这不是我想要的,我只想选择矩形卡,但由于它们相互遮挡,我没有得到我期望的结果。我相信我需要应用形态学技巧或其他操作来将它们分开或使边缘更加突出或可能是其他东西。如果您能分享您解决此问题的方法,我们将不胜感激。

其他同学要求的更多示例:

【问题讨论】:

每当您处理这个问题时,是否会出现黑色背景(如发布的图片中所示)? 不一定。 在这种情况下,我建议再分享一些示例图像。 是的,请多添加几张测试图像,以便制作通用代码。 分段不是解决方法。我建议直接使用对象检测技术(YOLO、ImageAI、...) 【参考方案1】:

有很多方法可以在图像中找到重叠的对象。您确定的信息是您的卡片都是矩形,大部分是白色并且大小相同。你的变量是亮度,角度,可能是一些透视失真。如果您想要一个强大的解决方案,您需要解决所有这些问题。

我建议使用霍夫变换来查找卡片边缘。首先,运行常规边缘检测。比你需要清理的结果,因为许多短边将属于“面”卡。我建议使用 dilate(11)->erode(15)->dilate(5) 的组合。这种组合将填补“人脸”卡片中的所有空白,然后它“缩小”斑点,在去除原始边缘的过程中,最后长回来并与原始人脸图片重叠一点。然后将其从原始图像中删除。

现在您的图像几乎具有所有相关边缘。使用霍夫变换找到它们。它会给你一组线。稍加过滤后,您可以将这些边缘调整为卡片的矩形形状。

dst = cv2.Canny(img, 250, 50, None, 3)

cn = cv2.dilate(dst, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11, 11)))
cn = cv2.erode(cn, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15, 15)))
cn = cv2.dilate(cn, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)))
dst -= cn
dst[dst < 127] = 0

cv2.imshow("erode-dilated", dst)

# Copy edges to the images that will display the results in BGR
cdstP = cv2.cvtColor(dst, cv2.COLOR_GRAY2BGR)

linesP = cv2.HoughLinesP(dst, 0.7, np.pi / 720, 30, None, 20, 15)

if linesP is not None:
    for i in range(0, len(linesP)):
        l = linesP[i][0]
        cv2.line(cdstP, (l[0], l[1]), (l[2], l[3]), (0, 255, 0), 2, cv2.LINE_AA)

cv2.imshow("Detected edges", cdstP)

这将为您提供以下信息:

【讨论】:

这是解决手头问题的绝妙方法。我会等一天,看看能否让更多人分享他们的想法。然后我会将答案标记为已接受。 可以更新/解释这段代码在做什么吗?如果可能,需要更多的代码 cmets。 此代码在图像中找到重要的边缘。我真的无法比我在文本中所做的更好地解释它。尝试绘制每个操作的结果以及它带来了哪些变化,它会让你更好地理解每一行的目的是什么。【参考方案2】:

另一种获得更好结果的方法是去掉边缘检测/线检测部分(我个人更喜欢),在图像预处理后找到轮廓。

下面是我的代码和结果:

img = cv2.imread(<image_name_here>)
imgC = img.copy()

# Converting to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Applying Otsu's thresholding
Retval, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

# Finding contours with RETR_EXTERNAL flag to get only the outer contours
# (Stuff inside the cards will not be detected now.)
cont, hier = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# Creating a new binary image of the same size and drawing contours found with thickness -1.
# This will colour the contours with white thus getting the outer portion of the cards.
newthresh = np.zeros(thresh.shape, dtype=np.uint8)
newthresh = cv2.drawContours(newthresh, cont, -1, 255, -1)

# Performing erosion->dilation to remove noise(specifically white portions detected of the poker coins).
kernel = np.ones((3, 3), dtype=np.uint8)
newthresh = cv2.erode(newthresh, kernel, iterations=6)
newthresh = cv2.dilate(newthresh, kernel, iterations=6)

# Again finding the final contours and drawing them on the image.
cont, hier = cv2.findContours(newthresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cv2.drawContours(imgC, cont, -1, (255, 0, 0), 2)

# Showing image
cv2.imshow("contours", imgC)
cv2.waitKey(0)

结果 -

这样,我们得到了图像中卡片的边界。为了检测和分离每张卡片,需要更复杂的算法,或者可以使用深度学习模型来完成。

【讨论】:

开箱即用的绝佳思维方式。【参考方案3】:

我正在检测您的形状内的白色矩形。最终结果是检测到的图像和边界框坐标。剧本还没有完成。我会在接下来的几天里继续尝试。

import os
import cv2
import numpy as np


def rectangle_detection(img):    
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    _, binarized = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

    cn = cv2.dilate(binarized, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11, 11)), iterations=3)
    cn = cv2.erode(cn, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15, 15)), iterations=3)
    cn = cv2.dilate(cn, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)), iterations=3)

    _, contours, _ = cv2.findContours(binarized, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
    # contours = sorted(contours, key=lambda x: cv2.contourArea(x))

    # detect all rectangles
    rois = []
    for contour in contours:
        cont_area = cv2.contourArea(contour)
        approx = cv2.approxPolyDP(contour, 0.02*cv2.arcLength(contour, True), True)
        if 1000 < cont_area < 15000:
            x, y, w, h = cv2.boundingRect(contour)
            rect_area = w * h
            if cont_area / rect_area < 0.6: # check the 'rectangularity'
                continue     
            cv2.drawContours(img, [approx], 0, (0, 255, 0), 2)
            if len(approx) == 4:
                cv2.putText(img, "Rect", (x, y), cv2.FONT_HERSHEY_COMPLEX, 1, (0, 0, 255))
            rois.append((x, y, w, h))
    return img, rois


def main():
    # load and prepare images
    INPUT = 'path'
    img = cv2.imread(INPUT)
    display, rects = rectangle_detection(img)
    cv2.imshow('img', display)
    cv2.waitKey()


if __name__ == "__main__":
    main()

【讨论】:

以上是关于如何使用 python opencv 将重叠的卡片彼此分开?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Python 和 OpenCV 构建 SET 求解器

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

将图像分成两等份python opencv

opencv 如何计算两张图像重叠的区域并计算出重叠度?

如何在Python中使用OpenCV以特定顺序显示图像?

使用 OpenCV (Python) 改进轮廓检测