Как отделить перекрывающиеся карты друг от друга с помощью python opencv?

Я пытаюсь обнаружить игральные карты и преобразовать их, чтобы получить представление о карте с высоты птичьего полета, используя 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()

Результат:

введите здесь описание изображения

Это не то, что я хотел, я хотел, чтобы были выбраны только прямоугольные карты, но, поскольку они перекрывают друг друга, я не получаю того, что ожидал. Я считаю, что мне нужно применить морфологические приемы или другие операции, чтобы, возможно, разделить их или сделать края более заметными, или может быть что-то еще. Было бы очень признательно, если бы вы могли поделиться своим подходом к решению этой проблемы.

Еще несколько примеров, запрошенных другими коллегами:

введите здесь описание изображения

введите здесь описание изображения


person Hissaan Ali    schedule 01.07.2020    source источник
comment
Будет ли черный фон всякий раз, когда вы сталкиваетесь с этой проблемой, как на опубликованном изображении?   -  person Vardan Agarwal    schedule 01.07.2020
comment
не обязательно.   -  person Hissaan Ali    schedule 01.07.2020
comment
В этом случае я рекомендую поделиться еще несколькими образцами изображений.   -  person karlphillip    schedule 06.07.2020
comment
Да, пожалуйста, добавьте еще несколько тестовых изображений, чтобы можно было сделать обобщенный код.   -  person Vardan Agarwal    schedule 06.07.2020
comment
Добавил еще две картинки. Взглянуть   -  person Hissaan Ali    schedule 07.07.2020
comment
Сегментация — это не выход. Я бы порекомендовал перейти прямо к методам обнаружения объектов (YOLO, ImageAI, ...)   -  person karlphillip    schedule 08.07.2020
comment
Да, я также думаю, что классификация изображений или сегментация экземпляров, такие как YOLO, являются лучшим выбором. Я использовал Jetson Nano (SOC Board) для переноса обучения MASK RCNN для достижения результатов, сравнимых с этим видео, при сегментации карт: youtube.com/watch?v=npZ-8Nj1YwY.   -  person Paul Higazi    schedule 12.07.2020


Ответы (3)


Существует множество подходов к поиску перекрывающихся объектов на изображении. Информация, которой вы владеете наверняка, заключается в том, что все ваши карты представляют собой прямоугольники, в основном белого цвета и одинакового размера. Ваши переменные: яркость, угол, возможно искажение перспективы. Если вам нужно надежное решение, вам необходимо решить все эти проблемы.

Я предлагаю использовать преобразование Хафа, чтобы найти края карты. Во-первых, запустите обычное обнаружение границ. Затем вам нужно очистить результаты, так как многие короткие ребра будут принадлежать лицевым картам. Я предлагаю использовать комбинацию 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)

Это даст вам следующее:

введите здесь описание изображения

person igrinis    schedule 09.07.2020
comment
Это был действительно замечательный способ решить насущную проблему. Я бы подождал один день, чтобы увидеть, смогу ли я заставить больше людей поделиться своими мыслями. Затем я отмечу принятый ответ. - person Hissaan Ali; 09.07.2020
comment
можно обновить/объяснить, что делает этот код? Нужны дополнительные комментарии к коду, если это возможно. - person Tony; 02.02.2021
comment
Этот код находит важные края изображения. Я действительно не могу объяснить это лучше, чем я уже сделал в тексте. Постарайтесь изобразить результат каждой операции и какие изменения она вносит, это поможет вам лучше понять назначение каждой строки. - person igrinis; 03.02.2021

Другой способ получить лучшие результаты — отказаться от части обнаружения краев/обнаружения линий (лично я предпочитаю) и найти контуры после предварительной обработки изображения.

Ниже мой код и результаты:

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)

Полученные результаты -

вывод карты1 card2 output

Таким образом, мы получили границу карт на изображении. Для обнаружения и разделения каждой отдельной карты потребуется более сложный алгоритм или это можно сделать с помощью модели глубокого обучения.

person Rahul Kedia    schedule 10.07.2020
comment
Отличное нестандартное мышление. - person Hissaan Ali; 10.07.2020

Я обнаруживаю белые прямоугольники внутри вашей фигуры. Конечным результатом является обнаруженное изображение и координаты ограничивающей рамки. Сценарий еще не завершен. Постараюсь продолжить в ближайшие пару дней.

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()

введите здесь описание изображения

введите здесь описание изображения

введите здесь описание изображения

person Rishab P.    schedule 12.07.2020