데이스쿨 진도를 나가며 코드 작성 연습을 하다보니
이론으로 배울때와 다르게 또 새로운 느낌이 들었다...
오늘은 그 중 이미지 증강 방법에 대한 정리를 해보고자 한다.
이미지 증강은 원본 이미지를 직접 늘리는 것이 아니라,
원본을 다양한 방식으로 변형해서 학습 데이터의 다양성을 높이는 방법이다.
예를 들어 같은 강아지 사진 한 장이 있다고 하자.
이런 이미지들은 모두 원본과 완전히 다른 데이터는 아니지만,
모델 입장에서는 다양한 조건을 경험하게 해준다.
즉, 이미지 증강의 핵심 목적은 다음과 같다.
하지만 명심해야 할 것이 있다.
“변환 후에도 정답 의미(label semantics)가 유지되어야 한다”는 것
그래서 증강을 고를 때는 항상 이 질문을 해야 한다:
“이 변환을 적용한 뒤에도 정답이 그대로인가?”
"실제 환경에서 충분히 일어날 법한 변화인가?"
모델은 생각보다 쉽게 데이터의 특정 패턴에 과하게 의존한다.
예를 들어,
이미지 증강은 이런 문제를 줄여준다.
즉, 모델이 “이 모양, 이 위치, 이 밝기에서만 정답” 이라고 외우는 것이 아니라,
조금 달라진 입력에서도 같은 대상을 인식하도록 훈련시키는 것이다.
이미지 증강을 할 때 대표적으로 많이 쓰는 라이브러리가 torchvision과 albumentations다.
torchvision은 PyTorch에서 기본적으로 많이 사용하는 이미지 처리 라이브러리다.
특징은 다음과 같다.
예를 들어 transform= 인자에 바로 넣어서 사용할 수 있다.
import torchvision.transforms as transforms
from torchvision.datasets import CIFAR10
transform = transforms.Compose([
transforms.RandomHorizontalFlip(),
transforms.RandomRotation(10),
transforms.ToTensor()
])
train_dataset = CIFAR10(
root='./data',
train=True,
transform=transform,
download=True
)
albumentations는 이미지 증강에 특화된 고성능 라이브러리다.
특징은 다음과 같다.
다양한 증강 기법을 제공한다
속도와 유연성이 좋다
컴퓨터 비전 실전 프로젝트에서 자주 사용된다
특히 복잡한 증강 파이프라인을 설계하기 좋다
예를 들어 다음과 같이 쓸 수 있다.
import albumentations as A
from albumentations.pytorch import ToTensorV2
transform = A.Compose([
A.HorizontalFlip(p=0.5),
A.Rotate(limit=10, p=0.5),
A.Normalize(mean=(0.5,), std=(0.5,)),
ToTensorV2()
])
torchvision: PyTorch와 자연스럽게 연결되는 기본형albumentations: 더 다양하고 강력한 실전형즉, 간단한 실습이나 입문 단계에서는 torchvision이 편하고,
복잡한 이미지 증강 전략을 설계할 때는 albumentations가 더 강력하다.
이미지 증강을 공부할 때 많은 사람이 헷갈리는 부분이 바로 자료형의 흐름이다.
이미지를 열 때 보통 PIL.Image.open()을 많이 사용한다.
from PIL import Image
image = Image.open("dog.png")
이 단계에서의 이미지는 PIL 객체다.
즉, “이미지 파일을 다루기 좋은 형태”라고 보면 된다.
이미지를 수치적으로 다루려면 NumPy 배열로 변환해야한다.
import numpy as np
image_np = np.array(image)
이제 이미지는 픽셀 값을 가진 배열이 된다.
즉, 이미지가 단순한 그림이 아니라 숫자 행렬이 되는 것이다.
이 변환이 중요한 이유는 다음과 같다.
특히 albumentations는 주로 NumPy 배열 형태의 이미지를 다룬다.
모델에 넣기 위해서는 결국 텐서로 바꿔야 한다.
# 방법 1
transforms.ToTensor()
# 방법 2
ToTensorV2()
이 과정을 거치면 이미지가 PyTorch 텐서가 된다.
여기서 중요한 점은 차원 순서다.
즉, 높이-너비-채널 순서였던 이미지가,
PyTorch에서는 채널-높이-너비 순서로 바뀐다.
Matplotlib은 보통 [H, W, C] 형식을 기대한다.
반면 PyTorch 텐서는 [C, H, W]다.
그래서 텐서를 다시 이미지로 보고 싶다면 차원을 바꿔야 한다.
# 방법 1
img = img_tensor.numpy().transpose(1, 2, 0)
# 방법 2
img = img_tensor.permute(1, 2, 0).cpu().numpy()
이 부분은 “라이브러리마다 기대하는 이미지 형식이 다르다” 는 걸 이해해야 가능하다.
import torchvision.transforms as transforms
transform = transforms.Compose([
transforms.Resize((224, 224)), # 이미지를 224 x 224 크기로 조정
transforms.ToTensor() # 텐서로 변환
])
import matplotlib.pyplot as plt
from PIL import Image
image = Image.open("dog.png")
image_tensor = transform(image)
def imshow(img_tensor):
img = img_tensor.numpy().transpose(1, 2, 0) # 채널이 맨 뒤에 가게!
plt.imshow(img)
plt.axis("off")
plt.show()
imshow(image_tensor)
albumentations는 보통 입력을 NumPy 배열로 받고, 결과를 딕셔너리 형태로 반환한다.
import albumentations as A
from albumentations.pytorch import ToTensorV2
from PIL import Image
import numpy as np
image = Image.open("dog.png").convert("RGB")
image_np = np.array(image) # albumentations는 입력 형태가 np 배열이기 때문!
transform = A.Compose([
A.Resize(224, 224),
ToTensorV2()
])
transformed = transform(image=image_np)
image_tensor = transformed["image"] # 반환값이 딕셔너리라 이렇게 꺼내야 함.
def imshow(img_tensor):
img = img_tensor.permute(1, 2, 0).cpu().numpy() # 차원 맨 마지막으로!
plt.imshow(img)
plt.axis("off")
plt.show()
imshow(image_tensor)
torchvision은 transform= 인자에 바로 연결하면 되지만,
albumentations는 보통 커스텀 Dataset을 통해 적용하는 경우가 많다.
from torch.utils.data import Dataset
from torchvision.datasets import CIFAR10
import numpy as np
class AlbumentationsDataset(Dataset):
def __init__(self, dataset, transform=None):
self.dataset = dataset
self.transform = transform
def __len__(self):
return len(self.dataset)
def __getitem__(self, idx):
image, label = self.dataset[idx]
if self.transform:
image = self.transform(image=np.array(image))["image"]
return image, label
train_dataset = CIFAR10(root="./data", train=True, download=True)
train_dataset = AlbumentationsDataset(train_dataset, transform=transform)
이 구조가 필요한 이유는 albumentations가 PIL 이미지가 아니라 NumPy 이미지 기반으로 동작하는 경우가 많기 때문이다.
즉, 입력 형식이 서로 다르기 때문에 생기는 연결 작업이다.
A.HorizontalFlip(p=0.5)A.VerticalFlip(p=0.5)A.RandomCrop(height=200, width=200, p=1.0)A.ColorJitter(
brightness=0.2, # 밝기 조절
contrast=0.2, # 대비 조절
saturation=0.2, # 채도 조절
hue=0.1, # 색조 조절
p=0.5
)
A.GaussNoise(var_limit=(300.0, 400.0), p=1.0)
각 픽셀에 무작위 값을 더하는 방식
수식으로 보면 대략 이런 느낌이다.
의미
같은 상황을 흉내 낸다.
파라미터
A.OneOf([
A.GlassBlur(p=1.0),
A.MotionBlur(p=1.0), # 움직이며 찍힌 듯한 블러
A.MedianBlur(p=1.0), # 노이즈 제거 성격이 있는 블러
A.GaussianBlur(p=1.0) # 부드럽게 퍼지는 전형적인 블러
], p=0.5)
CLAHE는 Contrast Limited Adaptive Histogram Equalization의 약자
핵심은 아래와 같다.
A.CLAHE(
clip_limit=2.0,
tile_grid_size=(8, 8),
p=1.0
)
일반 히스토그램 평활화와 차이
그래서 더 세밀한 디테일을 살리는 데 유리하다.
즉, CLAHE는 단순 증강이라기보다
보정형 전처리와 증강의 중간 성격을 가진 기법이라고 보는 것이 더 정확하다.
이미지 일부를 네모난 구멍처럼 가려버리는 증강
A.CoarseDropout(
max_holes=20,
max_height=8,
max_width=8,
min_holes=2,
min_height=4,
min_width=4,
fill_value=0,
p=1.0
)
모델은 종종 이미지의 아주 특정한 부분에만 과하게 의존한다.
예를 들어,
이럴 때 CoarseDropout을 쓰면
모델이 일부 정보가 가려져도 나머지 문맥을 보고 판단하도록 유도할 수 있다.
의미
즉, CoarseDropout은
“모델의 시선을 한 군데에 고정시키지 않는 증강” 이라고 이해하면 좋다.
Compose는 여러 변환을 순서대로 적용한다.
transform = A.Compose([
A.HorizontalFlip(p=0.5),
A.VerticalFlip(p=0.5),
A.RandomCrop(height=200, width=200, p=1.0),
A.ShiftScaleRotate(shift_limit=0.0625, scale_limit=0.1, rotate_limit=45, p=0.5),
A.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1, p=0.5),
A.GaussNoise(var_limit=(300.0, 400.0), p=1.0)
])
OneOf는 여러 변환 중 하나만 선택해서 적용한다.
transform = A.Compose([
A.GaussNoise(var_limit=(1000.0, 1100.0), p=0.5),
A.OneOf([
A.GlassBlur(p=1.0),
A.MotionBlur(p=1.0),
A.MedianBlur(p=1.0),
A.GaussianBlur(p=1.0)
], p=0.5),
A.CLAHE(clip_limit=2.0, tile_grid_size=(8, 8), p=1.0),
A.CoarseDropout(
max_holes=20,
max_height=8,
max_width=8,
min_holes=2,
min_height=4,
min_width=4,
fill_value=0,
p=1.0
)
])
에 강한 모델 만들기
에 강한 모델 만들기
에 강한 모델 만들기
에 강한 모델 만들기
이미지 증강은 무조건 많이 넣는다고 좋은 것이 아니다.
오히려 데이터의 의미를 깨뜨리면 성능이 떨어질 수 있다.
예를 들어 사람 얼굴 인식에서 VerticalFlip은 부자연스럽다.
문자 인식에서 좌우 반전도 위험할 수 있다.
즉, 현실에서 발생 가능한 변형인지 먼저 생각해야 한다.
즉, 증강의 목표는 정보를 없애는 것이 아니라
적절히 흔들어서 일반화를 돕는 것이다.
분류, 탐지, 세그멘테이션, 의료 영상 분석은
필요한 증강이 서로 다를 수 있다.
같은 이미지 증강이라도
어떤 태스크에 적용하느냐에 따라 적절성이 달라진다.