アートという自己表現で食べていくのって個人的にとても憧れがあります。アーティストになりたい。
コンピュータビジョンのアート領域の研究に画風変換というものがあり、楽に試すことができましたので、メモを残しておこうと思います。
・画像を用意する
・山男を画風変換する
・GIFアニメーションを作成する
・セグメンテーションを活用して、背景だけ画風変換する
画像を用意する
こちらから画像を取得します
https://unsplash.com/photos/-87JyMb9ZfU
画像サイズが大きいので、1200 × 800 にリサイズをしておきます。
今回は Google Colab でやりましたので、colabを開いて、sample_mountain.jpg という名前でアップロードをします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import os import numpy as np import matplotlib.pyplot as plt from glob import glob from PIL import Image import cv2 import torch import torch.nn.functional as F import torchvision from torchvision import models, transforms # 画像の読み込み img = Image.open("sample_mountain.jpg") img |
画像のサイズの情報も後で使うため取得しておきます。
1 2 3 4 5 6 7 8 |
h1, w1 = np.array(img).shape[0], np.array(img).shape[1] # h2, w2 = int(h1/3), int(w1/3) h1, w1 ''' (800, 1200) ''' |
画像サイズが大きすぎた場合は、1/2や1/3にしてやります。
山男を画風変換する
はじめに、ColabでどのGPUが割り当てられているか確認をしておきます。
Tesla K80 が多いですが、K80だとこのサイズの画像で処理を回そうとするとメモリが足らなくて処理が走らないことがあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
!nvidia-smi ''' Fri Jul 24 03:37:15 2020 +-----------------------------------------------------------------------------+ | NVIDIA-SMI 450.51.05 Driver Version: 418.67 CUDA Version: 10.1 | |-------------------------------+----------------------+----------------------+ | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |===============================+======================+======================| | 0 Tesla P100-PCIE... Off | 00000000:00:04.0 Off | 0 | | N/A 36C P0 25W / 250W | 0MiB / 16280MiB | 0% Default | | | | ERR! | +-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+ | Processes: | | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=============================================================================| | No running processes found | +-----------------------------------------------------------------------------+ ''' |
P100 でした。K80の場合でうまく走らなかったら、画像サイズを小さくするか、GPUガチャを回しましょう。Colab Pro の日本版が待ち遠しいです。
こちらのレポジトリを使わせてもらいます。https://github.com/titu1994/Neural-Style-Transfer
画風変換のアルゴリズムは少しだけ調べたところ以下のようなものがあるようです。
・任意のスタイル画像で変換できるが、1枚ごとに学習を行うため時間がかかる
・スタイル画像は学習済みのもの以外は使用できないが、変換(推論)は早い
GANを使うものはまだ調べてないですが、どんな感じなんだろう。
今回は、前者の任意のスタイル画像を使えるものを試します。
1 |
!git clone https://github.com/titu1994/Neural-Style-Transfer.git |
ハイパーパラメータを設定します。vgg19の中間の様々な特徴をとってきて、content loss と style loss を計算して、トータルのロスが小さくなるように学習していくようです。Max Pooling でなく Average Poolingにするとよくなるといった研究もあるそうで、POOLING_TYPEで選択できるみたいです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
dir_path = "Neural-Style-Transfer" # Image size IMAGE_SIZE = h1 # Loss Weights CONTENT_WEIGHT = 0.025 STYLE_WEIGHT = 1.0 STYLE_SCALE = 1.0 TOTAL_VARIATION_WEIGHT = 8.5e-5 CONTENT_LOSS_TYPE = 0 # Training arguments NUM_ITERATIONS = 20 MODEL = 'vgg19' RESCALE_IMAGE = 'false' MAINTAIN_ASPECT_RATIO = 'false' # Set to false if OOM occurs # Transfer Arguments CONTENT_LAYER = 'conv' + '5_2' # only change the number 5_2 to something in a similar format INITIALIZATION_IMAGE = 'content' POOLING_TYPE = 'max' # Extra arguments PRESERVE_COLOR = 'false' MIN_IMPROVEMENT = 0.0 |
CONETNT_IMAGE(変換したい画像)と STYLE_IMAGE(スタイル画像)のパスを設定します。スタイル画像は、レポジトリ内に用意されていた candy-style というものを使用します。レポジトリ内の他の選択肢としては、ここ を見るとよいです。また、wikiart のページでは様々な有名な絵画を見ることができます。
1 2 3 4 5 6 7 8 9 10 11 |
CONTENT_IMAGE_FN = "sample_mountain.jpg" STYLE_IMAGE_FN = "/content/Neural-Style-Transfer/images/inputs/style/candy-style.jpg" NETWORK = 'INetwork' + '.py' # 出力のパス等の設定 RESULT_DIR = "generated/" RESULT_PREFIX = RESULT_DIR + "gen" FINAL_IMAGE_PATH = RESULT_PREFIX + "_at_iteration_%d.png" % (NUM_ITERATIONS) if not os.path.exists(RESULT_DIR): os.makedirs(RESULT_DIR) |
変換は、.pyスクリプト実行すると開始します。
1 2 3 4 5 6 7 8 |
!python {dir_path}/{NETWORK} {CONTENT_IMAGE_FN} {STYLE_IMAGE_FN} {RESULT_PREFIX} \ --image_size {IMAGE_SIZE} --content_weight {CONTENT_WEIGHT} --style_weight \ {STYLE_WEIGHT} --style_scale {STYLE_SCALE} --total_variation_weight \ {TOTAL_VARIATION_WEIGHT} --content_loss_type {CONTENT_LOSS_TYPE} --num_iter \ {NUM_ITERATIONS} --model {MODEL} --rescale_image {RESCALE_IMAGE} \ --maintain_aspect_ratio {MAINTAIN_ASPECT_RATIO} --content_layer {CONTENT_LAYER} \ --init_image {INITIALIZATION_IMAGE} --pool_type {POOLING_TYPE} --preserve_color \ {PRESERVE_COLOR} --min_improvement {MIN_IMPROVEMENT} |
これで、generated フォルダ内に、各毎の変換pngファイルが格納されます。
今回は10イテレーション回しているので、10個出てきます。
一番最後のものだけ確認します。
1 2 3 4 5 |
fig = plt.figure(figsize=(10, 10)) transformed_img = plt.imread(FINAL_IMAGE_PATH) plt.axis('off') plt.title('Generated image') plt.imshow(transformed_img) |
異次元の空間に紛れ込んだ感じになりました。
GIFアニメーションを作成する
せっかくなので、変換過程のGIFアニメーションを作成してみようと思います。
generated フォルダ内に 10 枚の変換過程の画像があります。
sample_mountain.jpg を generated フォルダに移動し、ソートをかけて正しい順番になるように名前を調整します(01_~~, 02_~~ みたいにします)
1 2 3 4 |
files = sorted(glob("generated/*")) images = list(map(lambda file: Image.open(file), files)) images[0].save("output.gif", save_all=True, append_images=images[1:], duration=400) |
参考:https://qiita.com/hiro9/items/222bea3e77dfed235f0a
元画像は jpg 拡張子ですが、変換後画像は png ですので、*.png としないように注意します。

セグメンテーションを活用して、背景だけ画風変換する
このレポジトリでは、マスク画像を用意してあげるとマスクされた部分だけ画風変換する、なんていうこともできるようです。
というわけで、人の部分と背景の部分でわけたマスク画像を取得して試してみたいと思います。
torchvision の 学習済み deeplabv3 を持ってきてマスク画像を取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
transform = transforms.Compose([ #transforms.Resize((h2, w2)), transforms.ToTensor(), ]) inputs = transform(img) inputs = inputs.unsqueeze(0) #推論 model = models.segmentation.deeplabv3_resnet101(pretrained=True, num_classes=21) model.eval() pred = model(inputs)["out"] # pred = F.upsample(pred, size=(h1, w1), mode="bilinear") mask = torch.argmax(pred[0], 0) np.unique(mask) # 0 : background , 15 : person ''' array([ 0, 15]) ''' |
できたものについて、2値化を行います。
1 2 3 4 5 6 7 8 9 10 |
res1 = np.where(mask == 0, 255, 0) res2 = np.where(mask == 0, 0, 255) fig, ax = plt.subplots(1, 2, figsize=(8, 6)) ax[0].imshow(res1, cmap="gray") ax[1].imshow(res2, cmap="gray") # 保存 cv2.imwrite("mask_person.jpg", res1) cv2.imwrite("mask_back.jpg", res2) |
mask_person.jpg : 人が0, 背景が255 のマスク画像
mask_back.jpg:人が255, 背景が0 のマスク画像
人だけ変換、背景だけ変換、と両パターンためせるように2種類マスク画像を用意しておきます。
変換は、レポジトリで用意されている、 mask_transfer.py で実行できます。
1 2 3 4 5 |
# mask_back を使用 !python3 Neural-Style-Transfer/mask_transfer.py "sample_mountain.jpg" "generated/gen_at_iteration_10.png" "mask_back.jpg" # mask_person を使用 !python3 Neural-Style-Transfer/mask_transfer.py "sample_mountain.jpg" "generated/gen_at_iteration_10.png" "mask_person.jpg" |
ImportError: cannot import name ‘imread’
と出てきたら、scipy のバージョンの問題なので、 !pip install scipy==1.1.0
としてから実行し直します。
generated フォルダ内に画像が出力されます。


なんだかちぐはぐな感じ担ってしまいました。センスが欲しい。
最後まで読んで頂きありがとうございました。