Albumentations: 같은 transform(변형)을 여러 이미지에 적용하는 방법

Albumentation

Albumentations은 이미지 증강(Augmentation)을 위한 Python 패키지입니다. 사용하기도 편하고 PyTroch 및 Tensorflow에 내장된 같은 기능을 하는 Transform 함수들 보다 최적화가 잘 되어 있어 컴퓨터 비전 분야에서 많이 사용되고 있습니다.

개요

단일 이미지를 Augmentation 할 때는 각 이미지에 대해 랜덤하게 Transform을 적용하는게 일반적 입니다. 그러나 시퀀스 이미지(예. 동영상, 위성 영상 등)를 다룰 때에는 각 시퀀스 데이터는 동일한 Transform을 적용해야 할 때도 있습니다. 별도의 설정 없이 정의한 Transform Compose를 이미지에 적용하게 되면 Transform에는 랜덤성이 존재 하기 때문에 새로운 이미지가 입력될 때마다 다른 파라미터를 가진 다른 Transform이 적용됩니다.

문제의 재현

import albumentations as albu
import cv2
from matplotlib import pyplot as plt

이 글에서는 문제의 현 및 실험을 위해 위와 같이 Albumentations, opencv, matplotlib 패키지를 사용합니다.

def visualize(images:list):
    for i, image in enumerate(images):
        title = f'sequence{i}'

        plt.subplot(1, 5, i+1)
        plt.title(title)
        plt.xticks([])
        plt.yticks([])
        plt.imshow(image)
    plt.show()

먼저 데이터 가시화를 위해 이미지가 들어 있는 List를 받아 matplotlib을 이용해 가시화 하는 함수를 작성합니다.

sequence_size = 5 # sequence 크기

image = cv2.imread('./Lenna.png') # 이미지 로드

# 채널 순서 변경(opencv와 matplotlib의 기본 채널 순서가 다르기 때문)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) 

# sequence_size 만큼 이미지 복사해 List 생성
sequence =  [image.copy() for _ in range(sequence_size)] 

visualize(sequence) # 출력

image 5
이미지 가시화 결과 1

시퀀스의 크기(sequence_size)를 정의하고 이미지를 읽어서 정의한 사이즈 만큼 이미지를 복사한 시퀀스(sequence) 리스트를 정의하고 위에서 작성한 visualize 함수를 이용해 가시화 하였습니다.

# transforms 정의
transforms = albu.Compose([
        albu.HorizontalFlip(p=0.5), # 가로로 뒤집기
        albu.RandomBrightnessContrast(p=0.5), # 밝기 및 대비 조절
        albu.Rotate(180, p=0.5) # 회전
])

# 각 이미지에 transforms 적용
transformed = [transforms(image=sequence[i])['image'] for i in range(len(sequence))]

visualize(transformed) # 출력

image 6
transform 적용한 이미지 가시화 결과

위 코드는 transform을 정의하고 정의한 transform을 각 이미지에 개별적으로 적용한 결과입니다. 당연하게도, 위 가시화 결과를 보면 매 transform이 적용 될 때마다 정의한 파라미터가 랜덤하게 적용되는 것을 확인할 수 있습니다.

문제 해결 방법

additional_targets = { f'image{i}':'image' for i in range(1, sequence_size) }
print(additional_targets)

'''
>>> {'image1': 'image', 'image2': 'image', 'image3': 'image', 'image4': 'image'}
'''

기본 적으로, 여러 이미지에 모두 같은 transform를 적용하기 위해서는 Compose에 additional_targets 파라미터를 사용하면 됩니다. 위 코드 처럼 딕셔너리로 사용할 additional_targets를 정의해 줍니다. 여기서 주의할 점은 딕셔너리를 생성하는 부분에서 반복문의 범위가 sequence_size 보다 1 작다는 점입니다.

왜냐하면 albumentations을 이용해 transform을 적용할때 “image” argument가 필수로 있어야 합니다. 그렇기 때문에, 시퀀스의 첫번째 이미지를 “image” 에 넣어주기 위해 첫번째 이미지를 제외하고 넣어줄 targets을 만든 것 입니다.

transforms = albu.Compose([
        albu.HorizontalFlip(p=0.5),
        albu.RandomBrightnessContrast(p=0.5),
        albu.Rotate(180, p=0.5)
], additional_targets=additional_targets)

input_dict = {('image' if i==0 else f'image{i}'):img for i, img in enumerate(sequence)}

''' 위의 코드와 같은 기능
input_dict = {}
for i, image in enumerate(sequence):
    if i == 0:
        # 첫번째 이미지는 'image'를 key로 사용
        input_dict['image'] = image
    else:
        # 두번째 이미지 부터는 위에서 설정한 additional_targets의 key값에 따라 설정
        input_dict[f'image{i}'] = image
'''

# key(변수명) 및 value(이미지).shape 출력
for key, value in input_dict.items():
    print(f'{key}:{img.shape}')


''' 출력
>>> image:(220, 220, 3)
>>> image1:(220, 220, 3)
>>> image2:(220, 220, 3)
>>> image3:(220, 220, 3)
>>> image4:(220, 220, 3)
'''

이제, 위에서 만든 additional_targets을 transform을 정의 할 때 넣어주고 입력 쌍 “{key(변수 이름):value(이미지)}”을 만들어 줍니다. 바로 위에서도 말했지만 “image” argument는 필수이기 때문에 첫번째 입력 쌍의 key는 “image”로 생성해 줍니다.

transformed = [t_image for t_image in transforms(**input_dict).values()]
visualize(transformed)

image 7
동일 transform을 적용한 결과

마지막으로 만든 input_dict을 언패킹(**) 하여 transforms에 변수로 넣어줍니다. transforms의 출력 결과는 위에서 설정한 key를 사용하여 “transforms[key]”로 얻을 수 있습니다. 위의 가시화 결과를 확인해 보면 의도한 것 처럼 모든 이미지에 같은 transform이 적용된 것을 확인 할 수 있습니다.

참조

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤