본문 바로가기
DeepLearning

[CutMix] 실습 및 정리

by junnykim 2023. 5. 18.

~ 이번 팀 프로젝트를 하게 되면서 처음으로 딥러닝을 시작하게 되었는데, 이제 더이상 안할 것같아서 기록하고자 한다.~
 
 

개념 

딥러닝 모델에게 데이터는 중요한 존재이다. 데이터는 많을수록 좋기 때문이다. 
그래서 한 이미지를 다양한 방법으로 변형시켜 이미지 데이터 수를 늘리는 방법인 데이터 증강(Data Augmentation)을 딥러닝에서 많이 사용한다.
 
그 중 Cutmix는 훈련 이미지들의 패치들이 자르고 붙여져 이미지의 레이블들 또한 패치들의 영역에 정비례하게 섞이게 하는 방법이다. 
Cutout(2017년)과 Mixup(2017년)이라는 이전에 소개된 방법들을 혼합한 방법으로서, 두 방법들의 단점들을 해결하였다. Cutout은 이미지의 일부를 사각형으로 잘라내는 방법으로, 중요한 feature들이 모여있는 영역을 잘라낼 수 있어 딥러닝 훈련에 방해 요인으로 작용할 수 있다.
그리고 Mixup은 두 이미지를 반투명하게 만들어 혼합하는 방법으로, 만들어진 이미지가 애매모호하고 자연스럽지 않아 훈련을 할 딥러닝 모델을 혼란스럽게 만들 수 있다.
그러나 CutMix는 증강된 이미지의 모든 픽셀들을 유용히 사용할 수 있고 합쳐진 이미지의 비율대로 레이블이 되어 더욱 효과적인 훈련을 할 수 있는 장점이 있다.
 
 

실습

4컷,2컷으로 만드는 Cutmix

나중을 대비해서 차근차근히 step적어두기 !
Colab환경, 밑 블로그 참조
 
1.  install 해주기

!pip install opencv-python
!pip install matplotlib

 
 
2. 사진 불러오기 위해서 드라이브 마운트 하기.

from google.colab import drive
drive.mount('/content/drive')

 
3. 사용할 모듈 import 해주고, 이미지 불러오기

import os
import numpy as np
import random
import cv2
import matplotlib.pyplot as plt

image_path = '/content/drive/MyDrive/cloud_data/'
index_len = len(os.listdir(image_path))
image_list = os.listdir(image_path)

def load_image(path, index):
    image = cv2.imread(os.path.join(path, image_list[index]), cv2.IMREAD_COLOR)
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32)
    image /= 255.0

    return image

image = load_image(image_path, 10)
image_size = image.shape[0]

cloud_data에 사진들을 넣어주면 되는데, 이 때 모든 사진들의 크기를 같게 하자.
바보같이 다 다른 사이즈해서 계~~속 에러 났었다....ㅎ
코드상에서 맞추는 방법이 있을 수도 있는데, 나는 그런거 몰라서 그냥 노가다해버림 ㅋㅎ
밑의 코드를 보면 image.shape[0]이 있는데 이것은 이미지의 세로 방향 크기......... 크기 통일할 것!
 
 
4. CutMix

def cutmix(path, index, imsize):
    w, h = imsize, imsize # 이미지의 가로와 세로 크기를 설정
    s = imsize // 2

    # 랜덤하게 선택한 값을 xc와 yc로 지정. 잘라낼 영역의 중심 좌표를 나타낸다.
    xc, yc = [int(random.uniform(imsize * 0.25, imsize * 0.75)) for _ in range(2)]  # 256 ~ 768

    # index와 랜덤하게 선택한 인덱스 3개를 리스트 indexes에 저장. 잘라낼 영역에 사용할 이미지의 인덱스.
    indexes = [index] + [random.randint(0, index) for _ in range(3)]
    
    # result_img를 크기 (imsize, imsize, 3)로 생성. 이는 결과 이미지를 저장할 배열. 초기값은 1로 설정
    result_img = np.full((imsize, imsize, 3),1, dtype=np.float32)

    # indexes에 저장된 각 인덱스에 대해 반복
    for i, index in enumerate(indexes):
        image = load_image(path, index) # 인덱스에 대응하는 이미지 로

        # 잘라낼 영역의 위치와 크기를 설정. i 값에 따라서 이미지를 잘라낼 위치를 결정
        if i == 0:  # top left
            x1a, y1a, x2a, y2a = max(xc - w, 0), max(yc - h, 0), xc, yc
            x1b, y1b, x2b, y2b = w - (x2a - x1a), h - (y2a - y1a), w, h
        elif i == 1:  # top right
            x1a, y1a, x2a, y2a = xc, max(yc - h, 0), min(xc + w, s * 2), yc
            x1b, y1b, x2b, y2b = 0, h - (y2a - y1a), min(w, x2a - x1a), h
        elif i == 2:  # bottom left
            x1a, y1a, x2a, y2a = max(xc - w, 0), yc, xc, min(s * 2, yc + h)
            x1b, y1b, x2b, y2b = w - (x2a - x1a), 0, max(xc, w), min(y2a - y1a, h)
        elif i == 3:  # bottom right
            x1a, y1a, x2a, y2a = xc, yc, min(xc + w, s * 2), min(s * 2, yc + h)
            x1b, y1b, x2b, y2b = 0, 0, min(w, x2a - x1a), min(y2a - y1a, h)

        # 잘라낸 조각이 결과 이미지에 덮어씌워질 범위에 대해 조건문을 사용하여 크기가 0이 아닌 경우에만 조각을 덮어씌우기.
        if x2a - x1a > 0 and y2a - y1a > 0:  # 조각의 크기가 0이 아닌 경우에만 조각 내기 수행
            piece = cv2.resize(image[y1b:y2b, x1b:x2b], (x2a - x1a, y2a - y1a))
            result_img[y1a:y2a, x1a:x2a] = piece

    return result_img

이 코드가 CutMix코드이다.
주석 달았는데 보기 편하게 다시 정리 한 것.

더보기

1. imsize 변수를 이용하여 이미지의 가로와 세로 크기를 설정한다.
2. imsize * 0.25와 imsize * 0.75 사이에서 랜덤하게 선택한 값을 xc와 yc로 지정한다. 이것은 잘라낼 영역의 중심 좌표를 나타낸다.
3. index와 랜덤하게 선택한 인덱스 3개를 리스트 indexes에 저장합니다. 이건 잘라낼 영역에 사용할 이미지의 인덱스를 나타낸다.
4. np.full() 함수를 사용하여 result_img를 크기 (imsize, imsize, 3)로 생성한다. 이는 결과 이미지를 저장할 배열이고, 초기값은 1로 설정된다.
5. indexes에 저장된 각 인덱스에 대해 반복한다.
6. 해당 인덱스에 대응하는 이미지를 load_image() 함수를 사용하여 로드한다.
7. 잘라낼 영역의 위치와 크기를 설정한다. i 값에 따라서 이미지를 잘라낼 위치를 결정한다.
8. 잘라낸 조각이 결과 이미지에 덮어씌워질 범위에 대해 조건문을 사용하여 크기가 0이 아닌 경우에만 조각을 덮어씌운다.

 
 
5. 확인

test = cutmix(image_path, 10, image_size)
plt.imshow(test)
plt.show()
결과

 
 
 
2컷으로 만드는 코드 - 세로

def cutmix2(path, index, imsize):
    w, h = imsize, imsize # 이미지의 가로와 세로 크기를 설정
    s = imsize // 2

    # 랜덤하게 선택한 값을 xc와 yc로 지정. 잘라낼 영역의 중심 좌표를 나타낸다.
    xc, yc = [int(random.uniform(imsize * 0.25, imsize * 0.75)) for _ in range(2)]  # 256 ~ 768

    # index와 랜덤하게 선택한 인덱스 1개를 리스트 indexes에 저장. 잘라낼 영역에 사용할 이미지의 인덱스.
    indexes = [index] + [random.randint(0, index) for _ in range(1)]

    # result_img를 크기 (imsize, imsize, 3)로 생성. 이는 결과 이미지를 저장할 배열. 초기값은 1로 설정
    result_img = np.full((imsize, imsize, 3),1, dtype=np.float32)

    # indexes에 저장된 각 인덱스에 대해 반복
    for i, index in enumerate(indexes):
        image = load_image(path, index) # 인덱스에 대응하는 이미지 로

        # 잘라낼 영역의 위치와 크기를 설정. i 값에 따라서 이미지를 잘라낼 위치를 결정
        if i == 0:  # top left
            x1a, y1a, x2a, y2a = max(xc - w, 0), max(yc - h, 0), xc, h # y2a 변경
            x1b, y1b, x2b, y2b = w - (x2a - x1a), h - (y2a - y1a), w, h
        elif i == 1:  # top right
            x1a, y1a, x2a, y2a = xc, max(yc - h, 0), min(xc + w, s * 2), h # y2a 변경
            x1b, y1b, x2b, y2b = 0, h - (y2a - y1a), min(w, x2a - x1a), h
        

        # 잘라낸 조각이 결과 이미지에 덮어씌워질 범위에 대해 조건문을 사용하여 크기가 0이 아닌 경우에만 조각을 덮어씌우기.
        if x2a - x1a > 0 and y2a - y1a > 0:  # 조각의 크기가 0이 아닌 경우에만 조각 내기 수행
            piece = cv2.resize(image[y1b:y2b, x1b:x2b], (x2a - x1a, y2a - y1a))
            result_img[y1a:y2a, x1a:x2a] = piece

    return result_img

 

결과

 
 
2컷으로 만드는 코드 - 가로

def cutmix3(path, index, imsize):
    w, h = imsize, imsize # 이미지의 가로와 세로 크기를 설정
    s = imsize // 2

    # 랜덤하게 선택한 값을 xc와 yc로 지정. 잘라낼 영역의 중심 좌표를 나타낸다.
    xc, yc = [int(random.uniform(imsize * 0.25, imsize * 0.75)) for _ in range(2)]  # 256 ~ 768

    # index와 랜덤하게 선택한 인덱스 1개를 리스트 indexes에 저장. 잘라낼 영역에 사용할 이미지의 인덱스.
    indexes = [index] + [random.randint(0, index) for _ in range(1)]

    # result_img를 크기 (imsize, imsize, 3)로 생성. 이는 결과 이미지를 저장할 배열. 초기값은 1로 설정
    result_img = np.full((imsize, imsize, 3),1, dtype=np.float32)

    # indexes에 저장된 각 인덱스에 대해 반복
    for i, index in enumerate(indexes):
        image = load_image(path, index) # 인덱스에 대응하는 이미지 로

        # 잘라낼 영역의 위치와 크기를 설정. i 값에 따라서 이미지를 잘라낼 위치를 결정
        if i == 0:  # top left
            x1a, y1a, x2a, y2a = max(xc - w, 0), max(yc - h, 0), w, yc # x2a 변경
            x1b, y1b, x2b, y2b = w - (x2a - x1a), h - (y2a - y1a), w, h
        elif i == 1:  # bottom left
            x1a, y1a, x2a, y2a = max(xc - w, 0), yc, w, min(s * 2, yc + h) # x2a 변경
            x1b, y1b, x2b, y2b = w - (x2a - x1a), 0, max(xc, w), min(y2a - y1a, h)
        

        # 잘라낸 조각이 결과 이미지에 덮어씌워질 범위에 대해 조건문을 사용하여 크기가 0이 아닌 경우에만 조각을 덮어씌우기.
        if x2a - x1a > 0 and y2a - y1a > 0:  # 조각의 크기가 0이 아닌 경우에만 조각 내기 수행
            piece = cv2.resize(image[y1b:y2b, x1b:x2b], (x2a - x1a, y2a - y1a))
            result_img[y1a:y2a, x1a:x2a] = piece

    return result_img

 

결과

 

 

Cutmix

세팅은 비슷한데, 달라진거 위주로 차근차근 볼 것!
 
1. 모듈 import

import tensorflow as tf
from tensorflow import keras

# Helper libraries
import numpy as np
import matplotlib.pyplot as plt

import tensorflow_datasets as tfds

 
 
2. 사용할 사진 가지고 오기

image_path = '/content/drive/MyDrive/cloud_data/'

index_len = len(os.listdir(image_path))
image_list = os.listdir(image_path)

def load_image(path, index):
    image = cv2.imread(os.path.join(path, image_list[index]), cv2.IMREAD_COLOR)
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32)
    image /= 255.0

    return image

image = load_image(image_path, 0)
image_size = image.shape[0]

image_a = cv2.imread(os.path.join(image_path, image_list[0]), cv2.IMREAD_COLOR)
image_a = cv2.cvtColor(image_a, cv2.COLOR_BGR2RGB).astype(np.float32)
image_a /= 255.0

image_b = cv2.imread(os.path.join(image_path, image_list[1]), cv2.IMREAD_COLOR)
image_b = cv2.cvtColor(image_b, cv2.COLOR_BGR2RGB).astype(np.float32)
image_b /= 255.0

plt.subplot(1,2,1)
plt.title("image_a")
plt.imshow(image_a)

plt.subplot(1,2,2)
plt.title("image_b")
plt.imshow(image_b)

위에서 사용한 코드랑 비슷하지만, 지금은 2가지의 사진만 사용할 것이므로 약간 수정했다.
 

결과

 
 
3. 잘라낼 영역 박스 좌표 결정 함수

# 삽입될 영역의 바운딩 박스의 위치를 결정하는 함수
def get_clip_box(image_a, image_b): 
    # image.shape = (height, width, channel)
    image_size_x = image_a.shape[1]   # width
    image_size_y = image_a.shape[0]   # height
    
    # 박스의 전체 영역을 가져오기
    x = tf.cast( tf.random.uniform([], minval=0, maxval=image_size_x), tf.int32)
    y = tf.cast( tf.random.uniform([], minval=0, maxval=image_size_y), tf.int32)

    # 박스의 너비와 높이를 가져오기
    width  = tf.cast(image_size_x * tf.math.sqrt(1-tf.random.uniform([],0,1)), tf.int32)
    height = tf.cast(image_size_y * tf.math.sqrt(1-tf.random.uniform([],0,1)), tf.int32)
    
    # 이미지에서 박스를 자르고 최소 및 최대 바운딩 박스를 얻기
    xa = tf.math.maximum(0, x-width//2)        # get point starting in an area larger than 0       
    ya = tf.math.maximum(0, y-height//2)              
    xb = tf.math.minimum(image_size_x, x+width//2)   # get point ending in an area smaller than the image size
    yb = tf.math.minimum(image_size_y, y+width//2)
    
    return xa, ya, xb, yb

xa, ya, xb, yb = get_clip_box(image_a, image_b)
print(xa, ya, xb, yb)

 
 
 
4. 바운딩 안쪽 영역을 가져와 합치는 함수

# 다른 이미지 b에서 바운딩 박스 안쪽 영역을 가져와서 합치는 함수
# mix two images
def mix_2_images(image_a, image_b, xa, ya, xb, yb):
    # image.shape = (height, width, channel)
    image_size_x = image_a.shape[1]
    image_size_y = image_a.shape[0] 
    
    # 이미지를 다섯 부분으로 분할하여 영역을 결합
    one = image_a[ya:yb,0:xa,:]
    two = image_b[ya:yb,xa:xb,:]
    three = image_a[ya:yb,xb:image_size_x,:]
    middle = tf.concat([one,two,three],axis=1)
    
    top = image_a[0:ya,:,:] # image_a의 첫 번째 행부터 ya 전까지의 행 가져오
    bottom = image_a[yb:image_size_y,:,:] # image_a의 yb 다음 행부터 마지막 행까지의 행 가져오기
    mixed_img = tf.concat([top, middle, bottom],axis=0) # top, middle, bottom을 세로로 연결한 텐서인 mixed_img를 생성
    
    return mixed_img

mixed_img = mix_2_images(image_a, image_b, xa, ya, xb, yb)

plt.title("cut_mixed image")
plt.imshow(mixed_img.numpy())

 

완료

 
 
 
 
참고 : https://ruek.tistory.com/239
https://www.cubox.ai/board/blog/board_view.php?&page=1&num=625
https://github.com/stereo-weld/explorations/blob/master/CV4_Project_Augmentation_CutMix_MixUp_v2.ipynb

'DeepLearning' 카테고리의 다른 글

[CNN] 구름 분류  (0) 2023.07.14

댓글