[학습 일기 #38] 이미지 증강(Image Augmentation)

Ariel_Jeong·2026년 3월 9일

[학습 일기 시리즈]

목록 보기
39/44

데이스쿨 진도를 나가며 코드 작성 연습을 하다보니
이론으로 배울때와 다르게 또 새로운 느낌이 들었다...

오늘은 그 중 이미지 증강 방법에 대한 정리를 해보고자 한다.




1. 이미지 증강이란?

이미지 증강은 원본 이미지를 직접 늘리는 것이 아니라,
원본을 다양한 방식으로 변형해서 학습 데이터의 다양성을 높이는 방법이다.

예를 들어 같은 강아지 사진 한 장이 있다고 하자.

  • 좌우를 뒤집은 이미지
  • 일부를 잘라낸 이미지
  • 밝기가 더 어두운 이미지
  • 회전된 이미지
  • 노이즈가 섞인 이미지

이런 이미지들은 모두 원본과 완전히 다른 데이터는 아니지만,
모델 입장에서는 다양한 조건을 경험하게 해준다.

즉, 이미지 증강의 핵심 목적은 다음과 같다.

  • 데이터 다양성 증가
  • 과적합 완화
  • 일반화 성능 향상
  • 실제 환경의 다양한 촬영 조건에 대한 강건성 확보

하지만 명심해야 할 것이 있다.

“변환 후에도 정답 의미(label semantics)가 유지되어야 한다”는 것

그래서 증강을 고를 때는 항상 이 질문을 해야 한다:

“이 변환을 적용한 뒤에도 정답이 그대로인가?”
"실제 환경에서 충분히 일어날 법한 변화인가?"


1.2. 왜 이미지 증강이 필요한가?

모델은 생각보다 쉽게 데이터의 특정 패턴에 과하게 의존한다.

예를 들어,

  • 고양이 사진이 항상 정면만 있다면 → 옆모습에서 약해질 수 있고
  • 의료 영상이 항상 일정한 대비를 가진다면 → 다른 장비로 촬영된 영상에 약해질 수 있고
  • 객체가 항상 중앙에만 있다면 → 위치가 조금만 바뀌어도 성능이 흔들릴 수 있다

이미지 증강은 이런 문제를 줄여준다.

즉, 모델이 “이 모양, 이 위치, 이 밝기에서만 정답” 이라고 외우는 것이 아니라,
조금 달라진 입력에서도 같은 대상을 인식하도록 훈련시키는 것이다.




2. torchvision과 albumentations 비교

이미지 증강을 할 때 대표적으로 많이 쓰는 라이브러리가 torchvisionalbumentations다.

2.1. torchvision

torchvision은 PyTorch에서 기본적으로 많이 사용하는 이미지 처리 라이브러리다.

특징은 다음과 같다.

  • PyTorch와의 호환성이 매우 좋다
  • 사용법이 직관적이다
  • Dataset과 DataLoader 파이프라인에 자연스럽게 연결된다
  • 기본적인 증강을 구현하기 좋다

예를 들어 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
)

장점

  • 코드가 간단하다
  • PyTorch 생태계와 연결이 쉽다
  • 입문자가 이해하기 좋다

단점

  • 복잡하고 다양한 증강 조합에서는 상대적으로 제한적일 수 있다

2.2. albumentations

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()
])

장점

  • 증강 종류가 매우 다양하다
  • 실전적인 파이프라인 구성에 강하다
  • OneOf, CoarseDropout, CLAHE, GlassBlur 같은 고급 증강이 편리하다

단점

  • 입력 형식과 출력 형식을 이해해야 한다
  • PyTorch에 붙일 때 커스텀 Dataset이 필요한 경우가 많다

2.3. 둘의 차이를 한 줄로 정리하면

  • torchvision: PyTorch와 자연스럽게 연결되는 기본형
  • albumentations: 더 다양하고 강력한 실전형

즉, 간단한 실습이나 입문 단계에서는 torchvision이 편하고,
복잡한 이미지 증강 전략을 설계할 때는 albumentations가 더 강력하다.




3. PIL → NumPy → Tensor 흐름 이해하기

이미지 증강을 공부할 때 많은 사람이 헷갈리는 부분이 바로 자료형의 흐름이다.

3.1. PIL 이미지

이미지를 열 때 보통 PIL.Image.open()을 많이 사용한다.

from PIL import Image

image = Image.open("dog.png")

이 단계에서의 이미지는 PIL 객체다.
즉, “이미지 파일을 다루기 좋은 형태”라고 보면 된다.


3.2. NumPy 배열

이미지를 수치적으로 다루려면 NumPy 배열로 변환해야한다.

import numpy as np

image_np = np.array(image)

이제 이미지는 픽셀 값을 가진 배열이 된다.
즉, 이미지가 단순한 그림이 아니라 숫자 행렬이 되는 것이다.

이 변환이 중요한 이유는 다음과 같다.

  • 픽셀 단위 계산 가능
  • 증강 연산 수행 가능
  • 다양한 전처리 적용 가능

특히 albumentations는 주로 NumPy 배열 형태의 이미지를 다룬다.


3.3. PyTorch Tensor

모델에 넣기 위해서는 결국 텐서로 바꿔야 한다.

# 방법 1
transforms.ToTensor()

# 방법 2
ToTensorV2()

이 과정을 거치면 이미지가 PyTorch 텐서가 된다.

여기서 중요한 점은 차원 순서다.

  • 일반 이미지/NumPy: [H, W, C]
  • PyTorch Tensor: [C, H, W]

즉, 높이-너비-채널 순서였던 이미지가,
PyTorch에서는 채널-높이-너비 순서로 바뀐다.


3.4. 텐서를 이미지로 보고싶다면 차원을 바꿔야 한다.

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()

이 부분은 “라이브러리마다 기대하는 이미지 형식이 다르다” 는 걸 이해해야 가능하다.



4. torchvision으로 기본 이미지 변환

4.1. 기본 파이프라인

코드 예시

import torchvision.transforms as transforms

transform = transforms.Compose([
    transforms.Resize((224, 224)),    # 이미지를 224 x 224 크기로 조정
    transforms.ToTensor()             # 텐서로 변환
])

4.2. 시각화 예시

코드 예시

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)




5. albumentations로 이미지 변환

albumentations는 보통 입력을 NumPy 배열로 받고, 결과를 딕셔너리 형태로 반환한다.

5.1. 기본 파이프라인

코드 예시

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"]    # 반환값이 딕셔너리라 이렇게 꺼내야 함.

5.2. 시각화 예시

코드 예시

def imshow(img_tensor):
    img = img_tensor.permute(1, 2, 0).cpu().numpy()    # 차원 맨 마지막으로!
    plt.imshow(img)
    plt.axis("off")
    plt.show()

imshow(image_tensor)

5.3. 커스텀 dataset 만들기

torchvisiontransform= 인자에 바로 연결하면 되지만,
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 이미지 기반으로 동작하는 경우가 많기 때문이다.

즉, 입력 형식이 서로 다르기 때문에 생기는 연결 작업이다.




6. 주요 이미지 증강 기법 정리

6.1. Flip: 뒤집기

  • HorizontalFlip
    • 좌우를 뒤집는다.
    • A.HorizontalFlip(p=0.5)
  • VerticalFlip
    • 상하를 뒤집는다.
    • A.VerticalFlip(p=0.5)

6.2. Crop: 잘라내기

  • RandomCrop
    • 무작위 위치를 지정 크기로 잘라낸다.
    • A.RandomCrop(height=200, width=200, p=1.0)
  • CenterCrop
    • 중앙 부분을 잘라낸다.

## 6.3. Shift / Scale / Rotate 이미지 위치, 크기, 방향을 바꾸는 기하학적 증강 ``` A.ShiftScaleRotate( shift_limit=0.1, # 원본 크기의 최대 10% 범위 내에서 이동시킬 수 있다는 뜻 scale_limit=0.1, # 원본의 90%~110% 범위에서 크기 변화가 일어날 수 있음을 의미 rotate_limit=45, # -45도~+45도 범위에서 회전할 수 있다는 뜻 p=0.5 ) ```

6.4. ColorJitter: 밝기, 대비, 채도, 색상 변화

A.ColorJitter(
    brightness=0.2,    # 밝기 조절
    contrast=0.2,      # 대비 조절
    saturation=0.2,    # 채도 조절
    hue=0.1,           # 색조 조절
    p=0.5
)

6.5. GaussNoise: 가우시안 노이즈 추가

A.GaussNoise(var_limit=(300.0, 400.0), p=1.0)

각 픽셀에 무작위 값을 더하는 방식

수식으로 보면 대략 이런 느낌이다.

  • 원본 픽셀값: I(i, j)
  • 노이즈: N ~ N(0, σ²)
  • 변형 픽셀값: I'(i, j) = I(i, j) + N(i, j)

의미

  • 센서 노이즈
  • 촬영 환경 잡음
  • 현실 이미지의 불완전성

같은 상황을 흉내 낸다.

파라미터

  • var_limit: 노이즈 세기 범위
  • p: 적용 확률

6.6. Blur 계열: 흐림에 대한 강건성 학습

  • GlassBlur(자주 쓰임)
    • 이미지를 마치 유리 너머로 보는 것처럼 흐리게 만든다.
    • 특징
      • 단순한 흐림보다 불규칙한 왜곡 느낌이 있다
      • 세부 질감과 경계가 흐려진다
    • 의미
      • 모델이 선명한 경계에만 의존하지 않게 함
      • 품질 저하 상황에 대한 일반화 성능 향상
  • 함께 자주 묶이는 블러들
 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)

6.7. CLAHE: 국소 대비 향상

CLAHE는 Contrast Limited Adaptive Histogram Equalization의 약자

핵심은 아래와 같다.

  • 이미지를 작은 구역으로 나눈다
  • 각 구역의 대비를 개선한다
  • 과도한 대비 증폭은 제한한다
A.CLAHE(
    clip_limit=2.0,
    tile_grid_size=(8, 8),
    p=1.0
)

일반 히스토그램 평활화와 차이

  • 일반 히스토그램 평활화는 전체 이미지에 한 번에 적용된다.
  • 반면 CLAHE는 작은 구역 단위로 대비를 조절한다.

그래서 더 세밀한 디테일을 살리는 데 유리하다.

어디에 유용한가?

  • 저조도 영상
  • 의료 영상
  • 세부 구조가 중요한 이미지
  • 대비가 낮아 정보가 묻히는 이미지

즉, CLAHE는 단순 증강이라기보다
보정형 전처리와 증강의 중간 성격을 가진 기법이라고 보는 것이 더 정확하다.


6.8. CoarseDropout: 일부 영역 가리기

이미지 일부를 네모난 구멍처럼 가려버리는 증강

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을 쓰면
모델이 일부 정보가 가려져도 나머지 문맥을 보고 판단하도록 유도할 수 있다.

의미

  • occlusion robustness 향상
  • 특정 위치 과의존 완화
  • 일반화 성능 향상

즉, CoarseDropout은
“모델의 시선을 한 군데에 고정시키지 않는 증강” 이라고 이해하면 좋다.




7. Compose와 OneOf: 증강 파이프라인 설계하기

7.1. Compose

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)
])

7.2. OneOf

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
    )
])



8. 증강 기법을 목적별로 분류해보기

8.1. 기하학적 증강

  • Flip
  • Crop
  • Shift / Scale / Rotate

목적

  • 위치 변화
  • 크기 변화
  • 방향 변화

에 강한 모델 만들기

8.2. 색/조명 기반 증강

  • ColorJitter
  • CLAHE

목적

  • 조명 변화
  • 대비 변화
  • 색감 변화

에 강한 모델 만들기

8.3. 품질 저하 시뮬레이션

  • GaussNoise
  • GlassBlur
  • MotionBlur
  • MedianBlur
  • GaussianBlur

목적

  • 노이즈
  • 흐림
  • 촬영 품질 저하

에 강한 모델 만들기

8.4. 부분 정보 손실 시뮬레이션

  • CoarseDropout

목적

  • 가려짐
  • 일부 정보 누락
  • 특정 위치 과의존 방지

에 강한 모델 만들기




9. 증강을 사용할 때 주의할 점

이미지 증강은 무조건 많이 넣는다고 좋은 것이 아니다.
오히려 데이터의 의미를 깨뜨리면 성능이 떨어질 수 있다.

9.1. 데이터의 현실성과 맞아야 한다

예를 들어 사람 얼굴 인식에서 VerticalFlip은 부자연스럽다.
문자 인식에서 좌우 반전도 위험할 수 있다.

즉, 현실에서 발생 가능한 변형인지 먼저 생각해야 한다.

9.2. 중요한 정보를 너무 많이 파괴하면 안 된다

  • Crop이 너무 크면 핵심 객체가 잘릴 수 있고
  • Blur가 너무 강하면 구분 자체가 어려워질 수 있고
  • CoarseDropout이 너무 과하면 학습이 불안정할 수 있다

즉, 증강의 목표는 정보를 없애는 것이 아니라
적절히 흔들어서 일반화를 돕는 것이다.

9.3. 태스크에 맞는 증강을 골라야 한다

분류, 탐지, 세그멘테이션, 의료 영상 분석은
필요한 증강이 서로 다를 수 있다.

같은 이미지 증강이라도
어떤 태스크에 적용하느냐에 따라 적절성이 달라진다.

profile
R&D 분야의 경험을 토대로 커리어 확장에 도전중인 개발꿈나무입니다.

0개의 댓글