정보/R&E

OpenCV image를 grid-based tiles로 변환하기

MinseobKim 2021. 8. 30. 15:12

이전 글 : OpenCV-python를 이용하여 Unrailed! 윈도우 창 캡쳐하기 https://minseob.tistory.com/7

 

OpenCV를 이용하여 게임 화면을 캡쳐한 다음에는 이 화면을 에이전트가 맵을 파악할 수 있도록 grid-based tiles로 변환해주는 작업이 필요하다.

이와 같은 격자 타일 모양의 맵을 grid-based tiles라고 한다.

그전에 이전 글에서 캡쳐한 Unrailed! 윈도우 창에서 물체를 인식할 수 있도록 변환해주는 작업이 필요하다.

 

cut_image함수로 화면에서 인식에 필요한 맵 부분을 잘라낸다.

def cut_image(im):
    """Cut the image"""
    im = rotate(im, -8)
    x, y = 0, 125
    h, w = 320, 800
    im = im[y:y + h, x:x + w]

    rows, cols = im.shape[:-1]

    a, b, c = [382, 52], [500, 50], [400, 200]
    offsetx = 20
    d, e, f = [382 + offsetx, 52], [500 + offsetx, 50], [400, 200]

    pts1 = np.float32([a, b, c])
    pts2 = np.float32([d, e, f])

    dst = im

    M = cv2.getAffineTransform(pts1, pts2)
    dst = cv2.warpAffine(im, M, (cols, rows))

    return dst

또한 rotate함수를 정의하여 기울어진 화면을 바로잡아준다.

def rotate(im, angle):
    """Rotate the image"""
    image_center = tuple(np.array(im.shape[1::-1]) / 2)
    rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)
    result = cv2.warpAffine(
        im, rot_mat, im.shape[1::-1], flags=cv2.INTER_LINEAR)
    return result

 

그리고 draw_grid함수로 격자를 그린다.

def draw_grid(im):
    """Draw grid to the image"""
    # Draw a line along the x-axis
    for x in range(5, 900, 22):
        for y in range(0, 400):
            im = set_pixel_color(im, x, y, (100, 0, 100))

    # Draw a line along the y-axis
    for y in range(0, 400, 16):
        for x in range(0, 900):
            im = set_pixel_color(im, x, y, (100, 0, 100))

 

이제 화면에서 각각의 물체들을 인식해야 한다.

인식해야 할 물체들은 플레이어, 도끼, 곡괭이, 나무, 돌, 검은 돌, 철도, 강 등이 있다.

각각 물체에 대해서 파일을 만든다.

그 후 각각의 파일에서 

다음과 같이 인식해야 할 각 물체의 색상, 채도, 명도를 HSV형식으로 변수를 선언한다.

HSV_MIN_THRESH = np.array([0, 0, 65])
HSV_MAX_THRESH = np.array([180, 255, 255])

HSV_MIN_THRESH_2 = np.array([0, 0, 0])
HSV_MAX_THRESH_2 = np.array([180, 255, 63])

 

get_bin함수로 화면 속에서 물체의 색 범위에 해당하는 색을 가지는 위치를 이진수로서 반환한다. 

def get_bin(image, hsv_image, color=(255, 150, 255)):
    """Get binary of the black rock found in image"""
    # Remove last value because we don't need the channels
    h, w = image.shape[:-1]

    # Create the bin_image with the treshold values on the hsv image and not BGR
    bin_image = cv2.inRange(hsv_image, HSV_MIN_THRESH, HSV_MAX_THRESH)
    bin_image += cv2.inRange(hsv_image, HSV_MIN_THRESH_SUB, HSV_MAX_THRESH_SUB)

    # Get the locations of the the black rock then remove rest pixels
    nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(
        bin_image, 8, cv2.CV_32S)
    _remove_random_from_bin_image(bin_image, nb_components, stats, w, h)

    dilated_bin_image = cv2.dilate(bin_image,
                                   np.ones((3, 3), np.uint8),
                                   iterations=2)

    result = cv2.bitwise_and(image, image, mask=dilated_bin_image)

    return result

 

그 후 draw_contours 함수는 각 물체를 인식하여 경계선을 그려준다.

def draw_contours(image, hsv_image, color=(255, 150, 255)):
    """Draws contours of the black rock found in image"""
    # Remove last value because we don't need the channels
    h, w = image.shape[:-1]

    # Create the bin_image with the treshold values on the hsv image and not BGR
    bin_image = cv2.inRange(hsv_image, HSV_MIN_THRESH, HSV_MAX_THRESH)
    bin_image += cv2.inRange(hsv_image, HSV_MIN_THRESH_SUB, HSV_MAX_THRESH_SUB)

    # Get the locations of the the black rock then remove rest pixels
    nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(
        bin_image, 8, cv2.CV_32S)
    _remove_random_from_bin_image(bin_image, nb_components, stats, w, h)

    dilated_bin_image = cv2.dilate(bin_image,
                                   np.ones((3, 3), np.uint8),
                                   iterations=2)

    result = cv2.bitwise_and(image, image, mask=dilated_bin_image)

    contours, hierarchy = cv2.findContours(dilated_bin_image,
                                           cv2.RETR_EXTERNAL,
                                           cv2.CHAIN_APPROX_NONE)
    cv2.drawContours(image, contours, -1, color, 3)

    return result

 

이 과정을 거치면 다음과 같이 크기에 맞게 잘라지고 회전되며 각 물체가 인식되어 경계선으로 표현된 화면이 생성된다.

이제 이 화면을 에이전트가 인식하기 편하기 위해 grid-based tiles로 간단화할 것이다.

 

각 물체 별로 다른 색을 draw함수에 넘겨준다.

    def draw_call(self, e, j, i):
        if e == 'M':
            self.draw(i, j, (86, 215, 156))
        elif e == 'T':
            self.draw(i, j, (109, 196, 63))
        elif e == 'K':
            self.draw(i, j, (118, 150, 182))
        elif e == 'B':
            self.draw(i, j, (65, 65, 65))
        elif e == 'R':
            self.draw(i, j, (243, 255, 114))
        elif e == '0':
            self.draw(i, j, (0, 0, 0))
        elif e == 'P':
            self.draw(i, j, (0, 100, 255))
        elif e == 'A':
            self.draw(i, j, (150, 100, 200))
        elif e == 'I':
            self.draw(i, j, (200, 100, 150))
        elif e == 'D':
            self.draw(i, j, (255, 255, 255))  # Debug
        else:
            self.draw(i, j, (155, 0, 155))

 

그 후 draw_cell 함수로 각 셀을 그린다.

    def draw_cell(self):
        size_x = self.width * self.cell_size_x
        size_y = self.height * self.cell_size_y

        for y in range(0, size_y):
            for x in range(0, size_x):
                if x % self.cell_size_x == 0 or y % self.cell_size_y == 0:
                    functions.set_pixel_color(self.im, x, y, (0, 255, 0))
                else:
                    functions.set_pixel_color(self.im, x, y, (155, 0, 155))

 

중간중간 많은 과정이 생략되었지만 지금까지의 과정을 종합하여 다음과 같이 grid-based tile 맵을 만들 수 있다.