Ekstrak karya seni dari gambar kartu permainan meja dengan OpenCV

10

Saya menulis sebuah skrip kecil dengan python di mana saya mencoba untuk mengekstrak atau memotong bagian dari kartu remi yang hanya mewakili karya seni, menghilangkan sisanya. Saya sudah mencoba berbagai metode ambang tetapi tidak bisa sampai di sana. Juga perhatikan bahwa saya tidak bisa hanya merekam secara manual posisi karya seni karena tidak selalu dalam posisi atau ukuran yang sama, tetapi selalu dalam bentuk persegi panjang di mana yang lainnya hanyalah teks dan batas.

masukkan deskripsi gambar di sini

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

Output saat ini adalah hal terdekat yang bisa saya dapatkan. Saya bisa berada di jalan yang benar dan mencoba beberapa perselisihan lebih lanjut untuk menggambar persegi panjang di sekitar bagian putih, tapi saya tidak berpikir itu metode yang berkelanjutan:

Output saat ini

Sebagai catatan terakhir, lihat kartu di bawah, tidak semua bingkai memiliki ukuran atau posisi yang persis sama, tetapi selalu ada karya seni dengan hanya teks dan batas di sekitarnya. Tidak harus dipotong dengan sangat tepat, tetapi jelas bahwa seni adalah "wilayah" kartu, dikelilingi oleh wilayah lain yang berisi beberapa teks. Tujuan saya adalah mencoba menangkap wilayah karya seni sebaik mungkin.

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

Waroulolz
sumber
Output seperti apa yang Anda tunggu dari kartu "Narcomoeba"? Bahkan tidak memiliki batas berbentuk biasa. Selain itu, saya rasa tidak ada solusi tanpa bantuan pengguna.
Burak
Yang terbaik yang dapat Anda lakukan adalah mengklik titik-titik pembatas, tingkatkan poin-poin itu dengan mencocokkannya dengan sudut yang terdeteksi terdekat, lalu cari tahu bentuknya berdasarkan tepi antar titik. Saya masih ragu implementasi yang baik dari algoritma ini akan mencapai sebagian besar waktu. Menyesuaikan ambang deteksi tepi dan memberi petunjuk tentang kelengkungan garis antara titik (klik kiri: lurus, klik kanan: melengkung, mungkin?) Secara real time dapat meningkatkan peluang keberhasilan.
Burak
1
Saya menambahkan contoh yang lebih baik ke kartu Narcomoeba. Seperti yang Anda lihat saya tertarik pada wilayah karya seni kartu, itu tidak harus 100% tepat. Saya berpendapat, harus ada beberapa transformasi yang memungkinkan saya untuk membagi kartu di 'daerah' yang berbeda untuk berbicara.
Waroulolz
saya pikir Anda pertama kali dapat memotong gambar menjadi 2 jenis (mungkin 4 jenis? sebagai info yang diberikan, gambar akan ditampilkan di atas atau kanan) dan menggunakan opencv untuk memeriksa apakah ada teks dalam gambar. Jadi potong -> saring -> hasil -> potong jika diperlukan lebih mudah bagi OpenCV untuk membuat hasil yang lebih baik.
elprup

Jawaban:

3

Saya menggunakan transformasi garis Hough untuk mendeteksi bagian linear dari gambar. Persimpangan semua garis digunakan untuk membangun semua persegi panjang yang mungkin, yang tidak mengandung titik persimpangan lainnya. Karena bagian dari kartu yang Anda cari selalu yang terbesar dari segi empat (setidaknya dalam sampel yang Anda berikan), saya hanya memilih yang paling besar dari segi empat sebagai pemenang. Skrip berfungsi tanpa interaksi pengguna.

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

Ini adalah hasil dengan sampel yang Anda berikan:

Gambar1

Gambar2

Gambar3

Kode untuk menemukan penyilangan garis dapat ditemukan di sini: temukan titik persimpangan dua garis yang ditarik menggunakan houghlines opencv

Anda dapat membaca lebih lanjut tentang Garis Hough di sini .

M. Martin
sumber
2
Terima kasih atas kerja kerasnya. Jawaban Anda adalah apa yang saya cari. Saya tahu Hough Lines akan memainkan peran besar di sini. Saya mencoba sendiri beberapa kali untuk menggunakannya tetapi tidak bisa mendekati solusi Anda. Seperti yang Anda komentari, beberapa penyesuaian harus dilakukan pada parameter untuk menggeneralisasi pendekatan tetapi logikanya bagus dan kuat.
Waroulolz
1
Saya pikir ini adalah solusi yang bagus untuk masalah seperti ini, tidak perlu input pengguna. Bravo !!
Meto
@Meto - Saya menghargai pekerjaan yang dilakukan di sini, tetapi saya tidak setuju bagian no user input . Itu hanya alias apakah Anda memasukkan saat runtime atau mengubah ambang setelah melihat hasilnya.
Burak
1
@ Burak - Saya dapat menjalankan semua sampel yang disediakan dengan pengaturan yang sama, jadi saya berasumsi, bahwa sebagian besar kartu lain akan berfungsi juga. Jadi pengaturan default hanya perlu dilakukan sekali.
M. Martin
0

Kita tahu bahwa kartu memiliki batas lurus sepanjang sumbu x dan y. Kita dapat menggunakan ini untuk mengekstrak bagian gambar. Kode berikut mengimplementasikan pendeteksian garis horizontal dan vertikal pada gambar.

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

Anda hanya perlu mengklik dua area untuk disertakan. Area klik sampel dan hasil yang sesuai adalah sebagai berikut:

garis result_of_lines

Hasil dari gambar lain:

result_2 result_3

Burak
sumber
0

Saya pikir tidak mungkin untuk memotong ROI karya seni secara otomatis menggunakan teknik pemrosesan gambar tradisional karena sifat dinamis dari warna, dimensi, lokasi, dan tekstur untuk setiap kartu. Anda harus mempelajari pembelajaran mesin / mendalam dan melatih classifier Anda sendiri jika Anda ingin melakukannya secara otomatis. Alih-alih, inilah pendekatan manual untuk memilih dan memotong ROI statis dari sebuah gambar.

Idenya adalah untuk menggunakan cv2.setMouseCallback()dan penangan acara untuk mendeteksi apakah mouse telah diklik atau dilepaskan. Untuk implementasi ini, Anda dapat mengekstraksi ROI karya seni dengan menahan tombol kiri mouse dan menarik untuk memilih ROI yang diinginkan. Setelah Anda memilih ROI yang diinginkan, tekan cuntuk memotong dan menyimpan ROI. Anda dapat mengatur ulang ROI menggunakan tombol kanan mouse.

ROI karya seni yang disimpan

Kode

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
sumber