Extrahieren Sie mit OpenCV Grafiken aus dem Bild einer Tischspielkarte

10

Ich habe ein kleines Skript in Python geschrieben, in dem ich versuche, den Teil der Spielkarte zu extrahieren oder zuzuschneiden, der nur das Bildmaterial darstellt, und den Rest zu entfernen. Ich habe verschiedene Methoden des Schwellenwerts ausprobiert, konnte aber nicht dorthin gelangen. Beachten Sie auch, dass ich die Position des Bildmaterials nicht einfach manuell aufzeichnen kann, da es sich nicht immer an derselben Position oder Größe befindet, sondern immer in einer rechteckigen Form, bei der alles andere nur aus Text und Rahmen besteht.

Geben Sie hier die Bildbeschreibung ein

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

Die aktuelle Ausgabe ist die nächste, die ich bekommen könnte. Ich könnte auf dem richtigen Weg sein und weiter streiten, um ein Rechteck um die weißen Teile zu zeichnen, aber ich denke nicht, dass dies eine nachhaltige Methode ist:

Aktueller Output

Als letzte Anmerkung siehe die Karten unten, nicht alle Rahmen haben genau die gleiche Größe oder Position, aber es gibt immer ein Kunstwerk mit nur Text und Rändern. Es muss nicht sehr präzise geschnitten sein, aber die Kunst ist eindeutig eine "Region" der Karte, umgeben von anderen Regionen, die Text enthalten. Mein Ziel ist es, die Region des Kunstwerks so gut wie möglich zu erfassen.

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Waroulolz
quelle
Was für eine Ausgabe warten Sie von der "Narcomoeba" -Karte? Es hat nicht einmal eine regelmäßig geformte Grenze. Außerdem glaube ich nicht, dass es eine Lösung ohne Benutzerunterstützung gibt.
Burak
Das Beste, was Sie tun können, ist, auf Begrenzungspunkte zu klicken, diese Punkte zu verbessern, indem Sie sie an die nächste erkannte Ecke anpassen, und dann die Form anhand der Kanten zwischen Punkten zu ermitteln. Ich bezweifle immer noch, dass eine gute Implementierung dieses Algorithmus die meiste Zeit erreicht. Das Anpassen des Kantenerkennungsschwellenwerts und das Angeben eines Hinweises auf die Krümmung der Linie zwischen Punkten (linker Klick: gerade, rechter Klick: gekrümmt, vielleicht?) In Echtzeit kann die Erfolgschance erhöhen.
Burak
1
Ich habe der Narcomoeba-Karte ein besseres Beispiel hinzugefügt. Wie Sie sehen, interessiert mich der Grafikbereich der Karte, er muss nicht 100% genau sein. Meiner Meinung nach muss es einige Transformationen geben, die es mir ermöglichen, eine Karte sozusagen in verschiedene "Regionen" zu unterteilen.
Waroulolz
Ich denke, Sie können zuerst Bilder auf 2 Typen zuschneiden (vielleicht 4 Typen? Wie angegeben, das Bild wird oben oder rechts angezeigt) und mit opencv prüfen, ob es Text im Bild enthält. So beschneiden -> filtern -> Ergebnis -> Schnittkante bei Bedarf ist für opencv einfacher, um ein besseres Ergebnis zu erzielen.
Elprup

Antworten:

3

Ich habe die Hough-Linientransformation verwendet, um lineare Teile des Bildes zu erkennen. Die Kreuzungen aller Linien wurden verwendet, um alle möglichen Rechtecke zu konstruieren, die keine anderen Kreuzungspunkte enthalten. Da der Teil der Karte, den Sie suchen, immer das größte dieser Rechtecke ist (zumindest in den von Ihnen bereitgestellten Beispielen), habe ich einfach das größte dieser Rechtecke als Gewinner ausgewählt. Das Skript funktioniert ohne Benutzerinteraktion.

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

Dies sind die Ergebnisse mit den von Ihnen bereitgestellten Beispielen:

Bild1

Bild2

Bild3

Den Code zum Auffinden von Linienkreuzungen finden Sie hier: Finden Sie den Schnittpunkt zweier Linien, die mit houghlines opencv gezeichnet wurden

Weitere Informationen zu Hough Lines finden Sie hier .

M. Martin
quelle
2
Danke für die harte Arbeit. Ihre Antwort ist das, wonach ich gesucht habe. Ich wusste, dass Hough Lines hier eine große Rolle spielen würde. Ich habe ein paar Mal versucht, es zu verwenden, konnte aber Ihrer Lösung nicht nahe kommen. Wie Sie kommentiert haben, müssen einige Änderungen an den Parametern vorgenommen werden, um den Ansatz zu verallgemeinern, aber die Logik ist großartig und leistungsfähig.
Waroulolz
1
Ich denke, es ist eine großartige Lösung für diese Art von Problem, es sind keine Benutzereingaben erforderlich. Bravo!!
Meto
@Meto - Ich schätze die hier geleistete Arbeit, stimme aber dem Teil ohne Benutzereingabe nicht zu. Es ist nur ein Alias, ob Sie zur Laufzeit eingeben oder den Schwellenwert ändern, nachdem Sie das Ergebnis nachgeschlagen haben.
Burak
1
@Burak - Ich konnte alle Samples ausführen, die mit denselben Einstellungen geliefert wurden, daher gehe ich davon aus, dass die meisten anderen Karten auch funktionieren würden. Die Schwellenwerteinstellungen müssen also nur einmal vorgenommen werden.
M. Martin
0

Wir wissen, dass Karten entlang der x- und y-Achse gerade Grenzen haben. Wir können dies verwenden, um Teile des Bildes zu extrahieren. Der folgende Code implementiert das Erkennen horizontaler und vertikaler Linien im Bild.

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

Sie müssen nur auf zwei Bereiche klicken, um sie einzuschließen. Ein Beispiel für einen Klickbereich und das entsprechende Ergebnis lauten wie folgt:

Linien result_of_lines

Ergebnisse aus anderen Bildern:

Ergebnis_2 Ergebnis_3

Burak
quelle
0

Ich denke nicht, dass es möglich ist, den ROI des Bildmaterials mithilfe traditioneller Bildverarbeitungstechniken automatisch zuzuschneiden, da die Farben, Abmessungen, Positionen und Texturen für jede Karte dynamisch sind. Sie müssten sich mit maschinellem / tiefem Lernen befassen und Ihren eigenen Klassifikator trainieren, wenn Sie dies automatisch tun möchten. Stattdessen finden Sie hier einen manuellen Ansatz zum Auswählen und Zuschneiden eines statischen ROI aus einem Bild.

Die Idee ist, mithilfe von cv2.setMouseCallback()Ereignishandlern zu erkennen, ob die Maus angeklickt oder losgelassen wurde. Für diese Implementierung können Sie den ROI des Bildmaterials extrahieren, indem Sie die linke Maustaste gedrückt halten und ziehen, um den gewünschten ROI auszuwählen. Wenn Sie den gewünschten ROI ausgewählt haben, drücken Sie c, um den ROI zuzuschneiden und zu speichern. Sie können den ROI mit der rechten Maustaste zurücksetzen.

Gespeicherte Grafik-ROIs

Code

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
quelle