목차

  1. Data Augmentation 개요
  2. Augmentation 유형
    1. 공간 레벨
    2. 픽셀 레벨
  3. TF.Keras 지원 Augmentation
  4. ImageDataGenerator 특징
  5. Keras의 Preprocessing과 Data Loading 메커니즘
  6. ImageDataGenerator를 이용한 Augmentation
  7. ImagDataGenerator를 이용하여 Augmentation 적용하기
    1. 좌우, 상하 반전(Horizontal Flip, Vertical Flip)
    2. 여러 이미지의 ImageDataGenerator 변환을 수행하는 함수 생성
    3. Affine Transformation
    4. 이미지 Rotation 
    5. 이미지 좌우, 상하 이동(Shift)
    6. Zoom(Scale) 
    7. Shear 
    8. Bright 
    9. Channel Shift
    10. Normalization
      1. featurewise 
      2. rescale 
    11. 싹다 적용하기

Data Augmentation 개요

CNN 모델의 성능을 높이고 오버피팅을 극복할 수 있는 가장 좋은 방법은 다양한 유형의 학습 이미지 데이터의 을 늘리는 것이다.

그러나 이미지 데이터의 경우 학습 데이터량을 늘리기가 쉽지 않다.

Augmentation은 학습 시에 원본 이미지에 다양한 변형을 가해서 학습 이미지 데이터를 늘리는 것과 유사한 효과를 발휘한다.

Augmentation은 원본 학습 이미지의 개수를 늘리는 것이 아니고, 학습 시마다 개별 원본 이미지를 변형해서 학습을 수행하는 것이다.


Augmentation 유형 - Spatial level, Pixel level

공간(Spatial)레벨 변형

  1. Flip: Vertical(up/down) 상하 변형, Horizontal(left/right) 좌우 변형 
  2. Crop(확대): Center, Random
  3. Affine: Rotate, Translate, Shear, Scale(Zoom)

픽셀(Pixel)레벨 변형

  1. Bright(밝기), Saturation(채도), Hue(색상), GrayScale, ColorJitter(임의로 섞어서 변환)
  2. Contrast
  3. Blur, Gaussian Blur, Median Blur
  4. Noise, Cutout
  5. Histogram
  6. Gamma
  7. RGBShift
  8. Sharpen

TF.Keras 지원 Augmentation

 

 

Keras ImageDataGenerator는 임의로 적용되기 때문에 각 기능별로 변환 확률을 정할 수 없다.

즉, 자기가 알아서 랜덤으로 조절한다.


ImageDataGenerator 특징

  • Keras 내에서 쉽게 Augmentation을 적용 가능
  • 지원되는 Augmentation 기능이 제한적이다. 하지만 기본적인 기능만으로도 학습 데이터 세트에 충분히 효율적인 Augmentation 적용이 가능하다.
  • 개별적으로 이미지 변환 적용을 하기가 어렵고, 변환 시간이 상대적으로 오래(?) 걸린다.
  • Keras는 (초기에) Data Augmenation등의 PreprocessingData Loading을 ImageDataGenerator 객체와 model의 fit()/fit_generator()를 밀접하게 연결하여 적용한다.
  • ImageDataGenerator는 파이프라인의 중간 역할을 한다.

Keras의 Preprocessing과 Data Loading 메커니즘


ImageDataGenerator를 이용한 Augmentation

ImageDataGenerator(
    featurewise_center=False, samplewise_center=False,
    featurewise_std_normalization=False, samplewise_std_normalization=False, zca_whitening=False, zca_epsilon=1e-06,
    rotation_range=0, # Rotation(회전)
    width_shift_range=0.0, height_shift_range=0.0, # Shift(Translate) brightness_range=None, # Bright(밝기)
    Shear_range=0.0, # Shear
    zoom_range=0.0, # Zoom(Scale)
    Channel_shift_range=0.0, #Channel Shift
    fill_mode='nearest', # 빈공간 채우는 방법
    cval=0.0,
    horizontal_flip=False, vertical_flip=False, # Flip(좌우,상하 반전)
    rescale=None,
    preprocessing_function=None, data_format=None, validation_split=0.0, dtype=None, )

 

  1. Flip
    1. 좌우 반전 : horizontal_flip=True
    2. 상하 반전 : vertical_flip=True
  2. Rotation
    1. rotation_range=45
    2. 임의의 -45 ~ +45 사이 회전
  3. Shift
    1. 좌우 이동 : width_shift_range=0.2
    2. 상하 이동 : height_shift_range=0.2
    3. 0~1 사이 값 설정하여 좌우, 상하를 이동
  4. Zoom
    1. zoom_range=[0.5, 1.5]
    2. 1보다 작은 값은 확장, 1보다 큰 값은 축소
  5. Shear
    1. shear_range=45
    2. x축 또는 y축을 중심으로, 0~45도 사이 변환
  6. Bright
    1. brightness_range = (0.1, 0.9)
    2. brightness_range로 밝기 조절
    3. 0~1 사이 값 입력
    4. 0에 가까울수록 어둡고, 1에 가까울수록 밝음
  7. Channel Shfit
    1. channel_shift_range=120
    2. R,G,B Pixel 값을 -120 ~ 120 사이의 임의의 값을 더하여 변환
  8. ZCA Whitening
    1. zca_whitening=True
    2. zca_epsilon=1e-6
    3. ZCA 변환으로 Whitening 효과
    4. keras 버그로 인해서 사용 안하는 것을 추천
  9. Normalization
    1. featurewise_center=True : 각 RGB Pixel 값에서 개별 채널 별 평균 Pixel 값을 빼서 평균이 0이 되게 유지
    2. featurewise_std_normalization=True : 각 RGB Pixel 값에서 개별 채널 별 표준편차 Pixel 값을 나눔
    3. rescale=1/255.0 : 딥러닝 입력은 비교적 작은 값을 선호하므로 0~255 사이의 Pixel값을 0~1 사이 값으로 변환

ImagDataGenerator를 이용하여 Augmentation 적용하기

  • ImageDataGenerator는 fit(), flow()를 통해서 입력된 image array(numpy)를 변환 동작시킬 수 있으며, 실제 변환은 next()등으로 iteration 호출해야 함
  • ImageDataGenerator가 입력으로 받는 image array는 batch를 포함한 4차원 array임. 즉 [579, 1028, 3] shape인 image array가 입력되면 이를 4차원 형태인 [1, 579, 1028, 3] 으로 변경해야 함.
  • 변경된 pixel값은 float이므로 이를 다시 int형으로 변경해서 시각화 필요.

좌우, 상하 반전(Horizontal Flip, Vertical Flip)

  • horizontal_flip=True로 좌우 반전 적용. 하지만 반드시 변환하는 것은 아니며 Random하게 원본 데이터를 유지하거나 변환 결정
  • vertical_flip=True로 상하 반전 적용. 마찬가지로 반드시 변환하는 것은 아니며 Random하게 원본 데이터를 유지하거나 변환 결정
from tensorflow.keras.preprocessing.image import ImageDataGenerator

# Horizontal Flip(좌우 반전)을 적용. horizontal_flip=True을 적용했지만 반드시 변환하는 것은 아님. Random하게 원본 데이터를 유지하거나 변환 결정. 
data_generator = ImageDataGenerator(horizontal_flip=True)

# ImageDataGenerator는 여러개의 image를 입력으로 받음. 
# 따라서 3차원이 아니라 batch를 포함한 4차원 array를 입력받음. 
# np.expand_dims()로 차원 증가.
image_batch = np.expand_dims(image, axis=0)
print('image_batch shape:', image_batch.shape)

# ImageDataGenerator 적용. 
data_generator.fit(image_batch)

# fit()후 flow()로 image batch를 넣어주어야 함. 
data_gen_iter = data_generator.flow(image_batch) # 이미지가 흘러 들어간다.
# 이제 흘러들어간 이미지를 next로 호출해야한다.

# ImageDataGenerator를 동작하기 위해서는 next()등으로 iteration을 호출해야함. 
aug_image_batch = next(data_gen_iter)

# 반환된 데이터는 batch까지 포함된 4차원 array이므로 다시 3차원 image array로 변환. 
aug_image = np.squeeze(aug_image_batch)
print('aug_image shape:', aug_image.shape)

# 반환된 pixel값은 float임. 이를 다시 int형으로 변경 후, 이미지 시각화 
aug_image = aug_image.astype('int')
show_image(aug_image)

위에서 말했든 ImageDataGenerator는 랜덤으로 Flip 하기 때문에 바뀔 때가 있고 안 바뀔 때가 있다.

여러번 돌려보면 확인할 수 있다.

 


여러 이미지의 ImageDataGenerator 변환을 수행하는 함수 생성

  • ImageDataGenerator는 next()로 호출이 될 때마다 Random하게 Augmentation 적용이 설정되어 변환됨.
N_IMAGES = 4 # 변환을 몇번 할것이냐에 대한 갯수를 정한다.
fig, axs = plt.subplots(nrows=1, ncols=N_IMAGES, figsize=(22, 8))

for i in range(N_IMAGES):
    aug_image_batch = next(data_gen_iter) # 이미지 데이터를 변환 적용
    aug_image = np.squeeze(aug_image_batch) # 4차원을 3차원으로 쪼갬
    aug_image = aug_image.astype('int') # int로 바꿈
    axs[i].imshow(aug_image)
    axs[i].axis('off')

한번만 변환 되었다.


위의 두 로직을 합쳐보자

# ImageDataGenerator 객체를 입력하면 augmentation 적용된 일련의 image들을 시각화 해주는 함수 생성. 
def show_aug_image_batch(image, data_generator, n_images=4, to_int=True):
    
    image_batch = np.expand_dims(image, axis=0)
    # ImageDataGenerator 적용. fit()후 flow()로 image batch를 넣어주어야 함. 
    data_generator.fit(image_batch)
    data_gen_iter = data_generator.flow(image_batch)
    
    fig, axs = plt.subplots(nrows=1, ncols=n_images, figsize=(6*n_images, 8))

    for i in range(n_images):
        aug_image_batch = next(data_gen_iter)
        aug_image = np.squeeze(aug_image_batch)
        if(to_int):
            aug_image = aug_image.astype('int')
        axs[i].imshow(aug_image)
        axs[i].axis('off')

 

좌우반전, 상하반전, 좌우상하반전을 적용해서 4번씩 뽑아보자.

# Horizontal Flip(좌우 반전)을 적용. horizontal_flip=True을 적용했지만 반드시 변환하는 것은 아님. Random하게 원본 데이터를 유지하거나 변환 결정. 
data_generator = ImageDataGenerator(horizontal_flip=True)
show_aug_image_batch(image, data_generator, n_images=4)

# Vertical Flip(좌우 반전)을 적용. vertical_flip=True을 적용했지만 반드시 변환하는 것은 아님. Random하게 원본 데이터를 유지하거나 변환 결정. 
data_generator = ImageDataGenerator(vertical_flip=True)
show_aug_image_batch(image, data_generator, n_images=4)

# 두개를 함께 적용. 
data_generator = ImageDataGenerator(horizontal_flip=True, vertical_flip=True)
show_aug_image_batch(image, data_generator, n_images=4)

 


Affine Transformation


이미지 Rotation 적용

 

# rotation 범위를 -45 ~ 45도로 설정.         
data_gen = ImageDataGenerator(rotation_range=45)
show_aug_image_batch(image, data_gen, n_images=4)

 

비어있는 곳은 같은 픽셀로 채운다.


이미지 좌우, 상하 이동(Shift)

  • width_shift_range, height_shift_range 입력 인자를 통해 좌우, 상하 이동.
  • width_shift_range, height_shift_range 값은 0 ~ 1 사이 값 부여. 수행 시 마다 전체 이미지 대비 해당 범위의 값 비율만큼 Random 하게 이동.
  • 이동하게 되면 이동되고 남아있는 공간은 비어있게 되는데 이를 어떻게 채울치는 fill_mode 인자로 결정
  • fill_mode는 아래와 같이 설정
    • nearest: 가장 빈공간에 가장 근접한 pixel로 채우기
    • reflect: 빈공간 만큼의 영역을 근처 공간으로 채우되 마치 거울로 반사되는 이미지를 보듯이 채움.
    • wrap: 빈공간을 이동으로 잘려나간 이미지로 채움
    • constant: 특정 픽셀값으로 채움. 이때 특정 픽셀값은 cval 값으로 채움. cval=0 이면 검은색 픽셀로 채움

 

 

# 왼쪽 또는 오른쪽으로 이미지 이동을 주어진 width_shift_range에 따라 random하게 수행. 
data_generator = ImageDataGenerator(width_shift_range=0.4)
show_aug_image_batch(image, data_generator, n_images=4)

40% 까지 이동할 수 있다.

비어 있는 영역은 기본적으로 근처에 있는 영역을 복사하게 된다.

 

# 위쪽 또는 아래쪽 이미지 이동을 주어진 height_shift_range에 따라 random하게 수행.
data_generator = ImageDataGenerator(height_shift_range=0.4)
show_aug_image_batch(image, data_generator, n_images=4)

 

# 빈공간을 가장 가까운 곳의 픽셀값으로 채움. 
data_generator = ImageDataGenerator(width_shift_range=0.4, fill_mode='nearest')
show_aug_image_batch(image, data_generator, n_images=4)

# 빈공간 만큼의 영역을 근처 공간으로 채움. 
data_generator = ImageDataGenerator(width_shift_range=0.4, fill_mode='reflect')
show_aug_image_batch(image, data_generator, n_images=4)

# 빈공간을 이동으로 잘려나간 이미지로 채움
data_generator = ImageDataGenerator(width_shift_range=0.4, fill_mode='wrap')
show_aug_image_batch(image, data_generator, n_images=4)

# 특정 픽셀값으로 채움. 이때 특정 픽셀값은 cval 값으로 채움
data_generator = ImageDataGenerator(width_shift_range=0.4, fill_mode='constant', cval=0)
show_aug_image_batch(image, data_generator, n_images=4)

 


Zoom(Scale) 적용

  • Zoom은 zoom_range을 설정하여 적용. zoom_range가 1보다 작으면 확대(Zoom In), 1보다 크면 축소(Zoom Out)
  • 축소 시 빈 공간은 fill_mode에 따라 픽셀을 채움. Default는 nearest
# Zoom In(확대)
data_generator = ImageDataGenerator(zoom_range=[0.5, 0.9])
show_aug_image_batch(image, data_generator, n_images=4)

# Zoom out(축소)
data_generator = ImageDataGenerator(zoom_range=[1.1, 1.5])
show_aug_image_batch(image, data_generator, n_images=4)

# Zoom out 시 비어 있는 공간 채우기 
data_generator = ImageDataGenerator(zoom_range=[1.1, 1.5], fill_mode='constant', cval=0)
show_aug_image_batch(image, data_generator, n_images=4)


Crop과 Scale(Zoom) 이미지 변환

  • crop은 원본 이미지의 특정 영역을 잘라낸 후 다시 원본 이미지 크기로 Resize .
  • Scale(Zoom)은 원본 이미지 자체 비율을 유지 하면서 이미지를 키우거나(Outbound) 작게 한 뒤에 다시 원본 이미지 만큼의 영역으로 잘 라내는 것.

Shear 적용

shear_range로 이용하여 적용. shear_range=45일 경우 -45 ~ 45 도 사이에서 변환 적용

data_generator = ImageDataGenerator(shear_range=45)
show_aug_image_batch(image, data_generator, n_images=4)


Bright 적용

brightness_range 로 밝기 조절. 0~1 사이 값이 입력 되며, 0에 가까울수록 원본 이미지 보다 더 어둡고, 1보다 커질 수록 원본 이미지 보다 밝음.

# 0으로 갈수록 어두워진다. 픽셀값0 = 검정색, 픽셀값1 = 하얀색
data_generator = ImageDataGenerator(brightness_range=(0.1, 0.9))
show_aug_image_batch(image, data_generator, n_images=4)
data_generator = ImageDataGenerator(brightness_range=(1.0, 1.0))
show_aug_image_batch(image, data_generator, n_images=4)
data_generator = ImageDataGenerator(brightness_range=(1.0, 2.0))
show_aug_image_batch(image, data_generator, n_images=4)
#show_aug_image_batch(image, data_generator, n_images=4)


Channel Shift

  • 원본 이미지의 RGB Pixel값을 channel_shift_range 범위에 있는 임의의 값으로 변환.
# -150 에서 +150 사이의 값으로 픽셀값을 랜덤으로 조절한다. 어두워질 수도 있고 밝아질 수도 있다.
data_generator = ImageDataGenerator(channel_shift_range=150.0) 
show_aug_image_batch(image, data_generator, n_images=4)


Normalization

  • 일반적으로 augmentation 용도가 아닌 CNN 입력 값으로 Pixel값 변환을 위해 0~1사이의 값으로 변환하거나 채널별 Z score변환(평균 0, 표준편차 1) 적용
  • featurewise_center = True이면 R, G, B 각 픽셀값에서 각각의 채널별로 평균 픽셀값을 빼서 평균을 0으로 함.
  • featurewise_std_normalization = True이면 R, G, B 각 픽셀값에서 각각의 채널들의 표준 편차값으로 나눔.
  • rescale = 255.0  : 각 픽셀값을 0 ~ 1사이의 값으로 만들기 위해서 보통 255.0 으로 나눔. (featurewise랑 같이 쓰면 안된다. 하나만)
r_mean = np.mean(image[:, :, 0])
g_mean = np.mean(image[:, :, 1])
b_mean = np.mean(image[:, :, 2])

r_std = np.std(image[:, :, 0])
g_std = np.std(image[:, :, 1])
b_std = np.std(image[:, :, 2])

print(r_mean, g_mean, b_mean, r_std, g_std, b_std )

r_zscore = (image[:, :, 0] - r_mean)/r_std
g_zscore = (image[:, :, 1] - g_mean)/g_std
b_zscore = (image[:, :, 2] - b_mean)/b_std

#print(r_zscore, g_zscore, b_zscore)

 

data_generator = ImageDataGenerator(featurewise_center=True, featurewise_std_normalization=True)
image_batch = np.expand_dims(image, axis=0)
# ImageDataGenerator 적용. fit()후 flow()로 image batch를 넣어주어야 함. 
data_generator.fit(image_batch)
print(data_generator.mean, data_generator.std)


featurewise 적용하기

data_generator = ImageDataGenerator(featurewise_center=True, featurewise_std_normalization=True)
image_batch = np.expand_dims(image, axis=0)
# ImageDataGenerator 적용. fit()후 flow()로 image batch를 넣어주어야 함. 
data_generator.fit(image_batch)
data_gen_iter = data_generator.flow(image_batch)

# ImageDataGenerator를 동작하기 위해서는 next()등으로 iteration을 호출해야함. 
aug_image_batch = next(data_gen_iter)

# 반환된 데이터는 batch까지 포함된 4차원 array이므로 다시 3차원 image array로 변환. 
aug_image = np.squeeze(aug_image_batch)
aug_image

- 값이 있긴 한데 어쨌든 작은 값이 들어가게 된다.


rescale 적용하기

data_generator = ImageDataGenerator(rescale=1/255.0)
aug_image = get_aug_image(image, data_generator)
print(aug_image)

 


싹다 적용하기

data_generator = ImageDataGenerator(
    rotation_range=20,
    width_shift_range=0.2,
    height_shift_range=0.2,
    brightness_range=(0.7, 1.3),
    horizontal_flip=True,
    vertical_flip=True,
    #rescale=1/255.0
)

show_aug_image_batch(image, data_generator, n_images=4)
show_aug_image_batch(image, data_generator, n_images=4)
show_aug_image_batch(image, data_generator, n_images=4)

 

완전히 랜덤으로 결합되고 선택돼서 적용이 된다.

중요한게 어느정도 원본 이미지도 유지를 해주어야 한다.

싹다 바꾸면 뭐가 원본인지 모르기 때문이다.

이렇게 ImageDataGenerator는 사용자가 세밀하게 조절할 수는 없다. 

그런점이 ImageDataGenerator가 아쉬운 부분이다.

복사했습니다!