본문 바로가기
  • 머킹이의 머신로그
프로젝트/팀 프로젝트

[프로젝트]파이썬으로 회전 이미지 바운딩 박스(AABB) 처리

by 머킹 2023. 9. 6.
728x90

파이썬으로 회전 이미지 바운딩 박스(AABB) 처리 

 

안녕하세요. 머킹입니다.

꽤 오랜 시간이 지나고 나서 글을 쓰는 것 같습니다.

이 블로그를 시작할 때 저의 공부를 정리하며 

제 공부가 누군가에게 도움이 되길 바라며 시작했는데요.

 

생각보다 학원의 일정이 너무 힘들고 

지속되는 건강 악화로 처음에 잡은 목표를 이루지 못했습니다ㅠㅠ

 

갑자기 뜬금스럽게 회전이미지니 바운딩 박스니 프로젝트니 당황스럽죠?

이제부터 제대로 정리를 하고자 합니다.

우선, 제가 가장 오랜 시간 붙잡았던 프로젝트를 정리하고자 합니다.

 

제 프로젝트는 끝나면 알려드리겠지만 

간단하게 쓰레기를 분류하는 머신러닝을 만들고자 합니다.

 

프로젝트를 위해서 아래의 과정을 진행했습니다.

 

주제선정 => 프로젝트 배경, 목표 수립 => 데이터 수집 => 데이터 전처리

=> 모델 선정 => 모델 훈련 => 앱 개발 => 배포

 

이 순서대로 진행을 하고 있는데요.

오늘 말한 AABB는 데이터 전처리에 들어갑니다.


AABB 박스란?

이미지를 회전하는 증강기법을 사용할 때 가장 문제 됐던 부분은 

바운딩 박스를 처리하는 것이었습니다.

 

일단 바운딩 박스는 예시로 보면 바로 이해하실 수 있으실 겁니다.

객체 탐지 모델에 사용되는 데이터의 크기가 방대하기 때문에, 바운딩 박스를 통하여 객체를 올바르게 탐지하고

딥러닝 과정에서 바운딩 박스 영역만 대상이 되기 때문에, 딥러닝을 효율적으로 수행할 수 있습니다.

 

바운딩 박스는 특정 사물을 탐지하여 모델을 효율적으로 학습할 수 있도록 도움을 주는 방법입니다.

객체 탐지 모델에서 바운딩 박스는 타깃 위치를 특정하기 위해 사용됩니다.

타겟 위치를 X와 Y축을 이용하여 사각형으로 표현합니다.

예를 들어, 바운딩 박스 값은 (X 최솟값, Y 최소값, X 최댓값, Y 최댓값)으로 표현이 됩니다.

 

AABB와 OBB형식을 대표적으로 사용하는데요.

사진을 보면 AABB와 OBB의 형식의 차이를 알 수 있었습니다.

 

이미지를 어떻게 사용하느냐에 따라 선호하는 방법은 다르지만

저희는 AABB방법이 필요했습니다.

 


모델의 올바른 학습을 위해서

이미지를 회전해도 AABB형식이길 바라는 마음이었는데

왜인지 자꾸 OBB 형식으로 바운딩 박스가 그려졌습니다ㅠㅠ

 

 

샘플에 샘플에 샘플에 샘플에...

이렇게 시도하면서 도전해 보니 알고 보니 바운딩박스 회전을 계산하지 않고 있었습니다 ㅎㅎ

이런 이미지만 주구장창 나왔습니다...

import json
import cv2
import numpy as np
import matplotlib.pyplot as plt

# 1. 이미지와 바운딩 박스 불러오기
image_path_final = "샘플 이미지.jpg"
image_final = cv2.imread(image_path_final)
image_final = cv2.cvtColor(image_final, cv2.COLOR_BGR2RGB)

json_path_final = "샘플 이미지.Json"
with open(json_path_final, 'r') as f:
    data_final = json.load(f)

bounding_data_final = data_final['Bounding']
bounding_boxes_final = []
for item in bounding_data_final:
    x1 = int(item['x1'])
    y1 = int(item['y1'])
    x2 = int(item['x2'])
    y2 = int(item['y2'])
    bounding_boxes_final.append([(x1, y1), (x2, y1), (x2, y2), (x1, y2)])


def rotate_image_bbox(image, bounding_boxes, angle):
    h, w = image.shape[:2]
    cx, cy = w // 2, h // 2
    
    # 회전 변환 행렬 계산
    matrix = cv2.getRotationMatrix2D((cx, cy), angle, 1)
    
    # 회전시 이미지가 짤리지 않게 회전 후의 이미지 크기 계산
    abs_cos = abs(matrix[0, 0])
    abs_sin = abs(matrix[0, 1])
    new_w = int(h * abs_sin + w * abs_cos)
    new_h = int(h * abs_cos + w * abs_sin)
    
    # 변환 행렬 업데이트
    matrix[0, 2] += new_w / 2 - cx
    matrix[1, 2] += new_h / 2 - cy

    rotated_image = cv2.warpAffine(image, matrix, (new_w, new_h))
    
    rotated_boxes = []
    for box in bounding_boxes:
        rotated_box = []
        for (x, y) in box:
            new_x = matrix[0][0] * x + matrix[0][1] * y + matrix[0][2]
            new_y = matrix[1][0] * x + matrix[1][1] * y + matrix[1][2]
            rotated_box.append((new_x, new_y))
        rotated_boxes.append(rotated_box)
    
    return rotated_image, rotated_boxes


# 결과 시각화 함수
def visualize_results(image, bounding_boxes, rotated_image, rotated_boxes):
    fig, axs = plt.subplots(1, 2, figsize=(20, 10))

    axs[0].imshow(image)
    for box in bounding_boxes:
        box_np = np.array(box, np.int32)
        box_np = np.append(box_np, [box_np[0]], axis=0)
        axs[0].plot(box_np[:, 0], box_np[:, 1], color='red')
    axs[0].set_title("Original Image")

    axs[1].imshow(rotated_image)
    for box in rotated_boxes:
        box_np = np.array(box, np.int32)
        box_np = np.append(box_np, [box_np[0]], axis=0)
        axs[1].plot(box_np[:, 0], box_np[:, 1], color='blue')
    axs[1].set_title("Rotated Image")

    plt.tight_layout()
    plt.show()

# 0도부터 360도까지 45도 간격으로 이미지와 바운딩 박스 회전
all_angles = list(range(0, 361, 45))

for angle in all_angles:
    rotated_image, rotated_boxes = rotate_image_bbox(image_final, bounding_boxes_final, angle)
    visualize_results(image_final, bounding_boxes_final, rotated_image, rotated_boxes)

이게 문제의 코드입니다.

 

이런저런 시도 끝에 회전된 꼭짓점에서 계산을 하기로 했습니다.

출처 : https://medium.com/@coding-otter/image-and-bounding-box-rotation-using-opencv-python-2def6c39453

처음에 문제점은 꼭지점 계산을 고려하지 않고

바운딩 박스를 그대로 회전시켜서 

이미지와 함께 바운딩 박스가 회전되는 결과를 볼 수 있었습니다.

 

def rotate_image_aabb_bbox(image, bounding_boxes, angle):
    h, w = image.shape[:2]
    cx, cy = w // 2, h // 2
    
    # 회전 변환 행렬 계산
    matrix = cv2.getRotationMatrix2D((cx, cy), angle, 1)
    
    # 회전시 이미지가 짤리지 않게 회전 후의 이미지 크기 계산
    abs_cos = abs(matrix[0, 0])
    abs_sin = abs(matrix[0, 1])
    new_w = int(h * abs_sin + w * abs_cos)
    new_h = int(h * abs_cos + w * abs_sin)
    
    # 변환 행렬 업데이트
    matrix[0, 2] += new_w / 2 - cx
    matrix[1, 2] += new_h / 2 - cy

    rotated_image = cv2.warpAffine(image, matrix, (new_w, new_h))
    
    rotated_aabb_boxes = []
    for box in bounding_boxes:
        rotated_box = []
        for (x, y) in box:
            new_x = matrix[0][0] * x + matrix[0][1] * y + matrix[0][2]
            new_y = matrix[1][0] * x + matrix[1][1] * y + matrix[1][2]
            rotated_box.append((new_x, new_y))
        
        # 회전된 꼭짓점에서 AABB를 계산
        rotated_box_np = np.array(rotated_box)
        x_min, y_min = rotated_box_np.min(axis=0)
        x_max, y_max = rotated_box_np.max(axis=0)
        aabb = [(x_min, y_min), (x_max, y_min), (x_max, y_max), (x_min, y_max)]
        rotated_aabb_boxes.append(aabb)
    
    return rotated_image, rotated_aabb_boxes


# 0도부터 360도까지 45도 간격으로 이미지와 바운딩 박스 회전 (AABB 방식 사용)
for angle in all_angles:
    rotated_image, rotated_aabb_boxes = rotate_image_aabb_bbox(image_final, bounding_boxes_final, angle)
    visualize_results(image_final, bounding_boxes_final, rotated_image, rotated_aabb_boxes)

 

수정된 코드를 보면 꼭짓점에서부터 계산하는 값을 입력했습니다.

 

이렇게 원하는 방법으로 회전된 것을 알 수 있었습니다.

 


이렇게 간단하게 설명하지만

사실 4일 내내 저 코드만 바라보고 있었습니다.

chat GPT의 도움도 많이 받았으나, 제가 원하는 것을 잘 입력하지 못하더라고요.

 

저에게 도움을 주신 해외 블로그, 국내 블로그....

여러 블로그들에게 너무 감사드립니다....

 

그럼 프로젝트 글들을 계속 이어가겠습니다.

감사합니다.