Tensorflow 2.0 Tutorial ch9.4 - 초해상도

Page content

공지

  • 본 Tutorial은 교재 시작하세요 텐서플로 2.0 프로그래밍의 강사에게 국비교육 강의를 듣는 사람들에게 자료 제공을 목적으로 제작하였습니다.

  • 강사의 주관적인 판단으로 압축해서 자료를 정리하였기 때문에, 자세하게 공부를 하고 싶으신 분은 반드시 교재를 구매하실 것을 권해드립니다.

  • 본 교재 외에 강사가 추가한 내용에 대한 Reference를 확인하셔서, 추가적으로 학습하시는 것을 권유드립니다.

Tutorial

이전 강의가 궁금하신 분들은 아래에서 선택하여 추가 학습 하시기를 바랍니다.

I. 개요

  • 저해상도에서 고해상도의 이미지로 변환하는 것은 어려운 연산입니다.
  • 픽셀로 구성된 이미지는 고해상도로 변환(확대)하면 이미지에서 사각형이 두드러져 보이는 것이 바로 초해상도(Super Resolution) 작업입니다.
  • 전통적으로는 interpolation(보간) 등의 기법이 있지만, 선명함을 잃고 흐릿해지는 단점이 있습니다.
  • 오토인코더로 초해상도 작업을 하는 과정을 진행합니다.
  • 이 때, REDNet이라는 네트워크를 사용합니다.

II. REDNet1

  • REDNetResidual Encoder-Decoder Network의 약자이며, ResidualResNet등에서 사용하는 건너뛴 연결(skip-connection)입니다.
  • 다수의 레이어가 중첩되는 구조에서 앞쪽의 정보를 잃어버리기 않기 위해 뒤쪽에 정보를 그대로 전달해줄 때 건너뛴 연결이 사용됩니다.
  • 논문에서 사용한 이미지는 BSD (Berkeley Segmentation Dataset) 이며, 책에서도 동일하게 사용합니다.
# 텐서플로 2 버전 선택
try:
    # %tensorflow_version only exists in Colab.
    %tensorflow_version 2.x
except Exception:
    pass
import tensorflow as tf
import numpy as np
import pandas as pd
import tensorflow_hub as hub
import matplotlib.pyplot as plt
import cv2

III. 데이터 불러오기

  • tf.keras.utils.get_file() 데이터를 불러옵니다.
tf.keras.utils.get_file('/content/bsd_images.zip', 'http://bit.ly/35pHZlC', extract=True)
Downloading data from http://bit.ly/35pHZlC
37527552/37520292 [==============================] - 0s 0us/step





'/content/bsd_images.zip'
!unzip /content/bsd_images.zip
Archive:  /content/bsd_images.zip
   creating: images/
   creating: images/test/
  inflating: images/test/100007.jpg  
  inflating: images/test/100039.jpg  
  .
  .
  .
  .
  inflating: images/val/97033.jpg    
  inflating: images/val/Thumbs.db    
  • 이미지 경로 저장 및 확인을 합니다.
import pathlib
image_root = pathlib.Path('/content/images')
all_images_paths=list(image_root.glob('*/*'))
print(all_images_paths[:10])
[PosixPath('/content/images/val/62096.jpg'), PosixPath('/content/images/val/361010.jpg'), PosixPath('/content/images/val/Thumbs.db'), PosixPath('/content/images/val/54082.jpg'), PosixPath('/content/images/val/87046.jpg'), PosixPath('/content/images/val/38082.jpg'), PosixPath('/content/images/val/156065.jpg'), PosixPath('/content/images/val/33039.jpg'), PosixPath('/content/images/val/14037.jpg'), PosixPath('/content/images/val/159008.jpg')]
  • 각 이미지의 경로는 root 디렉터리에서 glob() 함수를 사용해 하단의 모든 파일을 불러올 수 있습니다.
  • 각 파일의 경로는 PosixPath라는 객체가 되는데, 이 객체에서 경로를 가져오기 위해서는 문자열로 변환하는 str()함수를 사용합니다.

(1) 이미지 시각화

  • matplotlib.pyplot으로 확인합니다.
import PIL.Image as Image
import matplotlib.pyplot as plt

plt.figure(figsize=(12, 12))
for c in range(9):
  plt.subplot(3,3,c+1)
  plt.imshow(plt.imread(all_images_paths[c]))
  plt.title(all_images_paths[c])
  plt.axis('off')
plt.show()

/img/tensorflow2.0/tutorial_09_04/output_7_1.png png

  • 데이터를 보면 아시겠지만, 가로와 세로 길이가 모두 다릅니다. 또한, 이미지의 내용은 사람, 동물, 자연등으로 다양합니다.

(2) 이미지 경로 분리 저장

  • BSD500은 200장의 훈련 데이터, 100장의 검증 데이터, 200장의 테스트 데이터로 구성되어 있습니다.
  • tf.data.Dataset를 각 데이터세트마다 만듭니다.
train_path, valid_path, test_path = [], [], []

for image_path in all_images_paths:
  if str(image_path).split('.')[-1] != "jpg":
    continue
  
  if str(image_path).split('/')[-2]== 'train': 
    train_path.append(str(image_path))
  elif str(image_path).split('/')[-2]=='val':
    valid_path.append(str(image_path))
  else:
    test_path.append(str(image_path))

(3) 원본 이미지 조각 추출, 입력, 출력 데이터 변환 함수 정의

  • 현재의 이미지는 고해상도이기 때문에, 저해상도는 일부러 낮추고 원본과 함께 반환하는 함수를 만듭니다.
  • 원본 이미지에서 조각을 추출해서 입력, 출력 데이터를 생성하는 구조에 관한 이미지는 교재 360페이지를 확인해봅니다.
def get_hr_and_lr(image_path):
  img = tf.io.read_file(image_path)
  img = tf.image.decode_jpeg(img, channels=3)
  img = tf.image.convert_image_dtype(img, tf.float32)

  hr = tf.image.random_crop(img, [50,50,3])
  lr = tf.image.resize(hr, [25, 25])
  lr = tf.image.resize(lr, [50,50])

  return lr, hr 
  • JPEG 이미지는 tf.io.read_file()로 불러올 수 있습니다.
  • 이미지를 불러온 후 decode_jpeg() 함수를 사용해서 프로그램이 이해할 수 있는 데이터 형태로 만들어야 하고, convert_image_dtype()함수로 데이터 타입을 딥러닝에서 가장 범용적으로 사용하는 float32 데이터 타입으로 바꿉니다.
  • 가로 X 세로 50픽셀의 이미지를 random_crop()이라는 함수로 쉽게 얻을 수 있습니다.
  • 마지막의 3은 컬러 채널의 수를 의미합니다.

(4) train, valid Dataset 정의

  • Dataset를 정의할 차례이며, 학습 과정에서 사용할 훈련 데이터와 검증 데이터를 사용하는 Dataset를 각각 정의합니다.
train_dataset = tf.data.Dataset.list_files(train_path)
train_dataset = train_dataset.map(get_hr_and_lr)
train_dataset = train_dataset.repeat()
train_dataset = train_dataset.batch(16)

valid_dataset = tf.data.Dataset.list_files(train_path)
valid_dataset = valid_dataset.map(get_hr_and_lr)
valid_dataset = valid_dataset.repeat()
valid_dataset = valid_dataset.batch(1)
  • 먼저 파일의 경로 리스트를 가지고 있다면 tf.data.Dataset.list_files() 함수의 인수로 파일 경로 리스트를 넣어서 Dataset를 쉽게 정의할 수 있습니다.
  • get_hr_and_lr() 함수를 map()함수를 적용해 새로운 Dataset을 만듭니다.
  • 이렇게 연결되면 Dataset는 먼저 train_path 리스트의 이미지를 불러온 다음에 저해상도와 고해상도 조각인 lr, hr을 반환합니다. 여기까지 선언하면 쉽게 사용할 수 있는 Dataset을 1차로 완성한 것입니다.

(5) REDNet-30의 정의

REDNet-30에 대한 구조에 대한 설명은 교재 362-363 p를 확인합니다. 다음 소스코드로 네트워크 정의를 합니다.

def REDNet(num_layers):
    conv_layers = []
    deconv_layers = []
    residual_layers = []

    inputs = tf.keras.layers.Input(shape=(None, None, 3))
    conv_layers.append(tf.keras.layers.Conv2D(3, kernel_size=3, padding='same', activation='relu'))

    for i in range(num_layers-1):
        conv_layers.append(tf.keras.layers.Conv2D(64, kernel_size=3, padding='same', activation='relu'))
        deconv_layers.append(tf.keras.layers.Conv2DTranspose(64, kernel_size=3, padding='same', activation='relu'))

    deconv_layers.append(tf.keras.layers.Conv2DTranspose(3, kernel_size=3, padding='same'))

    # 인코더 시작
    x = conv_layers[0](inputs)

    for i in range(num_layers-1):
        x = conv_layers[i+1](x)
        if i % 2 == 0:
            residual_layers.append(x)

    # 디코더 시작
    for i in range(num_layers-1):
        if i % 2 == 1:
            x = tf.keras.layers.Add()([x, residual_layers.pop()])
            x = tf.keras.layers.Activation('relu')(x)
        x = deconv_layers[i](x) 

    x = deconv_layers[-1](x)
    
    model = tf.keras.Model(inputs=inputs, outputs=x)
    return model
  • 위 함수를 통해서, REDNet-10, REDNet-20, REDNet-30등 다양한 네트워크를 함수 호출 한 번으로 만들 수 있습니다.
  • num_layers는 컨볼루션 레이어와 디컨볼루션 레이어의 수입니다. 같은 수의 컨볼루션 레이어가 존재하기 때문에, REDNet-30이라면 num_layers=15를 입력하면 됩니다.
conv_layers=[]
deconv_layers=[]
residual_layers=[]

먼저 세 개의 리스트에 각각 컨볼루션 레이어, 디컨볼루션 레이어, 잔류(residual)레이어를 저장합니다.

각 레이어를 따로 저장할 리스트가 별도로 필요로 합니다.

    ...
    inputs = tf.keras.layers.Input(shape=(None, None, 3))
    conv_layers.append(tf.keras.layers.Conv2D(3, kernel_size=3, padding='same', activation='relu'))

    for i in range(num_layers-1):
        conv_layers.append(tf.keras.layers.Conv2D(64, kernel_size=3, padding='same', activation='relu'))
        deconv_layers.append(tf.keras.layers.Conv2DTranspose(64, kernel_size=3, padding='same', activation='relu'))

    deconv_layers.append(tf.keras.layers.Conv2DTranspose(3, kernel_size=3, padding='same'))
    ...
  • 먼저 입력 레이어를 정의합니다. 입력 레이어의 shape에서 이미지의 높이와 너비를 None으로 지정해서 어떤 크기의 이미지라도 입력으로 받을 수 있습니다.
  • 첫 번째 컨볼루션 레이어와 마지막 디컨볼루션 레이어를 제외한 레이어들은 for 문 안에서 정의해서 각 리스트에 저장합니다.
  • 첫번째 컨볼루션 레이어와 마지막 디컨볼루션 레이어는 필터의 수가 다른데 이는 필터의 수로 RGB 채널의 수인 3을 그대로 받기 위함입니다. 나머지 레이어에서는 64개의 필터를 사용합니다.
    # 인코더 시작
    x = conv_layers[0](inputs)

    for i in range(num_layers-1):
        x = conv_layers[i+1](x)
        if i % 2 == 0:
            residual_layers.append(x)

    # 디코더 시작
    for i in range(num_layers-1):
        if i % 2 == 1:
            x = tf.keras.layers.Add()([x, residual_layers.pop()])
            x = tf.keras.layers.Activation('relu')(x)
        x = deconv_layers[i](x) 

    x = deconv_layers[-1](x)
  • 여기에서는 x라는 변수에 레이어를 계속 적용해서 함수형 API를 사용합니다. 마지막에 x는 모든 레이어가 적용된 결과가 되기 때문에 모델의 출력이 됩니다.
  • 이렇게 하나의 변수 이름을 재사용하여 레이어를 적용해나가는 방법은 케라스의 함수형 APItorch에서 일반적으로 쓰이는 문법입니다.
  • 첫번째 명령인 x = conv_layers[0](inputs)의 결과로 x는 입력 레이어에 첫 번째 컨볼루션 레이어를 적용한 결과가 됩니다.
    for i in range(num_layers-1):
        x = conv_layers[i+1](x)
        if i % 2 == 0:
            residual_layers.append(x)
  • 그 다음으로는 for 문 안에서 x에 나머지 컨볼루션 레이어를 계속 적용시키며, 짝수번재 컨볼루션 레이어를 지날 때마다 x를 잔류 레이어 리스트에도 저장합니다.
  • 잔류 레이어에 x를 저장한 다음 스텝에서 x는 다시 컨볼루션 레이어를 통과해서 새로운 값이 되지만 잔류 레이어에 이미 저장된 값은 사라지지 않습니다.
  • 교재 366페이지에 그림설명이 있기 때문에 한번 더 확인하시기를 바랍니다.
    # 디코더 시작
    for i in range(num_layers-1):
        if i % 2 == 1:
            x = tf.keras.layers.Add()([x, residual_layers.pop()])
            x = tf.keras.layers.Activation('relu')(x)
        x = deconv_layers[i](x) 

    x = deconv_layers[-1](x)
  • 두 번째 for 문 안에서는 홀수 번째의 디컨볼루션 레이어를 통과할 경우 잔류 레이어 리스트에 저장돼 있던 값을 residual_layers.pop()으로 뒤에서부터 하나씩 가져옵니다. 그 다음 합연산과 ReLU 활성화함수를 통과한 후 다음 디컨볼루션 레이어에 연결시킵니다.
  • 짝수 번째일 때는 디컨볼루션 레이어만 연결합니다.
model = tf.keras.Model(inputs=inputs, outputs=x)
  • tf.keras의 함수형 APIModel을 만들기 위해서는 입력과 출력만 지정하면 됩니다.
  • 입력인 inputs는 함수의 가장 앞에서 정의한 입력 레이어로, 출력인 outputs는 지금까지 레이어 연산을 쭉 따라온 변수 이름인 x로 넣고, model을 반환합니다.

(6) PSNR 계산 공식 및 함수 정의

  • 고해상도 이미지가 잘 복원됐는지 알기 위해서 특별한 측정값을 컴파일에 추가해서 테스트할 수 있습니다.
  • 이 측정값은 PSNR(Peak Signal-to-Noise Ratio), 즉 “신호 대 잡임비"입니다.
  • PSNR의 계산 공식은 다음과 같습니다.

$$PSNR = 20\ast log_{10}\frac{Max(pixel)}{\sqrt{MSE}}$$

  • 여기서 Max(pixel)은 픽셀의 최대값으로, 앞에서 float32로 계산했기 때문에 이 값은 1.0이 됩니다.
  • MSE는 우리가 잘 알고 있는 평균 제곱 오차(Mean Squared Error)입니다.
  • 평균 제곱 오차가 로그의 분모에 있기 때문에 이 식은 평균 제곱 오차가 낮을수록 큰 값을 갖게 됩니다.
def psnr_metric(y_true, y_pred):
  return tf.image.psnr(y_true, y_pred, max_val=1.0)
  • Metric은 분류의 정확도(accuracy)처럼 tf.keras에 등록되어 있을 경우 그대로 사용하면 되지만 PSNR은 지원하지 않기 때문에 함수를 만들어줍니다.
  • y_true는 정답에 해당하는 값이고, y_pred는 네트워크가 학습 결과 예측한 값입니다.
  • 이 둘의 tf.image.psnr()을 계산해서 반환하는 것이 psnr_metric() 함수의 역할입니다.

(7) REDNet-30 네트워크 초기화 및 컴파일

  • 이제 REDNet() 함수로 네트워크를 초기화하고 컴파일합니다.
model = REDNet(15)
model.compile(optimizer=tf.optimizers.Adam(0.0001), loss='mse', metrics=[psnr_metric])
  • 컴파일된 네트워크 시각화를 작성해봅니다.
tf.keras.utils.plot_model(model)

(8) REDNet-30 네트워크 학습

  • 이제 네트워크를 학습합니다.
history = model.fit_generator(train_dataset, 
                              epochs=1000, 
                              steps_per_epoch=len(train_path)//16, 
                              validation_data=valid_dataset, 
                              validation_steps=len(valid_path), 
                              verbose=2)
WARNING:tensorflow:From <ipython-input-15-11785503027a>:6: Model.fit_generator (from tensorflow.python.keras.engine.training) is deprecated and will be removed in a future version.
Instructions for updating:
Please use Model.fit, which supports generators.
Epoch 1/1000
12/12 - 4s - loss: 0.2467 - psnr_metric: 7.6192 - val_loss: 0.2287 - val_psnr_metric: 8.1383
Epoch 2/1000
12/12 - 3s - loss: 0.1935 - psnr_metric: 8.8387 - val_loss: 0.0982 - val_psnr_metric: 11.4855
.
.
.
Epoch 998/1000
12/12 - 3s - loss: 0.0015 - psnr_metric: 32.7882 - val_loss: 0.0013 - val_psnr_metric: 32.6776
Epoch 999/1000
12/12 - 3s - loss: 0.0015 - psnr_metric: 32.2986 - val_loss: 0.0015 - val_psnr_metric: 32.9123
Epoch 1000/1000
12/12 - 3s - loss: 0.0015 - psnr_metric: 32.4111 - val_loss: 0.0014 - val_psnr_metric: 32.6642
  • Dataset를 이용한 학습은 fit() 함수 대신 fit_generator() 함수를 사용합니다.
  • Datasetrepeat() 함수를 사용했기 때문에 한 번의 에포크에 몇 개의 데이터를 학습시킬지를 지정하는 steps_per_epoch인수를 설정해야 합니다.
  • batch size가 16이기 때문에 steps_per_epochlen(train_path)//16으로 훈련 데이터의 크기를 batch size로 나눕니다.
    • //는 결과가 정수로 나오는 나눗셈을 의미합니다. 예를 들면 200//16의 결과는 12가 됩니다.
  • verbose=2의 의미니느 출력제한에 걸리지 않도록 하며, 진행 상황 애니메이션은 생략하고 각 에포크의 결과만 출력합니다.
  • 학습 결과, 훈련 데이터의 PSNR은 31~32, 검증데이터의 29-32정도가 나옵니다.

(9) 네트워크 학습 결과 확인

학습 겨로가를 확인합니다.

import matplotlib.pyplot as plt
plt.plot(history.history['psnr_metric'], 'b-', label='psnr')
plt.plot(history.history['val_psnr_metric'], 'r--', label='val_psnr')
plt.xlabel('Epoch')
plt.legend()
plt.show()

png

PSNR 수치 모두 학습할수록 증가하는 경향을 보입니다. 이렇게 학습된 데이터가 실제 이미지를 어떻게 복원하는지 확인합니다.

img = tf.io.read_file(test_path[0])
img = tf.image.decode_jpeg(img, channels=3)
hr = tf.image.convert_image_dtype(img, tf.float32)

lr = tf.image.resize(hr, [hr.shape[0]//2, hr.shape[1]//2])
lr = tf.image.resize(lr, [hr.shape[0], hr.shape[1]])
predict_hr=model.predict(np.expand_dims(lr, axis=0))

print(tf.image.psnr(np.squeeze(predict_hr, axis=0), hr, max_val=1.0))
print(tf.image.psnr(lr, hr, max_val=1.0))
tf.Tensor(26.110872, shape=(), dtype=float32)
tf.Tensor(24.853115, shape=(), dtype=float32)

(10) 테스트 이미지에 대한 초해상도 결과 확인

  • 테스트 데이터 중 첫 번째 이미지를 불러와서 저해상도 버전을 만든 다음 REDNet-30 네트워크에 통과시켜서 복원 이미지를 얻어냅니다.
  • 결과에서 PSNR 점수는 복원 이미지가 저해상도 이미지보다 아주 살짝 높은 것으로 나옵니다.
  • 이미지를 출력합니다.
plt.figure(figsize=(16,4))
plt.subplot(1,3,1)
plt.imshow(hr)
plt.title('original - hr')

plt.subplot(1,3,2)
plt.imshow(lr)
plt.title('lr')

plt.subplot(1,3,3)
plt.imshow(np.squeeze(predict_hr, axis=0))
plt.title('sr')

plt.show()
  • 첫번째 사진이 이미지의 원본이고, 두번째 사진이 저해상도, 세번째 줄이 복원된 이미지입니다.
  • 이 작업의 성능을 비교하기 위해 Set5라는 데이터세트가 있습니다. 이 중에서도 자주 쓰이는 나비의 사진을 불러와서 확인합니다.
image_path = tf.keras.utils.get_file('butterfly.png', 'http://bit.ly/2oAOxgH')
img = tf.io.read_file(image_path)
img = tf.image.decode_jpeg(img, channels=3)
hr = tf.image.convert_image_dtype(img, tf.float32)

lr = tf.image.resize(hr, [hr.shape[0]//4, hr.shape[1]//4])
lr = tf.image.resize(lr, [hr.shape[0], hr.shape[1]])
predict_hr = model.predict(np.expand_dims(lr, axis=0))

print(tf.image.psnr(np.squeeze(predict_hr, axis=0), hr, max_val=1.0))
print(tf.image.psnr(lr, hr, max_val=1.0))


plt.figure(figsize=(16,6))
plt.subplot(1, 3, 1)
plt.imshow(hr)
plt.title('original - hr')

plt.subplot(1, 3, 2)
plt.imshow(lr)
plt.title('lr')

plt.subplot(1, 3, 3)
plt.imshow(np.squeeze(predict_hr, axis=0))
plt.title('sr')
Downloading data from http://bit.ly/2oAOxgH
131072/127529 [==============================] - 0s 0us/step
tf.Tensor(20.3429, shape=(), dtype=float32)
tf.Tensor(20.217585, shape=(), dtype=float32)





Text(0.5, 1.0, 'sr')

png

  • 벤치마크답게 PSNR 점수에서 성능 차이가 뚜렷하게 드러납니다.
  • 조금 더 어려운 과제로는 확대 비율을 늘려볼 수 있습니다. 현재는 2배인데, 이를 4배로 늘려봅니다.

(11) 확대비율 4배로 수정, 이미지 보강 REDNet-30 네트워크 학습

Dataset에서는 쉽게 이미지 보강을 할 수 있습니다. 이미지에서 랜덤한 부분을 잘라서 고해상도와 저해상도 버전을 추출하던 get_hr_and_lr() 함수를 다음과 같이 바꿉니다.

import random
def get_hr_and_lr_flip_s4(image_path):
  img = tf.io.read_file(image_path)
  img = tf.image.decode_jpeg(img, channels=3)
  img = tf.image.convert_image_dtype(img, tf.float32)

  hr = tf.image.random_crop(img, [50,50,3])
  lr = tf.image.resize(hr, [12, 12])
  lr = tf.image.resize(lr, [50,50])

  if random.random() < 0.25:
    hr = tf.image.flip_left_right(hr)
    lr = tf.image.flip_left_right(lr)
  if random.random() < 0.25:
    hr = tf.image.flip_up_down(hr)
    lr = tf.image.flip_up_down(lr)

  return lr, hr 
  • 앞부분은 축소/확대 비율을 2배에서 4배로 바꾸는 부분을 제외하면 get_hr_and_lr() 함수와 같고, 뒤쪽에서 25%의 확률로 좌우 반전, 또 25%의 확률로 상하 반전을 시켜줍니다.
  • 결과적으로 Dataset을 다시 정의하고 바뀐 함수를 적용합니다.

(12) REDNet-30 네트워크 학습

train_dataset = tf.data.Dataset.list_files(train_path)
train_dataset = train_dataset.map(get_hr_and_lr_flip_s4)
train_dataset = train_dataset.repeat()
train_dataset = train_dataset.batch(16)

valid_dataset = tf.data.Dataset.list_files(valid_path)
valid_dataset = valid_dataset.map(get_hr_and_lr_flip_s4)
valid_dataset = valid_dataset.repeat()
valid_dataset = valid_dataset.batch(1)

model = REDNet(15)
model.compile(optimizer=tf.optimizers.Adam(0.0001), loss='mse', metrics=[psnr_metric])

history = model.fit_generator(train_dataset, 
                              epochs=4000, 
                              steps_per_epoch=len(train_path)//16, 
                              validation_data=valid_dataset, 
                              validation_steps=len(valid_path), 
                              verbose=2)
12/12 - 3s - loss: 0.0046 - psnr_metric: 27.3362 - val_loss: 0.0050 - val_psnr_metric: 27.4308
Epoch 3548/4000
12/12 - 3s - loss: 0.0046 - psnr_metric: 26.8918 - val_loss: 0.0045 - val_psnr_metric: 26.7091
.
.
.
Epoch 3999/4000
12/12 - 3s - loss: 0.0048 - psnr_metric: 26.8295 - val_loss: 0.0055 - val_psnr_metric: 25.5176
Epoch 4000/4000
12/12 - 3s - loss: 0.0046 - psnr_metric: 27.5956 - val_loss: 0.0048 - val_psnr_metric: 27.2664
  • 이미지 보강으로 데이터가 4배 정도 늘어난 것과 같은 효과이기 때문에 에포크를 1,000에서 4,000으로 늘렸씁니다.
  • Set5의 나비 이미지를 테스트해보면 다음과 같은 결과가 나옵니다.

(13) 학습 결과 확인 및 Set5 이미지 시각화

import matplotlib.pyplot as plt
plt.plot(history.history['psnr_metric'], 'b-', label='psnr')
plt.plot(history.history['val_psnr_metric'], 'r--', label='val_psnr')
plt.xlabel('Epoch')
plt.legend()
plt.show()

png

image_path = tf.keras.utils.get_file('butterfly.png', 'http://bit.ly/2oAOxgH')
img = tf.io.read_file(image_path)
img = tf.image.decode_jpeg(img, channels=3)
hr = tf.image.convert_image_dtype(img, tf.float32)

lr = tf.image.resize(hr, [hr.shape[0]//4, hr.shape[1]//4])
lr = tf.image.resize(lr, [hr.shape[0], hr.shape[1]])
predict_hr = model.predict(np.expand_dims(lr, axis=0))

print(tf.image.psnr(np.squeeze(predict_hr, axis=0), hr, max_val=1.0))
print(tf.image.psnr(lr, hr, max_val=1.0))


plt.figure(figsize=(16,6))
plt.subplot(1, 3, 1)
plt.imshow(hr)
plt.title('original - hr')

plt.subplot(1, 3, 2)
plt.imshow(lr)
plt.title('lr')

plt.subplot(1, 3, 3)
plt.imshow(np.squeeze(predict_hr, axis=0))
plt.title('sr')
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).


tf.Tensor(22.349665, shape=(), dtype=float32)
tf.Tensor(20.217585, shape=(), dtype=float32)





Text(0.5, 1.0, 'sr')

png

  • 이미지를 불러온 다음 각 이미지에 대한 저해상도 버전과 복원 이미지의 PSNR 점수를 구해서 평균을 비교합니다.
image_path = tf.keras.utils.get_file('/content/Set5.zip', 'http://bit.ly/2MEG4kr')
!unzip Set5.zip
Downloading data from http://bit.ly/2MEG4kr
860160/852576 [==============================] - 0s 0us/step
Archive:  Set5.zip
   creating: Set5/
 extracting: Set5/baby.png           
 extracting: Set5/bird.png           
 extracting: Set5/butterfly.png      
 extracting: Set5/head.png           
  inflating: Set5/woman.png          
set5_image_root = pathlib.Path('/content/Set5')
set5_image_paths = list(set5_image_root.glob('*.*'))

sr_psnr = []
lr_psnr = []

for image_path in set5_image_paths:
    img = tf.io.read_file(str(image_path))
    img = tf.image.decode_jpeg(img, channels=3)
    hr = tf.image.convert_image_dtype(img, tf.float32)

    lr = tf.image.resize(hr, [hr.shape[0]//4, hr.shape[1]//4])
    lr = tf.image.resize(lr, [hr.shape[0], hr.shape[1]])
    predict_hr = model.predict(np.expand_dims(lr, axis=0))
    
    sr_psnr.append(tf.image.psnr(np.squeeze(predict_hr, axis=0), hr, max_val=1.0).numpy())
    lr_psnr.append(tf.image.psnr(lr, hr, max_val=1.0).numpy())
    
print('sr:', sr_psnr)
print('sr mean:', np.mean(sr_psnr))
print()
print('lr:', lr_psnr)
print('lr mean:', np.mean(lr_psnr))
WARNING:tensorflow:5 out of the last 5 calls to <function Model.make_predict_function.<locals>.predict_function at 0x7fc446f63158> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings is likely due to passing python objects instead of tensors. Also, tf.function has experimental_relax_shapes=True option that relaxes argument shapes that can avoid unnecessary retracing. Please refer to https://www.tensorflow.org/tutorials/customization/performance#python_or_tensor_args and https://www.tensorflow.org/api_docs/python/tf/function for more details.
sr: [28.628256, 22.349665, 25.808668, 27.925306, 30.419857]
sr mean: 27.026352

lr: [28.569517, 20.217585, 24.495182, 27.31659, 29.72251]
lr mean: 26.064276
  • 복원 이미지의 PSNR 점수가 저해상도 버전보다 전반적으로 높게 나타나는 것을 확인할 수 있습니다.
  • 4배 확대 초해상도에 대한 벤치마크 사이트에서 REDNet-30Set5 이미지에 대해 최고 31.51점의 PSNR점수를 기록하고 있습니다.
  • 학습을 시킬수록 PSNR 점수는 증가하는 경향을 보이기 때문에 과적합되지 않을 정도로 충분한 에포크 동안 학습시키고 입력 이미지를 다양하게 만들어서 더 좋은 PSNR점수를 얻을 수 있을 겁니다.

IV. 연습 파일

V. Reference

김환희. (2020). 시작하세요! 텐서플로 2.0 프로그래밍: 기초 이론부터 실전 예제까지 한번에 끝내는 머신러닝, 딥러닝 핵심 가이드. 서울: 위키북스.


  1. X. Mao, C. Shen, and Y. Yang. (2016, September 1). Image Restoration Using Very Deep Convolutional Encoder-Decoder Networks with Symmetric Skip Connections. Retrieved from https://arxiv.org/abs/1603.09056 ↩︎