[PyTorch] 이미지 데이터 증강(Image Data Augmentation)
Posted: Updated:
AI 스터디를 하며 ‘파이토치 트랜스포머를 활용한 자연어 처리와 컴퓨터비전 심층학습’ 교재를 정리한 글입니다.
데이터 증강(Data Augmentation)
이미지 데이터
객체 검출, 인식, 이미지 분류 등 이미지 처리 모델 구성 시 데이터셋 크기를 쉽게 늘리기 위해 사용
회전, 대칭, 이동, 크기 조정 등이 있음
토치비전(torchvision) 라이브러리와 이미지 증강(imgaug) 라이브러리
토치비전 라이브러리: 이미지 데이터 증강, 변형을 위한 기본적인 메서드 제공
이미지 증강 라이브러리: 토치비전에서 제공하지 않는 기능 제공
pip install imgaug
변환(transfroms) 모듈
이미지 데이터 증강을 위해 사용
이미지 변환과 관련된 기능, 여러 모델 매개변수를 묶어주는 시퀀셜(Sequential) 역할을 하는 통합(Compose) 클래스 사용
from PIL import Image
from torchvision import transforms
transform = transforms.Compose(
[
transforms.Resize(size=(512, 512)), # 이미지 크기 변환
# 텐서로 변환
# PIL.Image to Tensor, 최대 최소 정규화(Min-max Normalization) 진행
# [높이, 너비, 채널] -> [채널, 높이, 너비]
transforms.ToTensor()
]
)
image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
transformed_image.shape
출력
torch.Size([3, 512, 512])
회전 및 대칭
이미지를 회전하거나 대칭하면 변형된 이미지에 강한 모델을 구축해 일반화된 성능 구현 가능
과도한 증강 시 본래 특징 소실, 실제 데이터에 존재하지 않는 데이터 생성 가능
import matplotlib.pyplot as plt
transform = transforms.Compose(
[
transforms.RandomRotation(degrees=30, expand=False, center=None), # 무작위 회전: -30~30 사이 각도로 회전, expand: True 설정 시 여백 생성되지 않음, center: 시퀀스 형태로 전달하며 미전달 시 왼쪽 상단 기준
transforms.RandomHorizontalFlip(p=0.5), # 수평 대칭 50% 확률로 적용
transforms.RandomVerticalFlip(p=0.5) # 수직 대칭 50% 확률로 적용
]
)
image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
plt.imshow(transformed_image)
plt.axis('off')
plt.show()
출력
자르기 및 패딩
객체 인식 모델 구성 시 학습 데이터 크기가 일정하지 않거나 주요 객체가 일부 영역에만 작게 존재할 수 있음
객체가 존재하는 위치로 이미지를 잘라 불필요한 특징 감소, 패딩을 주어 이미지 크기를 동일하게 맞출 수 있음
과도하게 자르면 검출하려는 객체가 없거나 패딩을 너무 많이 주어 특징 영향이 감소할 수 있으므로 주의
transform = transforms.Compose(
[
# 무작위 자르기 클래스: 정수 입력 시 정사각형, 시퀀스 입력 시 높이, 너비 순서로 자름
# 패딩으로 자를 때 발생하는 여백 공간 할당 가능
transforms.RandomCrop(size=(512, 512)),
# 패딩 클래스: 이미지 테두리에 특정 방식이나 고정값으로 이미지 확장
# RGB로 테두리 생성 가능, reflect, symmetric을 주면 RGB 무시하고 이미지 픽셀 반사 혹은 대칭
transforms.Pad(padding=50, fill=(127, 127, 255), padding_mode="constant")
]
)
image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
plt.imshow(transformed_image)
plt.axis('off')
plt.show()
출력
크기 조정
원활한 학습을 위해서 학습 데이터에 사용되는 이미지 크기가 일정해야 함
데이터 자체 수정 시 입력받는 이미지 크기가 달라지면 데이터 관리가 어려움
따라서 증강 클래스로 이미지 크기 변환
transform = transforms.Compose(
[
transforms.Resize(size=(512, 512))
]
)
image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
plt.imshow(transformed_image)
plt.axis('off')
plt.show()
출력
변형
기하학적 변환(Geometric Transfrom): 인위적으로 확대, 축소, 위치 변경, 회전, 외곡 등 이미지 형태 변환
아핀 변환(Affine Transformation): $2 \times 3$ 행렬 사용, 행렬 곱셈에 벡터 합 활용
원근 변환(Perspective Transformation): $3 \times 3$ 행렬 사용, 호모그래피(Homography)로 모델링할 수 있는 변환
transform = transforms.Compose(
[
transforms.RandomAffine( # 아핀 변환
# 각도, 이동, 척도, 전단 입력
degrees=15, translate=(0.2, 0.2), scale=(0.8, 1.2), shear=15
)
]
)
image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
plt.imshow(transformed_image)
plt.axis('off')
plt.show()
출력
색상 변환
색상(Hue), 채도(Saturation), 명도(Brightness), 대비(Contrast) 등을 변경
특정 색상에 편향되지 않도록 픽셀값 변환, 정규화
색상 변경을 통해 간접적으로 데이터셋의 일반화 효과를 얻을 수 있음
transform = transforms.Compose(
[
transforms.ColorJitter(
brightness=0.3, contrast=0.3, saturation=0.3, hue=0.3
),
transforms.ToTensor(),
transforms.Normalize( # 픽셀 평균, 표준편차 활용하여 정규화
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
),
transforms.ToPILImage()
]
)
image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
plt.imshow(transformed_image)
plt.axis('off')
plt.show()
출력
노이즈
특정 픽셀값에 편향되지 않도록 임의의 노이즈 추가
학습에 직접 포함하지 않고 일반화 능력, 강건성(Robustness) 평가에 사용 가능
import numpy as np
np.bool = np.bool_ # Deprecated 오류 방지
from PIL import Image
from torchvision import transforms
from imgaug import augmenters as iaa
class IaaTransforms:
def __init__(self): # 증강 방법 설정
self.seq = iaa.Sequential([
iaa.SaltAndPepper(p=(0.03, 0.07)), # 점잡음(Sail and pepper noise) 적용
iaa.Rain(speed=(0.3, 0.7)) # 빗방울 레이어 적용
])
def __call__(self, images):
images = np.array(images) # # PIL 이미지를 ndarray 형식으로 변환
print(images.shape, images.dtype)
augmented = self.seq.augment_image(images) # augment_image로 증강 적용
return Image.fromarray(augmented) # 다시 PIL.Image 형식으로 반환
transform = transforms.Compose([
IaaTransforms()
])
image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
plt.imshow(transformed_image)
plt.axis('off')
plt.show()
출력
(857, 1280, 3) uint8
컷아웃 및 무작위 지우기
컷아웃(Cutout): 이미지에서 임의의 사각형 영역 삭제, 픽셀값을 0으로 채우는 방법, 폐색 영역(Occlusion)에 강하게 해줌
무작위 지우기(Random Erasing): 임의의 사각형 영역을 삭제해 무작위 픽셀값으로 채우는 방법, 일부 영역 누락에 강함
두 방법 모두 이미지 객체 일부 누락 시에도 모델을 견고하게 함
transform = transforms.Compose([
transforms.ToTensor(), # 무작위 지우기를 위해 Tensor로 변환
transforms.RandomErasing(p=1.0, value=0), # 0으로 할당하여 컷아웃 실행
transforms.RandomErasing(p=1.0, value="random"), # 랜덤 할당 시 무작위 지우기, Tensor 형식만 지원
transforms.ToPILImage()
])
image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
plt.imshow(transformed_image)
plt.axis('off')
plt.show()
출력
혼합 및 컷믹스
혼합(Mixup): 두 개 이상 이미지를 혼합(Blending)해 새로운 이미지 생성, 픽셀값을 선형으로 결합하여 두 개의 이미지가 겹쳐 흐릿한 형상을 지님, 레이블링이 달라도 낮은 오류를 보이며 다중 레이블(Multi-label) 문제에 강함
컷믹스(CutMix): 네이버 클로바에서 발표한 방법, 이미지 패치(patch) 영역에 다른 이미지 덮어씌우는 잘라내고 붙여넣기 방식, 이미지 특정 영역 기억하지 않고 전체를 보고 판단할 수 있게 함
import numpy as np
from PIL import Image
from torchvision import transforms
class Mixup: # 혼합
def __init__(self, target, scale, alpha=0.5, beta=0.5):
self.target = target # 혼합하려는 이미지
self.scale = scale # 크기
self.alpha = alpha # 혼합 비율
self.beta = beta # 혼합 비율
def __call__(self, image):
image = np.array(image)
target = self.target.resize(self.scale)
target = np.array(target)
mix_image = image * self.alpha + target * self.beta
return Image.fromarray(mix_image.astype(np.uint8))
transform = transforms.Compose(
[
transforms.Resize((512, 512)),
Mixup(
target=Image.open("datasets/images/dog.jpg"),
scale=(512, 512),
alpha=0.5,
beta=0.5
)
]
)
image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
plt.imshow(transformed_image)
plt.axis('off')
plt.show()
출력
토치비전 이미지 데이터 증강 클래스
범위 | 클래스 | 입력 | 설명 |
---|---|---|---|
통합 | transforms.Compose() | - | 조합한 변형을 하나로 묶음 |
텐서 변환(1) | transforms.ToTensor() | PIL | PIL 이미지 또는 ndarray 형식을 Tensor로 변환 |
형식: (H x W x C) -> (C x H x W) | |||
범위: [0, 255] -> [0, 1] | |||
텐서 변환(2) | transforms.PILToTensor() | PIL | PIL 이미지 또는 ndarray 형식을 Tensor로 변환 |
형식: (H x W x C) -> (C x H x W) | |||
범위: 변환 없음 | |||
PIL 변환 | transforms.ToPILImage( | Tensor | Tensor를 PIL 이미지로 변환 ndarray 형식 변환 |
mode=str or number type) | 형식: (C x H x W) -> (H x W x C) | ||
모드(mode): 4채널(RGBA), 3채널(RGB), 2채널(LA), 1채널(int, float, short) | |||
이미지 형식 | transforms.ConvertImageDtype | 텐서 이미지 형식을 특정 dtype으로 변환 | |
변환 | dtype=torch.dtype) | ||
회전 | transforms.RandomRotation( | 각도(degrees): 동적 각도로 회전 | |
degrees=number or sequence, | 보간(interpolation): 이미지 보간 방법을 설정 | ||
interpolation=InterpolationMode, | 확장(expand): 범위 확장 여부 | ||
expand=bool, | 중심점(center): 이미지 회전 중심점 설정 | ||
center=sequence, | 채우기(fill): 범위의 빈 공간 | ||
fill=number or sequence) | |||
수평 대칭 | transforms.RandomHorizontalFlip( | 수평 뒤집기: 확률에 따라 대칭 사용 | |
p=float) | 0.0 = 대칭하지 않음 | ||
1.0 = 항상 대칭 | |||
수직 대칭 | transforms.RandomVerticalFlip( | 수직 뒤집기: 확률에 따라 대칭 사용 | |
p=float) | 0.0 = 대칭하지 않음 | ||
1.0 = 항상 대칭 | |||
무작위 자르기 | transforms.RandomCrop( | 크기(size): 동적 크기로 자르기(최소 크기) | |
size=number or sequence, | 패딩(padding): 여백 공간 | ||
padding=int or sequence, | 가능한 패딩(pad_if_needed): 이미지 크기가 자르는 크기보다 작은 경우 자동 패딩 | ||
pad_if_needed=bool, | 채우기(fill): 패딩 색상 | ||
fill=number or tuple, | 패딩 방식(padding_mode): 패딩 방식 설정 | ||
padding_mode=str) | 상수(constant), 테두리(edge), 반사(reflect), 대칭(symmetric) | ||
무작위 자르기 | transforms.RandomResizedCrop( | 크기(size): 동적 크기로 자르기(최소 크기) | |
및 크기 변경 | size=number or sequence, | 스케일(scale): 이미지 크기의 상한 및 하한 | |
scale=tuple of float, | 비율(ratio): 이미지 종횡비의 상한 및 하한 | ||
ratio=tuple of float, | 보간(interpolation): 이미지 보간 방법 | ||
interpolation=InterpolationMode) | 옵션(NEAREST, 이중 선형(BILINEAR), 바이큐빅(BICUBIC), 박스(BOX), 해밍(HAMMING), 란초스(LANCZOS)) | ||
중앙 자르기 | transforms.CenterCrop( | 크기(size): 동적 크기로 자르기(최소 크기) | |
size=number or sequence) | |||
패딩 | transforms.Pad( | 패딩(padding): 여백 공간 | |
padding=number or sequence, | 채우기(fill): 패딩 색상 | ||
fill=number or tuple, | 패딩 방식(padding_mode): 패딩 방식 설정 | ||
padding_mode=str) | 상수(constant), 테두리(edge), 반사(reflect), 대칭(symmetric) | ||
크기 조정 | transforms.Resize( | 크기(size): 동적 크기로 조정(최소 크기) | |
size=int or sequence, | 보간(interpolation): 이미지 보간 방법 | ||
interpolation=InterpolationMode, | 최대 크기(max_size): 크기를 정수로 표시하는 경우 최대 크기 | ||
max_size=int, | 앤티알리아싱(antialias): 계단 현상 최소화 여부 | ||
antialias=bool) | |||
아핀 변환 | transforms.RandomAffine( | 각도(degrees): 동적 각도로 회전 | |
degrees=number or sequence, | 이동(translate): 수평 및 수직 이동 | ||
translate=tuple, | 스케일(scale): 이미지 크기의 상한 및 하한 | ||
scale=tuple, | 진자(shear): 진자 값 | ||
shear=number or sequence, | X 축: -shear, shear 또는 shear[0], shear[1] | ||
interpolation=InterpolationMode, | Y 축: -shear, shear 또는 shear[2], shear[3] | ||
fill=number or sequence, | 보간(interpolation): 이미지 보간 방법 | ||
center=sequence) | 옵션(NEAREST, 이중 선형(BILINEAR), 바이큐빅(BICUBIC), 박스(BOX), 해밍(HAMMING), 란초스(LANCZOS)) | ||
원근 변환 | transforms.RandomPerspective( | 왜곡(distortion_scale): 원근 변화의 최대 측도 | |
distortion_scale=float, | 확률(p): 변환 확률 | ||
p=float, | 보간(interpolation): 이미지 보간 방법 | ||
interpolation=InterpolationMode, | 옵션(NEAREST, 이중 선형(BILINEAR), 바이큐빅(BICUBIC), 박스(BOX), 해밍(HAMMING), 란초스(LANCZOS)) | ||
fill=number or sequence) | 채우기(fill): 원근 변화의 빈 공간 | ||
색상 변환 | transforms.ColorJitter( | 밝기(brightness): 밝기 변화 범위 (min, max) | |
brightness=float or tuple of float, | 대비(contrast): 대비 변화 |
댓글남기기