Trích xuất tác phẩm nghệ thuật từ hình ảnh thẻ trò chơi với OpenCV


10

Tôi đã viết một kịch bản nhỏ bằng python, nơi tôi đang cố gắng trích xuất hoặc cắt một phần của thẻ chơi chỉ đại diện cho tác phẩm nghệ thuật, loại bỏ tất cả phần còn lại. Tôi đã thử nhiều phương pháp khác nhau nhưng không thể đến đó. Cũng lưu ý rằng tôi không thể đơn giản ghi lại vị trí của tác phẩm nghệ thuật một cách thủ công vì nó không phải luôn ở cùng một vị trí hoặc kích thước, mà luôn ở dạng hình chữ nhật trong đó mọi thứ khác chỉ là văn bản và đường viền.

nhập mô tả hình ảnh ở đây

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

Đầu ra hiện tại là thứ gần nhất tôi có thể nhận được. Tôi có thể đang đi đúng hướng và thử thêm một chút lộn xộn để vẽ một hình chữ nhật xung quanh các phần màu trắng, nhưng tôi không nghĩ đó là một phương pháp bền vững:

Sản lượng hiện tại

Lưu ý cuối cùng, hãy xem các thẻ bên dưới, không phải tất cả các khung đều có cùng kích thước hoặc vị trí, nhưng luôn có một tác phẩm nghệ thuật chỉ có văn bản và viền xung quanh nó. Nó không phải được cắt siêu chính xác, nhưng rõ ràng nghệ thuật là một "khu vực" của thẻ, được bao quanh bởi các khu vực khác có chứa một số văn bản. Mục tiêu của tôi là cố gắng nắm bắt khu vực của tác phẩm nghệ thuật tốt nhất có thể.

nhập mô tả hình ảnh ở đây

nhập mô tả hình ảnh ở đây


Loại đầu ra nào bạn chờ đợi từ thẻ "Narcomoeba"? Nó thậm chí không có một hình dạng thường xuyên. Ngoài ra, tôi không nghĩ có một giải pháp mà không có sự trợ giúp của người dùng.
Burak

Điều tốt nhất bạn có thể làm là nhấp vào các điểm giới hạn, nâng cao các điểm đó bằng cách khớp chúng với góc được phát hiện gần nhất, sau đó tìm ra hình dạng dựa trên các cạnh giữa các điểm. Tôi vẫn nghi ngờ việc thực hiện tốt thuật toán này sẽ hoàn thành hầu hết thời gian. Điều chỉnh ngưỡng phát hiện cạnh và đưa ra gợi ý về độ cong của đường giữa các điểm (nhấp chuột trái: thẳng, nhấp chuột phải: cong, có thể?) Theo thời gian thực có thể tăng cơ hội thành công.
Burak

1
Tôi đã thêm một ví dụ tốt hơn vào thẻ Narcomoeba. Như bạn có thể thấy tôi quan tâm đến khu vực tác phẩm nghệ thuật của thẻ, nó không phải chính xác 100%. Theo ý kiến ​​của tôi, phải có một số biến đổi cho phép tôi chia một thẻ ở các "vùng" khác nhau để nói.
Waroulolz

Tôi nghĩ rằng trước tiên bạn có thể cắt hình ảnh thành 2 loại (có thể là 4 loại? như thông tin được cung cấp, hình ảnh sẽ hiển thị ở phía trên hoặc bên phải) và sử dụng opencv để kiểm tra xem nó có văn bản trong hình ảnh không. Vì vậy, crop -> bộ lọc -> kết quả -> cắt cạnh nếu cần thì opencv sẽ dễ dàng tạo ra kết quả tốt hơn.
elprup

Câu trả lời:


3

Tôi đã sử dụng biến đổi dòng Hough để phát hiện các phần tuyến tính của hình ảnh. Các giao điểm của tất cả các dòng đã được sử dụng để xây dựng tất cả các hình chữ nhật có thể, không chứa các điểm giao nhau khác. Vì một phần của thẻ bạn đang tìm kiếm luôn là phần lớn nhất trong số các hình chữ nhật đó (ít nhất là trong các mẫu bạn cung cấp), tôi chỉ đơn giản chọn phần lớn nhất trong số các hình chữ nhật đó là người chiến thắng. Kịch bản hoạt động mà không có sự tương tác của người dùng.

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

Đây là kết quả với các mẫu bạn cung cấp:

Hình ảnh1

Hình ảnh 2

Hình 3

Mã để tìm đường giao nhau có thể được tìm thấy ở đây: tìm điểm giao nhau của hai đường được vẽ bằng houghlines opencv

Bạn có thể đọc thêm về Hough Lines tại đây .


2
Cam ơn sự nô lực. Câu trả lời của bạn là những gì tôi đang tìm kiếm. Tôi biết Hough Lines sẽ đóng một vai trò lớn ở đây. Tôi đã thử một vài lần để sử dụng nó nhưng không thể đến gần với giải pháp của bạn. Như bạn đã nhận xét, một vài điều chỉnh phải được thực hiện trên các tham số để khái quát hóa cách tiếp cận nhưng logic rất hay và mạnh mẽ.
Waroulolz

1
Tôi nghĩ rằng nó là một giải pháp tuyệt vời cho loại vấn đề này, không cần người dùng nhập liệu. Bravo !!
Meto

@Meto - Tôi đánh giá cao công việc được thực hiện ở đây nhưng tôi không đồng ý phần không có người dùng nhập . Đó chỉ là bí danh cho dù bạn nhập vào thời gian chạy hay thay đổi ngưỡng sau khi tra cứu kết quả.
Burak

1
@Burak - Tôi đã có thể chạy tất cả các mẫu được cung cấp cùng cài đặt, vì vậy tôi giả sử rằng hầu hết các thẻ khác cũng hoạt động tốt. Vì vậy, các thiết lập ngưỡng chỉ phải được thực hiện một lần.
M. Martin

0

Chúng ta biết rằng thẻ có ranh giới thẳng dọc theo trục x và y. Chúng ta có thể sử dụng điều này để trích xuất các phần của hình ảnh. Đoạn mã sau thực hiện phát hiện các đường ngang và dọc trong ảnh.

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

Bạn chỉ cần nhấp vào hai khu vực để bao gồm. Một vùng nhấp chuột mẫu và kết quả tương ứng như sau:

dòng result_of_lines

Kết quả từ những hình ảnh khác:

kết quả_2 kết quả_3


0

Tôi không nghĩ có thể tự động cắt ROI tác phẩm nghệ thuật bằng các kỹ thuật xử lý hình ảnh truyền thống do tính chất động của màu sắc, kích thước, vị trí và kết cấu cho mỗi thẻ. Bạn sẽ phải xem xét máy / học sâu và đào tạo trình phân loại của riêng bạn nếu bạn muốn làm điều đó tự động. Thay vào đó, đây là một cách tiếp cận thủ công để chọn và cắt ROI tĩnh từ một hình ảnh.

Ý tưởng là sử dụng cv2.setMouseCallback()và xử lý sự kiện để phát hiện nếu chuột đã được nhấp hoặc phát hành. Để thực hiện điều này, bạn có thể trích xuất ROI tác phẩm nghệ thuật bằng cách giữ nút chuột trái và kéo để chọn ROI mong muốn. Khi bạn đã chọn ROI mong muốn, nhấn cđể cắt và lưu ROI. Bạn có thể đặt lại ROI bằng nút chuột phải.

ROI tác phẩm nghệ thuật đã lưu

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()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.