deeplearning

PytorchでStyleTransferを実装する

pytorchによるstyle Transfer

ある画像に画像のスタイル(画風)を適用した合成画像を生成するStyle TransferをPytorchで実装してみます。例のごとくGoogle Colaboratory(以下Colab)を使用しましょう。

PytorchでStyleTransferを実装して合成画像を生成する

この記事の完成品です。

この記事の完成品です

以下が元となった画像と画風の元となったゴッホの絵です。 画像はunsplashから頂きました。 鳥居の画像に上手くゴッホの画風が適用されています。

元となった画像と画風の元となったゴッホの絵

前準備

学習する前の処理を実装しましょう。

必要なライブラリとGoogleドライブのマウント

必要なライブラリをインポートしましょう。今回はGoogleドライブをマウントして、ドライブにある画像に対して学習してみます。

pytorchと画像を表示するためにPillowをインストールしましょう。

!pip install torch torchvision
!pip install Pillow==4.0.0

ライブラリをインポートします。

%matplotlib inline
import torch
import torch.optim as optim
from torchvision import transforms, models
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np

Googleドライブをマウントします。これでColabからドライブにアクセスすることができます。

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

モデルの定義

VGGを使います。deviceのgpuも定義しておきます。

vgg = models.vgg19(pretrained=True).features

for param in vgg.parameters():
  param.requires_grad_(False)
 
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
vgg.to(device)

画像を表示する関数を定義

画像をパスからロードしてTensorを返す関数を定義します。サイズを指定できるようにしています。

def load_image(img_path, max_size=400, shape=None):
  
  image = Image.open(img_path).convert('RGB')
  if max(image.size) > max_size:
    size = max_size
  else:
    size = max(image.size)
  
  if shape:
    size = shape
  in_transform = transforms.Compose([
                    transforms.Resize(size),
                    transforms.ToTensor(),
                                            ])
  
  image = in_transform(image).unsqueeze(0)
  
  return image
  

画像を読み込んで表示する

画像のパスを定義します。ここは便宜パスを設定してください。最終的な画像のもととなる画像をcontent_pathとして、画風のもととなる画像をstyle_pathとして定義しています。

images_path ='drive/My Drive/'
content_path = images_path + 'content.jpg'
style_path = images_path + 'style.jpg'

ロードしましょう。二つの画像のサイズを合わせています。

content = load_image(content_path).to(device)
style = load_image(style_path, shape=content.shape[-2:]).to(device)

Tensorからnumpyに変換して、画像を表示できるようにする関数を定義

def im_convert(tensor):
  #Tensorをnp.arrayに変換する
  image = tensor.to("cpu").clone().detach()
  image = image.numpy().squeeze()
  image = image.transpose(1,2,0)
  image = image * np.array((0.5, 0.5, 0.5) + np.array((0.5, 0.5, 0.5)))
  image = image.clip(0, 1)
  
  return image

Colab上に画像を表示します。

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10))
ax1.imshow(im_convert(content))
ax1.axis("off")
ax2.imshow(im_convert(style))
ax2.axis("off")

特徴を抽出する

CNNは大きく二つの層に分かれます。画像から特徴を抜き出す前半の層と、抜き出した特徴から画像を最終的に分類する後半の層です。

二つの画像の特徴をCNNで抽出し、その特徴の差を損失関数にして最終的な画像を学習させます。
そのために画像をCNNに入れて、特定の層で特徴を抽出しましょう。VGGの0番、5番、10番、19番、21番、28番目の層で抽出します。

まず関数の定義です。

def get_features(image, model):
#   Feature Extraction
#   特徴を抜き出すレイヤー
  layers = {'0': 'conv1_1',
            '5': 'conv2_1',
            '10': 'conv3_1',
            '19': 'conv4_1',
            '21': 'conv4_2',
            '28': 'conv5_1',}
  
  features = {}
  
  for name, layer in model._modules.items():
#     CNNを回して
    image = layer(image)
#   特定のレイヤーで特徴を抽出する
#  ここでは 0番 5番 10番 19番 21番 28番
    if name in layers:
      features[layers[name]] = image
      
  return features

特徴を抽出しましょう。

content_features = get_features(content, vgg)
style_features = get_features(style, vgg)

画風の元となる画像は単純に特徴を比較するのではなく、特徴のグラム行列を比較します。グラム行列を計算する関数を定義しましょう。

def gram_matrix(tensor):
  #   グラム行列を計算する
  _, d, h, w = tensor.size()
  tensor = tensor.view(d, h * w)
#   転置行列と行列の掛け算
  gram = torch.mm(tensor, tensor.t())
  
  return gram

スタイルのグラム行列を保持する

style_grams = {layer: gram_matrix(style_features[layer]) for layer in style_features}

比較する層の重みを設定します。

style_weights = {'conv1_1':1.,
                 'conv2_1':0.75,
                 'conv3_1':0.2,
                 'conv4_1':0.2,
                 'conv5_1':0.2}

content_weight = 1 #alpha
style_weight = 1e6 #blue

最終的な画像はcontentの画像をコピーして学習させていくのでコピーし、targetとして定義します。

target = content.clone().requires_grad_(True).to(device)

学習

準備は完了です。学習させていきましょう!途中経過を記録して後で動画にできるようにしておきます。 ハイパーパラメータを設定しましょう。

show_every = 300
optimizer = optim.Adam([target], lr=0.003)
steps = 10000
total_capture_frame_number = 500

height, width, channels = im_convert(target).shape
image_array = np.empty(shape=(total_capture_frame_number, height, width, channels))
capture_frame =steps/total_capture_frame_number
counter = 0

では学習です。

for ii in range(1, steps+1):
  target_features = get_features(target, vgg)
#   コンテンツとの損失関数の計算
  content_loss = torch.mean((target_features['conv4_2'] - content_features['conv4_2'])**2)
#   スタイルとの損失関数の計算
  style_loss = 0
  
  for layer in style_weights:
    target_feature = target_features[layer]
    target_gram = gram_matrix(target_feature)
    style_gram = style_grams[layer]
#     重み * 二乗和誤差
    layer_style_loss = style_weights[layer] * torch.mean((target_gram - style_gram)**2)
    _, d, h, w = target_feature.shape
    style_loss += layer_style_loss / (d * h * w)
  
  # トータルの損失関数
  total_loss = content_weight * content_loss + style_weight * style_loss
  
  optimizer.zero_grad()
  total_loss.backward()
  optimizer.step()
  
  #経過観察
  if ii % show_every == 0:
    print('Total loss: ', total_loss.item())
    print('Iteration: ', ii)
    plt.imshow(im_convert(target))
    plt.axis("off")
    plt.show()
    
#   動画用に保管
  if ii % capture_frame == 0:
    image_array[counter] = im_convert(target)
    counter = counter + 1

少し時間がかかると思います。

600回目

5100回目

9900回目

きちんと学習できています。

動画を書き出す

OpenCVを使って動画を作成します。

import cv2

frame_height, frame_width, _ = im_convert(target).shape
vid = cv2.VideoWriter('output.mp4', cv2.VideoWriter_fourcc(*'XVID'), 30, (frame_width, frame_height))

for i in range(total_capture_frame_number):
  img = image_array[i]
  img = img*255
  img = np.array(img, dtype = np.uint8)
  img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  vid.write(img)
  
vid.release()

ダウンロードしましょう。

from google.colab import files
files.download('output.mp4')

動画を再生すると学習過程を見ることができます。

おわりに

今回はPytorchを使ってStyle Transferを実装してみました。Google ColaboratoryのGPUを使えば簡単に試すことができます。Googleドライブの中に保存するようにすればColabのインスタンスがなくなっても大丈夫です。是非、色々他の画像を試してみてください。

Sponsored Link

-deeplearning