5기(210102~)/A팀

[Pytorch] 데이터를 뻥튀기하자! Data Augmentation

KAU-Deeperent 2021. 2. 13. 10:27

머신러닝을 하는 중 데이터 수집 단계에서 복사-붙여 넣기의 유혹에 빠지곤 합니다.

데이터를 모으기도 귀찮고, 원하는 이미지 데이터를 직접 잘라내는 일도 만만치 않은 작업입니다.

다행히 우리는 DATA Augmentation이라는 데이터 뻥튀기하는 방법을 알고 있습니다.

 

[DATA Augmentation이란?]

한정된 데이터를 적절한 작업을 통해 늘릴 수 있습니다.

우선 다음 사진을 보겠습니다.

https://www.kdnuggets.com/2018/05/data-augmentation-deep-learning-limited-data.html

 

 인간의 눈으로 보면 위 사진은 같은 고양이를 약간 돌리거나 좌우 반전한 모습에 지나지 않습니다.

 

하지만 컴퓨터의 눈으로 위 사진을 본다면, 살짝 돌리거나 좌우 반전한 같은 고양이의 사진은 완전히 새로운 데이터가 될 것입니다. 컴퓨터는 이미지를 볼 때 세 개의 채널(RGB)로 나누고, 각 픽셀의 RGB농도를 조절하여 합치는 방식으로 데이터를 표현하니까요. 혹은 의도적인 노이즈를 추가해도 될 것입니다. 우리가 알아볼 정도의 노이즈 추가면 괜찮을 것 같습니다.

 

직접 구현해보았습니다. 우선 코드부터 보고, 설명 및 실행 결과까지 확인해 보겠습니다.

 

[코드 보기]

import random
import numpy as np
import os
import cv2
import glob
from PIL import Image
import PIL.ImageOps    

#다음 변수를 수정하여 새로 만들 이미지 갯수를 정합니다.
num_augmented_images = 50

file_path = 'custom_data\Wonbin_faces\\'
file_names = os.listdir(file_path)
total_origin_image_num = len(file_names)
augment_cnt = 1

for i in range(1, num_augmented_images):
    change_picture_index = random.randrange(1, total_origin_image_num-1)
    print(change_picture_index)
    print(file_names[change_picture_index])
    file_name = file_names[change_picture_index]
    
    origin_image_path = 'custom_data\Wonbin_faces\\' + file_name
    print(origin_image_path)
    image = Image.open(origin_image_path)
    random_augment = random.randrange(1,4)
    
    if(random_augment == 1):
        #이미지 좌우 반전
        print("invert")
        inverted_image = image.transpose(Image.FLIP_LEFT_RIGHT)
        inverted_image.save(file_path + 'inverted_' + str(augment_cnt) + '.png')
        
    elif(random_augment == 2):
        #이미지 기울이기
        print("rotate")
        rotated_image = image.rotate(random.randrange(-20, 20))
        rotated_image.save(file_path + 'rotated_' + str(augment_cnt) + '.png')
        
    elif(random_augment == 3):
        #노이즈 추가하기
        img = cv2.imread(origin_image_path)
        print("noise")
        row,col,ch= img.shape
        mean = 0
        var = 0.1
        sigma = var**0.5
        gauss = np.random.normal(mean,sigma,(row,col,ch))
        gauss = gauss.reshape(row,col,ch)
        noisy_array = img + gauss
        noisy_image = Image.fromarray(np.uint8(noisy_array)).convert('RGB')
        noisy_image.save(file_path + 'noiseAdded_' + str(augment_cnt) + '.png')
        
    augment_cnt += 1
    

주피터 노트북을 통해 작성하였습니다. 

깃허브에서 코드를 확인하실 수 있습니다. 훨씬 더 나은 방향으로 업데이트될 수 있습니다 :)

github.com/BUZZINGPolarBear/Why_Am_I_ALONE/blob/master/Image_Augmentation.ipynb

 

BUZZINGPolarBear/Why_Am_I_ALONE

Siamese Network와 Face Net을 활용하여 본인의 얼굴을 원빈과 고릴라와 비교하며 여친이 없는 이유를 찾아보자. - BUZZINGPolarBear/Why_Am_I_ALONE

github.com

 

[코드 설명 보기]

import random
import numpy as np
import os
import cv2
import glob
from PIL import Image
import PIL.ImageOps

#다음 변수를 수정하여 새로 만들 이미지 갯수를 정합니다.
num_augmented_images = 50

윗 문단은 단순 import해주시면 됩니다.

아래 문단의 num_augmented_images는 새로 만들 이미지 개수를 정합니다. 만약 50장의 원본 사진이 폴더에 있고 num_augmentd_images를 30장으로 설정한다면, 최종적으로 폴더에는 80장의 이미지가 저장됩니다.

변수 이름 참 못 짓네요... 죄송합니다...

 

file_path = 'custom_data\Wonbin_faces\\'
file_names = os.listdir(file_path)
total_origin_image_num = len(file_names)
augment_cnt = 1

 

file_path에 원본 사진이 있는 폴더를 정해줍니다.

file_names에는 위의 폴더 내부에 있는 이미지 이름의 배열이 저장됩니다.

total_origin_image_num에는 file_names의 길이를 저장합니다. 만약 50장의 원본 이미지가 있다면, 이 변수는 50이 될 것입니다.

augment_cnt는 몇번의 augmentation이 일어났는지를 확인하기 위해 선언해 주었습니다.

 

for i in range(1, num_augmented_images):
    change_picture_index = random.randrange(1, total_origin_image_num-1)
    print(change_picture_index)
    print(file_names[change_picture_index])
    file_name = file_names[change_picture_index]
    
    origin_image_path = 'custom_data\Wonbin_faces\\' + file_name
    print(origin_image_path)
    image = Image.open(origin_image_path)
    random_augment = random.randrange(1,4)

 for문은 두 파트로 나누어 설명하겠습니다. 
이 부분은 원본 이미지와 Augmetation 방식을 모두 랜덤으로 설정하는 부분입니다.

change_picture_index로 전체 이미지 개수 중 하나를 랜덤 하게 선택하여 file_name에 그 인덱스를 갖는 이미지 이름을 저장합니다. 

 

이제 origin_image_path에는 file_name에 저장해놓은 이름 덕분에 원본 이미지의 경로가 저장되어 있습니다.

image변수에는 이미지가 탑재됩니다.

random_augment 변수에는 1~3의 무작위 수가 결정됩니다. 

1이 선택되었다면 : 원본 이미지를 좌우반전하여 원 폴더에 저장합니다.

2가 선택되었다면 : 원본 이미지를 랜덤하게 -20 ~ 20도 회정하여 원 폴더에 저장합니다.

3이 선택되었다면 : 원본 이미지에 가우스 노이즈를 추가하여 원 폴더에 저장합니다.

 

 if(random_augment == 1):
        #이미지 좌우 반전
        print("invert")
        inverted_image = image.transpose(Image.FLIP_LEFT_RIGHT)
        inverted_image.save(file_path + 'inverted_' + str(augment_cnt) + '.png')
        
    elif(random_augment == 2):
        #이미지 기울이기
        print("rotate")
        rotated_image = image.rotate(random.randrange(-20, 20))
        rotated_image.save(file_path + 'rotated_' + str(augment_cnt) + '.png')
        
    elif(random_augment == 3):
        #노이즈 추가하기
        img = cv2.imread(origin_image_path)
        print("noise")
        row,col,ch= img.shape
        mean = 0
        var = 0.1
        sigma = var**0.5
        gauss = np.random.normal(mean,sigma,(row,col,ch))
        gauss = gauss.reshape(row,col,ch)
        noisy_array = img + gauss
        noisy_image = Image.fromarray(np.uint8(noisy_array)).convert('RGB')
        noisy_image.save(file_path + 'noiseAdded_' + str(augment_cnt) + '.png')
        
    augment_cnt += 1

이미지 반전, 기울이기 부분은 간단하여 설명이 없어도 될 것 같습니다.

다만 노이즈 추가 부분은 가우스 함수에 따른 노이즈 추가 방식을 사용했습니다.

이미지를 불러와 가우스 함수에 따른 노이즈를 추가하고 원 이미지와 합치는 방식입니다.

여기에서 고생을 좀 했었는데요, 이미지를 합치고 난 뒤의 형식은 numpy.array형식입니다. 따라서 바로 이미지로 저장이 되지 않았습니다. 

위 코드에서 보실 수 있듯이

noisy_image = Image.fromarray(np.uint8(noisy_array)).convert('RGB')

이 코드를 통해 array를 이미지로 바꾸어 원 폴더에 저장할 수 있습니다.

 

augment_cnt는 한 바퀴 돌 때마다 +1씩 업데이트해줍니다.

 

[실제 작동 과정 확인하기]

위 코드에는 원빈님의 경로로 되어있지만,.. 사진 저작권에 위배될 수 있으므로 만만한 제 얼굴로 테스트 해보았습니다.

원본 폴더입니다.

수치스럽네요

num_augmentd_images를 50으로 설정하여 50장의 Augmentation된 사진을 얻어보겠습니다.

끔찍해졌네요.

이젠 더 끔찍해졌습니다. 그럼 안녕~~ 

 

작성자 개인 블로그: hipolarbear.tistory.com