Wyodrębnij grafikę z obrazu karty gry stołowej za pomocą OpenCV

10

Napisałem mały skrypt w Pythonie, w którym próbuję wyodrębnić lub wykadrować część karty do gry, która reprezentuje tylko grafikę, usuwając resztę. Próbowałem różnych metod progowania, ale nie mogłem się tam dostać. Zauważ też, że nie mogę po prostu ręcznie zapisać pozycji dzieła, ponieważ nie zawsze jest ono w tej samej pozycji lub rozmiarze, ale zawsze w kształcie prostokąta, gdzie wszystko inne to tylko tekst i obramowania.

wprowadź opis zdjęcia tutaj

from matplotlib import pyplot as plt
import cv2

img = cv2.imread(filename)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

ret,binary = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY)

binary = cv2.bitwise_not(binary)
kernel = np.ones((15, 15), np.uint8)

closing = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)

plt.imshow(closing),plt.show()

Prąd wyjściowy jest najbliższą rzeczą, jaką mogę uzyskać. Mógłbym być na dobrej drodze i spróbować dalszych sporów, aby narysować prostokąt wokół białych części, ale nie sądzę, że jest to metoda zrównoważona:

Prąd wyjściowy

Na koniec, patrz karty poniżej, nie wszystkie ramki mają dokładnie takie same rozmiary lub pozycje, ale zawsze jest kawałek grafiki z samym tekstem i ramkami wokół niego. Nie musi być bardzo precyzyjnie cięty, ale najwyraźniej sztuka jest „regionem” karty, otoczonym innymi regionami zawierającymi tekst. Moim celem jest jak najlepsze uchwycenie regionu dzieła sztuki.

wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj

Waroulolz
źródło
Na jakiego rodzaju wyjście czekasz z karty „Narcomoeba”? Nie ma nawet regularnego kształtu. Poza tym nie sądzę, że istnieje rozwiązanie bez pomocy użytkownika.
Burak
Najlepsze, co możesz zrobić, to kliknąć punkty graniczne, ulepszyć je, dopasowując je do najbliższego wykrytego rogu, a następnie znaleźć kształt na podstawie krawędzi między punktami. Nadal wątpię, aby dobra implementacja tego algorytmu osiągnęła większość czasu. Dostosowanie progu wykrywania krawędzi i podpowiedź na temat krzywizny linii między punktami (lewy przycisk: prosty, prawy przycisk: zakrzywiony, może?) W czasie rzeczywistym może zwiększyć szansę na sukces.
Burak
1
Dodałem lepszy przykład do karty Narcomoeba. Jak widać interesuje mnie region grafiki karty, nie musi być ona w 100% precyzyjna. Moim zdaniem muszą nastąpić pewne zmiany, które pozwolą mi podzielić kartę w różnych „regionach”, że tak powiem.
Waroulolz
myślę, że możesz najpierw przyciąć zdjęcia do 2 typów (może 4 typy? jak podano informacje, obraz pokaże się na górze lub po prawej stronie) i użyj opencv, aby sprawdzić, czy ma tekst na obrazie. Zatem przycinanie -> filtrowanie -> wynik -> przycinanie krawędzi w razie potrzeby jest łatwiejsze dla opencv, aby uzyskać lepszy wynik.
elprup

Odpowiedzi:

3

Użyłem transformacji liniowej Hougha do wykrycia liniowych części obrazu. Skrzyżowania wszystkich linii wykorzystano do skonstruowania wszystkich możliwych prostokątów, które nie zawierają innych punktów przecięcia. Ponieważ część karty, której szukasz, jest zawsze największym z tych prostokątów (przynajmniej w podanych próbkach), po prostu wybrałem największy z tych prostokątów jako zwycięzcę. Skrypt działa bez interakcji użytkownika.

import cv2
import numpy as np
from collections import defaultdict

def segment_by_angle_kmeans(lines, k=2, **kwargs):
    #Groups lines based on angle with k-means.
    #Uses k-means on the coordinates of the angle on the unit circle 
    #to segment `k` angles inside `lines`.

    # Define criteria = (type, max_iter, epsilon)
    default_criteria_type = cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER
    criteria = kwargs.get('criteria', (default_criteria_type, 10, 1.0))
    flags = kwargs.get('flags', cv2.KMEANS_RANDOM_CENTERS)
    attempts = kwargs.get('attempts', 10)

    # returns angles in [0, pi] in radians
    angles = np.array([line[0][1] for line in lines])
    # multiply the angles by two and find coordinates of that angle
    pts = np.array([[np.cos(2*angle), np.sin(2*angle)]
                    for angle in angles], dtype=np.float32)

    # run kmeans on the coords
    labels, centers = cv2.kmeans(pts, k, None, criteria, attempts, flags)[1:]
    labels = labels.reshape(-1)  # transpose to row vec

    # segment lines based on their kmeans label
    segmented = defaultdict(list)
    for i, line in zip(range(len(lines)), lines):
        segmented[labels[i]].append(line)
    segmented = list(segmented.values())
    return segmented

def intersection(line1, line2):
    #Finds the intersection of two lines given in Hesse normal form.
    #Returns closest integer pixel locations.
    #See https://stackoverflow.com/a/383527/5087436

    rho1, theta1 = line1[0]
    rho2, theta2 = line2[0]

    A = np.array([
        [np.cos(theta1), np.sin(theta1)],
        [np.cos(theta2), np.sin(theta2)]
    ])
    b = np.array([[rho1], [rho2]])
    x0, y0 = np.linalg.solve(A, b)
    x0, y0 = int(np.round(x0)), int(np.round(y0))
    return [[x0, y0]]


def segmented_intersections(lines):
    #Finds the intersections between groups of lines.

    intersections = []
    for i, group in enumerate(lines[:-1]):
        for next_group in lines[i+1:]:
            for line1 in group:
                for line2 in next_group:
                    intersections.append(intersection(line1, line2)) 
    return intersections

def rect_from_crossings(crossings):
    #find all rectangles without other points inside
    rectangles = []

    # Search all possible rectangles
    for i in range(len(crossings)):
        x1= int(crossings[i][0][0])
        y1= int(crossings[i][0][1])

        for j in range(len(crossings)):
            x2= int(crossings[j][0][0])
            y2= int(crossings[j][0][1])

            #Search all points
            flag = 1
            for k in range(len(crossings)):
                x3= int(crossings[k][0][0])
                y3= int(crossings[k][0][1])

                #Dont count double (reverse rectangles)
                if (x1 > x2 or y1 > y2):
                    flag = 0
                #Dont count rectangles with points inside   
                elif ((((x3 >= x1) and (x2 >= x3))and (y3 > y1) and (y2 > y3) or ((x3 > x1) and (x2 > x3))and (y3 >= y1) and (y2 >= y3))):    
                    if(i!=k and j!=k):    
                        flag = 0

            if flag:
                rectangles.append([[x1,y1],[x2,y2]])

    return rectangles

if __name__ == '__main__':
    #img = cv2.imread('TAJFp.jpg')
    #img = cv2.imread('Bj2uu.jpg')
    img = cv2.imread('yi8db.png')

    width = int(img.shape[1])
    height = int(img.shape[0])

    scale = 380/width
    dim = (int(width*scale), int(height*scale))
    # resize image
    img = cv2.resize(img, dim, interpolation = cv2.INTER_AREA) 

    img2 = img.copy()
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    gray = cv2.GaussianBlur(gray,(5,5),cv2.BORDER_DEFAULT)

    # Parameters of Canny and Hough may have to be tweaked to work for as many cards as possible
    edges = cv2.Canny(gray,10,45,apertureSize = 7)
    lines = cv2.HoughLines(edges,1,np.pi/90,160)

    segmented = segment_by_angle_kmeans(lines)
    crossings = segmented_intersections(segmented)
    rectangles = rect_from_crossings(crossings)

    #Find biggest remaining rectangle
    size = 0
    for i in range(len(rectangles)):
        x1 = rectangles[i][0][0]
        x2 = rectangles[i][1][0]
        y1 = rectangles[i][0][1]
        y2 = rectangles[i][1][1]

        if(size < (abs(x1-x2)*abs(y1-y2))):
            size = abs(x1-x2)*abs(y1-y2)
            x1_rect = x1
            x2_rect = x2
            y1_rect = y1
            y2_rect = y2

    cv2.rectangle(img2, (x1_rect,y1_rect), (x2_rect,y2_rect), (0,0,255), 2)
    roi = img[y1_rect:y2_rect, x1_rect:x2_rect]

    cv2.imshow("Output",roi)
    cv2.imwrite("Output.png", roi)
    cv2.waitKey()

Oto wyniki z podanymi przez Ciebie próbkami:

Zdjęcie 1

Zdjęcie 2

Zdjęcie 3

Kod znajdowania skrzyżowań linii można znaleźć tutaj: znajdź punkt przecięcia dwóch linii narysowanych za pomocą linii Houghlines opencv

Możesz przeczytać więcej o Hough Lines tutaj .

M. Martin
źródło
2
Dzięki za ciężką pracę. Twoja odpowiedź jest tym, czego szukałem. Wiedziałem, że Hough Lines odegra tu dużą rolę. Kilka razy próbowałem go użyć, ale nie mogłem zbliżyć się do twojego rozwiązania. Jak skomentowałeś, musisz dokonać kilku poprawek parametrów, aby uogólnić podejście, ale logika jest świetna i potężna.
Waroulolz
1
Myślę, że jest to świetne rozwiązanie dla tego rodzaju problemu, nie wymaga interwencji użytkownika. Brawo!!
Meto
@Meto - Doceniam pracę wykonaną tutaj, ale nie zgadzam się z częścią nie wprowadzoną przez użytkownika . To tylko pseudonim, niezależnie od tego, czy wprowadzasz dane w czasie wykonywania, czy zmieniasz próg po sprawdzeniu wyniku.
Burak
1
@Burak - Byłem w stanie uruchomić wszystkie próbki, które zostały dostarczone z tymi samymi ustawieniami, więc zakładam, że większość innych kart również by działała. Ustawienia skrótu należy wprowadzić tylko raz.
M. Martin
0

Wiemy, że karty mają proste granice wzdłuż osi xiy. Możemy to wykorzystać do wyodrębnienia części obrazu. Poniższy kod implementuje wykrywanie poziomych i pionowych linii na obrazie.

import cv2
import numpy as np

def mouse_callback(event, x, y, flags, params):
    global num_click
    if num_click < 2 and event == cv2.EVENT_LBUTTONDOWN:
        num_click = num_click + 1
        print(num_click)
        global upper_bound, lower_bound, left_bound, right_bound
        upper_bound.append(max(i for i in hor if i < y) + 1)
        lower_bound.append(min(i for i in hor if i > y) - 1)
        left_bound.append(max(i for i in ver if i < x) + 1)
        right_bound.append(min(i for i in ver if i > x) - 1)

filename = 'image.png'
thr = 100  # edge detection threshold
lined = 50  # number of consequtive True pixels required an axis to be counted as line
num_click = 0  # select only twice
upper_bound, lower_bound, left_bound, right_bound = [], [], [], []
winname = 'img'

cv2.namedWindow(winname)
cv2.setMouseCallback(winname, mouse_callback)

img = cv2.imread(filename, 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
bw = cv2.Canny(gray, thr, 3*thr)

height, width, _ = img.shape

# find horizontal lines
hor = []
for i in range (0, height-1):
    count = 0
    for j in range (0, width-1):
        if bw[i,j]:
            count = count + 1
        else:
            count = 0
        if count >= lined:
            hor.append(i)
            break

# find vertical lines
ver = []
for j in range (0, width-1):
    count = 0
    for i in range (0, height-1):
        if bw[i,j]:
            count = count + 1
        else:
            count = 0
        if count >= lined:
            ver.append(j)
            break

# draw lines
disp_img = np.copy(img)
for i in hor:
    cv2.line(disp_img, (0, i), (width-1, i), (0,0,255), 1)
for i in ver:
    cv2.line(disp_img, (i, 0), (i, height-1), (0,0,255), 1)

while num_click < 2:
    cv2.imshow(winname, disp_img)
    cv2.waitKey(10)
disp_img = img[min(upper_bound):max(lower_bound), min(left_bound):max(right_bound)]
cv2.imshow(winname, disp_img)
cv2.waitKey()   # Press any key to exit
cv2.destroyAllWindows()

Musisz tylko kliknąć dwa obszary, które chcesz uwzględnić. Przykładowy obszar kliknięcia i odpowiedni wynik są następujące:

linie wynik_wierszy

Wyniki z innych zdjęć:

wynik_2 wynik_3

Burak
źródło
0

Nie sądzę, że możliwe jest automatyczne przycinanie ROI grafiki przy użyciu tradycyjnych technik przetwarzania obrazu ze względu na dynamiczny charakter kolorów, wymiarów, lokalizacji i tekstur dla każdej karty. Będziesz musiał przyjrzeć się uczeniu maszynowemu / głębokiemu i wyszkolić własnego klasyfikatora, jeśli chcesz to zrobić automatycznie. Zamiast tego oto ręczne podejście do wybierania i kadrowania statycznego ROI z obrazu.

Chodzi o to, aby użyć cv2.setMouseCallback()i obsługi zdarzeń, aby wykryć, czy mysz została kliknięta lub zwolniona. W przypadku tej implementacji można wyodrębnić ROI grafiki, przytrzymując lewy przycisk myszy i przeciągając, aby wybrać żądany ROI. Po wybraniu żądanego ROI naciśnij, caby przyciąć i zapisać ROI. Możesz zresetować ROI za pomocą prawego przycisku myszy.

Zapisane zwroty z inwestycji

Kod

import cv2

class ExtractArtworkROI(object):
    def __init__(self):
        # Load image
        self.original_image = cv2.imread('1.png')
        self.clone = self.original_image.copy()
        cv2.namedWindow('image')
        cv2.setMouseCallback('image', self.extractROI)
        self.selected_ROI = False

        # ROI bounding box reference points
        self.image_coordinates = []

    def extractROI(self, event, x, y, flags, parameters):
        # Record starting (x,y) coordinates on left mouse button click
        if event == cv2.EVENT_LBUTTONDOWN:
            self.image_coordinates = [(x,y)]

        # Record ending (x,y) coordintes on left mouse button release
        elif event == cv2.EVENT_LBUTTONUP:
            # Remove old bounding box
            if self.selected_ROI:
                self.clone = self.original_image.copy()

            # Draw rectangle 
            self.selected_ROI = True
            self.image_coordinates.append((x,y))
            cv2.rectangle(self.clone, self.image_coordinates[0], self.image_coordinates[1], (36,255,12), 2)

            print('top left: {}, bottom right: {}'.format(self.image_coordinates[0], self.image_coordinates[1]))
            print('x,y,w,h : ({}, {}, {}, {})'.format(self.image_coordinates[0][0], self.image_coordinates[0][1], self.image_coordinates[1][0] - self.image_coordinates[0][0], self.image_coordinates[1][1] - self.image_coordinates[0][1]))

        # Clear drawing boxes on right mouse button click
        elif event == cv2.EVENT_RBUTTONDOWN:
            self.selected_ROI = False
            self.clone = self.original_image.copy()

    def show_image(self):
        return self.clone

    def crop_ROI(self):
        if self.selected_ROI:
            x1 = self.image_coordinates[0][0]
            y1 = self.image_coordinates[0][1]
            x2 = self.image_coordinates[1][0]
            y2 = self.image_coordinates[1][1]

            # Extract ROI
            self.cropped_image = self.original_image.copy()[y1:y2, x1:x2]

            # Display and save image
            cv2.imshow('Cropped Image', self.cropped_image)
            cv2.imwrite('ROI.png', self.cropped_image)
        else:
            print('Select ROI before cropping!')

if __name__ == '__main__':
    extractArtworkROI = ExtractArtworkROI()
    while True:
        cv2.imshow('image', extractArtworkROI.show_image())
        key = cv2.waitKey(1)

        # Close program with keyboard 'q'
        if key == ord('q'):
            cv2.destroyAllWindows()
            exit(1)

        # Crop ROI
        if key == ord('c'):
            extractArtworkROI.crop_ROI()
nathancy
źródło