OpenCV로 테이블 게임 카드 이미지에서 아트웍 추출


10

파이썬으로 작은 스크립트를 작성하여 아트웍 만 나타내는 카드의 일부를 추출하거나 자르려고 노력하고 나머지는 모두 제거했습니다. 다양한 임계 값 방법을 시도했지만 얻을 수 없었습니다. 또한 아트 워크의 위치는 항상 같은 위치 나 크기가 아니라 항상 텍스트와 테두리가있는 직사각형 모양이므로 아트 워크의 위치를 ​​수동으로 기록 할 수 없습니다.

여기에 이미지 설명을 입력하십시오

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

현재 출력은 내가 얻을 수있는 가장 가까운 것입니다. 나는 올바른 길을 가고 흰색 부분 주위에 직사각형을 그리기 위해 좀 더 고민을 시도 할 수 있지만 그것이 지속 가능한 방법이라고 생각하지 않습니다.

전류 출력

마지막으로 아래의 카드를 참조하십시오. 모든 프레임의 크기 나 위치가 정확히 같은 것은 아니지만 항상 텍스트와 테두리 만있는 아트 워크가 있습니다. 그것은 매우 정확하게자를 필요는 없지만, 분명히 예술은 텍스트의 일부를 포함하는 다른 영역으로 둘러싸인 카드의 "영역"입니다. 저의 목표는 작품의 영역을 가능한 한 잘 포착하는 것입니다.

여기에 이미지 설명을 입력하십시오

여기에 이미지 설명을 입력하십시오


"Narcomoeba"카드에서 어떤 종류의 출력을 기다리는가? 규칙적인 모양의 경계도 없습니다. 또한 사용자 지원이없는 솔루션이 있다고 생각하지 않습니다.
Burak

가장 좋은 방법은 경계점을 클릭하고 가장 가까운 감지 된 모서리와 일치시켜 점을 강화한 다음 점 사이의 모서리를 기준으로 모양을 찾는 것입니다. 나는 여전히이 알고리즘의 좋은 구현이 대부분의 시간을 달성 할 것이라고 의심한다. 에지 감지 임계 값을 조정하고 실시간으로 포인트 (왼쪽 클릭 : 직선, 오른쪽 클릭 : 곡선, 아마도?) 사이의 선 곡률에 대한 힌트를 제공하면 성공 가능성이 높아질 수 있습니다.
Burak

1
Narcomoeba 카드에 더 좋은 예를 추가했습니다. 보시다시피, 카드의 아트 워크 영역에 관심이 있으므로 100 % 정확할 필요는 없습니다. 제 생각에는, 다른 '지역'에서 카드를 나누어 말할 수 있도록 몇 가지 변형이 있어야합니다.
Waroulolz

먼저 이미지를 2 가지 유형으로 자르고 (정보가 제공 된 것처럼 4 가지 유형입니까? 이미지는 상단 또는 오른쪽에 표시) opencv를 사용하여 이미지에 텍스트가 있는지 확인할 수 있습니다. 따라서 잘라 내기-> 필터-> 결과-> 필요한 경우 가장자리를 자르는 것이 opencv가 더 나은 결과를 얻는 데 더 쉽습니다.
엘프 럽

답변:


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를 사용하여 그려진 두 선의 교점

허프 라인에 대해 더 읽어 볼 수 있습니다 여기를 참조하십시오 .


2
노력해 주셔서 감사합니다. 당신의 대답은 내가 찾던 것입니다. 허프 라인 즈가 여기서 큰 역할을 할 것이라는 것을 알았습니다. 나는 그것을 사용하기 위해 몇 번 나 자신을 시도했지만 귀하의 솔루션에 가까워 질 수 없었습니다. 언급했듯이 접근 방식을 일반화하려면 매개 변수를 약간 조정해야하지만 논리는 강력하고 강력합니다.
Waroulolz

1
이런 종류의 문제에 대한 훌륭한 솔루션이며 사용자 입력이 필요하지 않습니다. 브라보!!
Meto

@Meto-여기서 수행 한 작업에 감사하지만 사용자가 입력 하지 않은 부분에 동의하지 않습니다 . 런타임에 입력하든 결과를 찾은 후 임계 값을 변경하든 별명 일뿐입니다.
Burak

1
@Burak-동일한 설정으로 제공된 모든 샘플을 실행할 수 있었으므로 대부분의 다른 카드도 잘 작동한다고 가정합니다. 따라서 해당 설정 값은 한 번만 설정하면됩니다.
M. Martin

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

각 카드의 색상, 치수, 위치 및 질감의 동적 특성으로 인해 전통적인 이미지 처리 기술을 사용하여 아트 워크 ROI를 자동으로 자르는 것이 불가능하다고 생각합니다. 기계 / 딥 러닝을 조사하고 자동으로 분류하려면 자체 분류기를 훈련시켜야합니다. 대신 이미지에서 정적 ROI를 선택하고 자르는 수동 방법이 있습니다.

아이디어는 cv2.setMouseCallback()마우스를 클릭하거나 놓았는지 여부를 감지 하기 위해 이벤트 핸들러 를 사용하는 것입니다 . 이 구현에서는 마우스 왼쪽 버튼을 누른 상태에서 드래그하여 원하는 ROI를 선택하여 아트 워크 ROI를 추출 할 수 있습니다. 원하는 ROI를 선택한 후 c를 눌러 ROI 를 자르고 저장하십시오. 마우스 오른쪽 버튼을 사용하여 ROI를 재설정 할 수 있습니다.

저장된 아트 워크 ROI

암호

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()
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.