Estrai le immagini dall'immagine delle carte da gioco da tavolo con OpenCV


10

Ho scritto una piccola sceneggiatura in pitone in cui sto cercando di estrarre o ritagliare la parte della carta da gioco che rappresenta solo la grafica, rimuovendo tutto il resto. Ho provato vari metodi di soglia ma non ci sono riuscito. Inoltre, non posso semplicemente registrare manualmente la posizione dell'opera d'arte perché non è sempre nella stessa posizione o dimensione, ma sempre in una forma rettangolare in cui tutto il resto è solo testo e bordi.

inserisci qui la descrizione dell'immagine

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

L'output attuale è la cosa più vicina che potrei ottenere. Potrei essere sulla buona strada e provare qualche ulteriore lotta per disegnare un rettangolo attorno alle parti bianche, ma non penso che sia un metodo sostenibile:

Uscita corrente

Come ultima nota, vedi le carte in basso, non tutte le cornici hanno esattamente le stesse dimensioni o posizioni, ma c'è sempre un'opera d'arte con solo testo e bordi attorno. Non deve essere tagliato con precisione, ma chiaramente l'arte è una "regione" della carta, circondata da altre regioni che contengono del testo. Il mio obiettivo è quello di provare a catturare la regione dell'opera d'arte nel miglior modo possibile.

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine


Che tipo di uscita aspetti dalla scheda "Narcomoeba"? Non ha nemmeno un bucato dalla forma regolare. Inoltre, non credo che esista una soluzione senza l'assistenza dell'utente.
Burak,

Il meglio che puoi fare è fare clic sui punti di delimitazione, migliorare quei punti abbinandoli all'angolo più vicino rilevato, quindi scoprire la forma in base ai bordi tra i punti. Dubito ancora che una buona implementazione di questo algoritmo possa essere eseguita la maggior parte delle volte. La regolazione della soglia di rilevamento dei bordi e il suggerimento della curvatura della linea tra i punti (clic sinistro: diritto, clic destro: curvo, forse?) In tempo reale possono aumentare le possibilità di successo.
Burak,

1
Ho aggiunto un esempio migliore alla carta Narcomoeba. Come puoi vedere sono interessato all'area grafica della scheda, non deve essere precisa al 100%. A mio avviso, ci devono essere alcune trasformazioni che mi consentono di dividere una carta in diverse "regioni" per così dire.
Waroulolz,

penso che tu possa prima ritagliare le immagini in 2 tipi (forse 4 tipi? come informazioni fornite, l'immagine verrà mostrata in alto o a destra) e usare Opencv per verificare se ha testo nell'immagine. Quindi ritagliare -> filtro -> risultato -> tagliare il bordo, se necessario, per Opencv è più facile ottenere risultati migliori.
elprup

Risposte:


3

Ho usato la trasformazione della linea di Hough per rilevare parti lineari dell'immagine. Gli incroci di tutte le linee sono stati usati per costruire tutti i possibili rettangoli, che non contengono altri punti di attraversamento. Poiché la parte della carta che stai cercando è sempre il più grande di quei rettangoli (almeno nei campioni che hai fornito), ho semplicemente scelto il più grande di quei rettangoli come vincitore. Lo script funziona senza l'interazione dell'utente.

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

Questi sono i risultati con i campioni forniti:

Image1

image2

Image3

Il codice per la ricerca di incroci di linee può essere trovato qui: trova il punto di intersezione di due linee disegnate usando houghlines opencv

Puoi leggere di più su Hough Lines qui .


2
Grazie per il duro lavoro. La tua risposta è quello che stavo cercando. Sapevo che Hough Lines avrebbe avuto un ruolo importante qui. Ho provato un paio di volte ad usarlo ma non sono riuscito ad avvicinarmi alla tua soluzione. Come hai commentato, è necessario apportare alcune modifiche ai parametri per generalizzare l'approccio, ma la logica è grande e potente.
Waroulolz,

1
Penso che sia un'ottima soluzione per questo tipo di problema, nessun input dell'utente necessario. Bravo!!
Meto,

@Meto - Apprezzo il lavoro svolto qui ma non sono d'accordo con la parte di input dell'utente . È solo un alias se si inserisce in fase di esecuzione o si modifica la soglia dopo aver cercato il risultato.
Burak,

1
@Burak - Sono stato in grado di eseguire tutti i campioni forniti con le stesse impostazioni, quindi presumo che anche la maggior parte delle altre schede funzionasse. Quindi le impostazioni del theshold devono essere fatte una sola volta.
M. Martin,

0

Sappiamo che le carte hanno confini dritti lungo gli assi xey. Possiamo usarlo per estrarre parti dell'immagine. Il codice seguente implementa il rilevamento di linee orizzontali e verticali nell'immagine.

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

Devi solo fare clic su due aree da includere. Un'area di clic di esempio e il risultato corrispondente sono i seguenti:

Linee result_of_lines

Risultati da altre immagini:

Risultato_2 result_3


0

Non penso che sia possibile ritagliare automaticamente il ROI del disegno usando le tradizionali tecniche di elaborazione delle immagini a causa della natura dinamica di colori, dimensioni, posizioni e trame per ogni carta. Dovresti esaminare la macchina / l'apprendimento profondo e formare il tuo classificatore se vuoi farlo automaticamente. Invece, ecco un approccio manuale per selezionare e ritagliare un ROI statico da un'immagine.

L'idea è quella di utilizzare i cv2.setMouseCallback()gestori di eventi per rilevare se è stato fatto clic o rilasciato il mouse. Per questa implementazione, puoi estrarre il ROI della grafica tenendo premuto il pulsante sinistro del mouse e trascinando per selezionare il ROI desiderato. Dopo aver selezionato il ROI desiderato, premere cper ritagliare e salvare il ROI. È possibile ripristinare il ROI utilizzando il pulsante destro del mouse.

ROI di grafica salvate

Codice

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()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.