데이터 증강
데이터 증강(Data Augmentation)은 학습 데이터의 다양성을 인위적으로 높여 모델의 일반화 성능을 향상시키는 기법입니다. 이 문서에서는 Albumentations 라이브러리를 중심으로 태스크별 증강 전략을 실습합니다.
환경 준비
pip install albumentations opencv-python-headless
왜 증강이 필요한가
학습 데이터가 부족하거나 편향되면 모델은 학습 데이터에 과적합(Overfitting)됩니다. 증강은 기존 이미지를 변형하여 모델이 다양한 상황에 대응하도록 만듭니다.
| 상황 | 증강 효과 |
|---|
| 데이터가 적을 때 | 데이터 다양성 확보 |
| 촬영 조건이 일정할 때 | 조명/각도 변화 시뮬레이션 |
| 클래스 불균형 | 소수 클래스 증강으로 균형 맞춤 |
| 실환경과 학습환경 차이 | 도메인 갭(Domain Gap) 감소 |
Albumentations 기본 사용법
import albumentations as A
import cv2
import numpy as np
# 증강 파이프라인 정의
transform = A.Compose([
A.HorizontalFlip(p=0.5), # 좌우 반전
A.RandomBrightnessContrast(p=0.3), # 밝기/대비 조절
A.Resize(224, 224), # 리사이즈
])
# 이미지에 증강 적용
image = cv2.imread('image.jpg')
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
augmented = transform(image=image)
result = augmented['image']
import matplotlib.pyplot as plt
def visualize_augmentation(image, transform, n=6):
"""증강 결과를 시각화합니다."""
fig, axes = plt.subplots(2, 3, figsize=(12, 8))
axes = axes.flatten()
for i, ax in enumerate(axes):
if i == 0:
ax.imshow(image)
ax.set_title('원본')
else:
augmented = transform(image=image)['image']
ax.imshow(augmented)
ax.set_title(f'증강 {i}')
ax.axis('off')
plt.tight_layout()
plt.show()
visualize_augmentation(image, transform)
주요 증강 기법
기하학적 변환 (Geometric)
geometric_transform = A.Compose([
A.HorizontalFlip(p=0.5), # 좌우 반전
A.VerticalFlip(p=0.2), # 상하 반전 (위성/의료 이미지)
A.Rotate(limit=30, p=0.5), # 회전 (-30도 ~ +30도)
A.ShiftScaleRotate(
shift_limit=0.1, # 이동
scale_limit=0.2, # 스케일
rotate_limit=15, # 회전
p=0.5
),
A.Affine(shear=(-10, 10), p=0.3), # 전단 변환
])
색상 변환 (Color)
color_transform = A.Compose([
A.RandomBrightnessContrast(
brightness_limit=0.2,
contrast_limit=0.2,
p=0.5
),
A.HueSaturationValue(
hue_shift_limit=20,
sat_shift_limit=30,
val_shift_limit=20,
p=0.3
),
A.ColorJitter(p=0.3),
A.CLAHE(clip_limit=4.0, p=0.3), # 대비 제한 적응 히스토그램 균등화
A.ToGray(p=0.1), # 그레이스케일 변환
])
노이즈와 블러 (Noise & Blur)
noise_transform = A.Compose([
A.GaussNoise(var_limit=(10, 50), p=0.3), # 가우시안 노이즈
A.GaussianBlur(blur_limit=7, p=0.3), # 가우시안 블러
A.MotionBlur(blur_limit=7, p=0.2), # 모션 블러
A.ImageCompression(
quality_lower=60, quality_upper=100, # JPEG 압축 아티팩트
p=0.3
),
])
드롭아웃 (Dropout)
dropout_transform = A.Compose([
A.CoarseDropout(
max_holes=8, # 구멍 개수
max_height=32, # 구멍 높이
max_width=32, # 구멍 너비
fill_value=0, # 채우기 값
p=0.5
),
A.RandomShadow(p=0.3), # 그림자 효과
])
태스크별 증강 전략
분류용 증강
분류에서는 이미지 전체에 적용되는 증강을 자유롭게 사용합니다.
classification_transform = A.Compose([
A.RandomResizedCrop(
size=(224, 224),
scale=(0.8, 1.0),
ratio=(0.9, 1.1),
),
A.HorizontalFlip(p=0.5),
A.ColorJitter(
brightness=0.2, contrast=0.2,
saturation=0.2, hue=0.1,
p=0.5
),
A.GaussNoise(var_limit=(10, 30), p=0.2),
A.CoarseDropout(max_holes=4, max_height=40, max_width=40, p=0.3),
A.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225],
),
])
# 적용
augmented = classification_transform(image=image)
result = augmented['image']
탐지용 증강
탐지에서는 바운딩 박스도 함께 변환해야 합니다. bbox_params를 설정합니다.
detection_transform = A.Compose([
A.HorizontalFlip(p=0.5),
A.RandomBrightnessContrast(p=0.3),
A.HueSaturationValue(p=0.3),
A.Resize(640, 640),
A.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225],
),
], bbox_params=A.BboxParams(
format='yolo', # 'yolo', 'coco', 'pascal_voc' 중 선택
label_fields=['labels'], # 클래스 레이블 필드명
min_visibility=0.3, # 최소 가시성 (잘린 박스 필터링)
))
# 적용 (바운딩 박스 포함)
augmented = detection_transform(
image=image,
bboxes=[[0.5, 0.5, 0.3, 0.4]], # YOLO 형식: [cx, cy, w, h]
labels=[0],
)
result_image = augmented['image']
result_bboxes = augmented['bboxes']
탐지 증강에서 기하학적 변환(회전, 크롭)을 적용하면 바운딩 박스가 이미지 밖으로 나갈 수 있습니다. min_visibility 파라미터로 너무 많이 잘린 박스를 자동 제거할 수 있습니다.
세그멘테이션용 증강
세그멘테이션에서는 마스크도 이미지와 동일하게 변환해야 합니다.
segmentation_transform = A.Compose([
A.HorizontalFlip(p=0.5),
A.VerticalFlip(p=0.2),
A.RandomBrightnessContrast(p=0.3),
A.Rotate(limit=15, p=0.3),
A.Resize(512, 512),
A.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225],
),
])
# 적용 (마스크 포함)
augmented = segmentation_transform(image=image, mask=mask)
result_image = augmented['image']
result_mask = augmented['mask'] # 마스크도 동일 변환 적용됨
Albumentations는 마스크에 대해 색상 변환(밝기, 대비 등)을 적용하지 않고, 기하학적 변환(회전, 반전 등)만 동일하게 적용합니다. 별도의 처리 없이 mask 파라미터에 전달하기만 하면 됩니다.
PyTorch Dataset 연동
import torch
from torch.utils.data import Dataset
from pathlib import Path
class CustomDataset(Dataset):
"""Albumentations 증강을 적용하는 커스텀 데이터셋입니다."""
def __init__(self, image_dir, transform=None):
self.image_paths = sorted(Path(image_dir).glob('*.jpg'))
self.transform = transform
def __len__(self):
return len(self.image_paths)
def __getitem__(self, idx):
# 이미지 로드
image = cv2.imread(str(self.image_paths[idx]))
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# 증강 적용
if self.transform:
augmented = self.transform(image=image)
image = augmented['image']
# numpy → tensor (HWC → CHW)
image = torch.from_numpy(image).permute(2, 0, 1).float()
return image
# 사용
dataset = CustomDataset('train_images/', transform=classification_transform)
증강 모범 사례
| 원칙 | 설명 |
|---|
| 검증 세트에는 증강 미적용 | Resize + Normalize만 적용 |
| 도메인에 맞는 증강 선택 | 위성 이미지에는 VerticalFlip 적용, 얼굴에는 미적용 |
| 과도한 증강 주의 | 너무 강한 증강은 오히려 학습을 방해 |
| 증강 확률 조절 | 0.3~0.5 범위가 일반적 |
| A/B 테스트 | 증강 유무에 따른 성능 비교 필수 |
torchvision.transforms 대신 Albumentations를 쓰는 이유는?
Albumentations는 바운딩 박스와 마스크를 함께 변환하는 기능이 내장되어 있어 탐지와 세그멘테이션에 적합합니다. 또한 내부적으로 NumPy/OpenCV를 사용하여 속도가 더 빠릅니다.
Mosaic은 4개의 이미지를 하나로 합치는 기법으로, YOLO에서 기본 사용됩니다. Albumentations에서는 직접 구현해야 하지만, ultralytics에서는 mosaic=1.0 옵션으로 자동 적용됩니다.