Витягніть твори з зображень настільної ігрової карти за допомогою OpenCV


10

Я написав невеликий сценарій у python, де я намагаюся витягнути або обрізати ту частину гральної карти, яка представляє лише твір мистецтва, видаляючи всі інші. Я пробував різні методи порогового визначення, але не зміг дістатися. Також зауважте, що я не можу просто записати вручну положення художнього твору, оскільки воно не завжди знаходиться в одному положенні чи розмірі, але завжди у прямокутній формі, де все інше - це лише текст та межі.

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

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

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

Поточний вихід

В якості останньої замітки дивіться на картках нижче: не всі кадри мають однакові розміри чи позиції, але завжди є твір ілюстрації з лише текстом і рамками навколо нього. Це не повинно бути надто точно вирізаним, але явно мистецтво - це "регіон" картки, оточений іншими регіонами, що містять текст. Моя мета - спробувати захопити область мистецтва так добре, як я можу.

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

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


Який вихід ви чекаєте від картки «Наркомоба»? Він навіть не має правильної форми межі. Крім того, я не думаю, що рішення не існує без допомоги користувача.
Бурак

Найкраще, що ви можете зробити, це натиснути обмежувальні точки, покращити ці точки, зіставивши їх до найближчого виявленого кута, а потім з’ясувати форму на основі ребер між точками. Я все ще сумніваюся, що хороша реалізація цього алгоритму досягатиме більшості випадків. Регулювання порогу виявлення ребра та натяк про викривлення лінії між точками (клацання лівою кнопкою миші: прямий, правою кнопкою миші: вигнута, можливо?) В реальному часі може збільшити шанс на успіх.
Бурак

1
Я додав кращий приклад до картки Наркомоби. Як ви бачите, мені цікаво область оформлення карти, вона не повинна бути точно на 100%. На мою думку, мають бути певні трансформації, які дозволяють мені розділити карту в різних регіонах, так би мовити.
Варулольц,

Я думаю, що ви можете спочатку обрізати зображення на 2 типи (можливо, 4 типи? як надано інформацію, зображення відображатиметься вгорі або в правій частині) та використовувати opencv, щоб перевірити, чи є у ньому текст. Тож обрізати -> фільтр -> результат -> вирізати край, якщо потрібно, для opencv простіше зробити кращий результат.
Elprup

Відповіді:


3

Я використовував перетворення лінії Хоф для виявлення лінійних частин зображення. Перехрестя всіх ліній використовувалося для побудови всіх можливих прямокутників, які не містять інших точок перетину. Оскільки частина картки, яку ви шукаєте, завжди є найбільшою з цих прямокутників (принаймні, у зразках, які ви надали), я просто вибрав найбільший із цих прямокутників як переможець. Сценарій працює без взаємодії з користувачем.

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

Ось результати із наданими вами зразками:

Зображення1

Зображення2

Зображення3

Код для знаходження перетину ліній можна знайти тут: знайдіть точку перетину двох ліній, намальованих за допомогою хонгліній opencv

Більше про Hough Lines ви можете прочитати тут .


2
Дякую за наполегливу працю. Ваша відповідь - те, що я шукав. Я знав, що Hough Lines зіграє тут велику роль. Я кілька разів намагався використовувати його, але не зміг наблизитися до вашого рішення. Як ви прокоментували, потрібно зробити кілька налаштувань параметрів, щоб узагальнити підхід, але логіка велика і потужна.
Варулольц

1
Я думаю, що це чудове рішення для подібних проблем, не потрібно вводити користувачів. Браво !!
Мето

@Meto - Я вдячний за виконану тут роботу, але я не погоджуюся з частиною вводу користувача, яка не вводиться . Це лише псевдонім, вводячи ви під час виконання або змінюючи поріг після пошуку результату.
Бурак

1
@Burak - Мені вдалося запустити всі зразки, які постачалися з тими самими налаштуваннями, тож я припускаю, що більшість інших карт також буде працювати. Таким чином, налаштування цього режиму потрібно зробити лише один раз.
М. Мартин

0

Ми знаємо, що картки мають прямі межі вздовж осей x і y. Ми можемо використовувати це для вилучення частин зображення. Наступний код реалізує виявлення горизонтальних і вертикальних ліній на зображенні.

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

Вам потрібно просто натиснути дві області, щоб включити їх. Зразок області клацання та відповідний результат:

лінії result_of_lines

Результати з інших зображень:

результат_2 результат_3


0

Я не думаю, що неможливо автоматично обрізати рентабельність інвестицій із застосуванням традиційних методів обробки зображень через динамічний характер кольорів, розмірів, розташування та текстур для кожної картки. Вам потрібно буде вивчити машинне / глибоке навчання та навчити власний класифікатор, якщо ви хочете робити це автоматично. Натомість, ось ручний підхід до вибору та обрізання статичної рентабельності інвестицій із зображення.

Ідея полягає у використанні cv2.setMouseCallback()обробників подій і для виявлення того, чи була натиснута або відпущена миша. Для цієї реалізації ви можете отримати рентабельність інвестицій із зображенням, утримуючи ліву кнопку миші та перетягнувши, щоб вибрати бажану рентабельність інвестицій. Вибравши бажану рентабельність інвестицій, натисніть, cщоб обрізати та зберегти рентабельність інвестицій. Ви можете скинути рентабельність інвестицій за допомогою правої кнопки миші.

Збережені рентабельність інвестицій

Код

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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.