1.はじめに
ディープラーニングで写真から油絵を作り出す技術は以前からありますが、今回ご紹介するのは、その作成プロセスに焦点を当てたものです。
*この論文は、2020.11に提出されました。
2.Stylized Neural Paintingとは?
従来からあるピクセル単位で画像変換する方法とは異なり、ベクトル化されたデータを使って順次レンダリングするという、まるで筆で絵具を重ね塗りするような方法で油絵へ変換します。
diffrentiable painting pipeline を使って、空のキャンバスに soft blending でベクトルデータを順次レンダリングし、レンダリング毎に元画像との差を最小化するようにパラメータの最適化を行います。ここで、黒い矢印は順伝搬、赤い矢印は勾配の逆伝搬を意味します。
パラメータは、dual-pathway neural render で、Color(色), Shade(ぼかし), Alpha(透明度)の要素から Stroke と Alpha matto が生成されます。
3.コード
コードはGoogle Colabで動かす形にしてGithubに上げてありますので、それに沿って説明して行きます。自分で動かしてみたい方は、この「リンク」をクリックし表示されたノートブックの先頭にある「Colab on Web」ボタンをクリックすると動かせます。
まず、Githubからコードをコピーし、学習済みモデルをダウンロードします。詳細は、Google Colab のノートブックを参照下さい。
初期設定と関数定義の部分です。
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
import argparse import torch torch.cuda.current_device() import torch.optim as optim from painter import * # Decide which device we want to run on device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # settings parser = argparse.ArgumentParser(description='STYLIZED NEURAL PAINTING') args = parser.parse_args(args=[]) args.img_path = './test_images/kasumi.png' # path to input photo args.renderer = 'oilpaintbrush' # [watercolor, markerpen, oilpaintbrush, rectangle] args.canvas_color = 'black' # [black, white] args.canvas_size = 512 # size of the canvas for stroke rendering' args.max_m_strokes = 500 # max number of strokes args.max_divide = 5 # divide an image up-to max_divide x max_divide patches args.beta_L1 = 1.0 # weight for L1 loss args.with_ot_loss = False # set True for imporving the convergence by using optimal transportation loss, but will slow-down the speed args.beta_ot = 0.1 # weight for optimal transportation loss args.net_G = 'zou-fusion-net' # renderer architecture args.renderer_checkpoint_dir = './checkpoints_G_oilpaintbrush' # dir to load the pretrained neu-renderer args.lr = 0.005 # learning rate for stroke searching args.output_dir = './output' # dir to save painting results def _drawing_step_states(pt): acc = pt._compute_acc().item() print('iteration step %d, G_loss: %.5f, step_acc: %.5f, grid_scale: %d / %d, strokes: %d / %d' % (pt.step_id, pt.G_loss.item(), acc, pt.m_grid, pt.max_divide, pt.anchor_id, pt.m_strokes_per_block)) vis2 = utils.patches2img(pt.G_final_pred_canvas, pt.m_grid).clip(min=0, max=1) def optimize_x(pt): pt._load_checkpoint() pt.net_G.eval() print('begin drawing...') PARAMS = np.zeros([1, 0, pt.rderr.d], np.float32) if pt.rderr.canvas_color == 'white': CANVAS_tmp = torch.ones([1, 3, 128, 128]).to(device) else: CANVAS_tmp = torch.zeros([1, 3, 128, 128]).to(device) for pt.m_grid in range(1, pt.max_divide + 1): pt.img_batch = utils.img2patches(pt.img_, pt.m_grid).to(device) pt.G_final_pred_canvas = CANVAS_tmp pt.initialize_params() pt.x_ctt.requires_grad = True pt.x_color.requires_grad = True pt.x_alpha.requires_grad = True utils.set_requires_grad(pt.net_G, False) pt.optimizer_x = optim.RMSprop([pt.x_ctt, pt.x_color, pt.x_alpha], lr=pt.lr, centered=True) pt.step_id = 0 for pt.anchor_id in range(0, pt.m_strokes_per_block): pt.stroke_sampler(pt.anchor_id) iters_per_stroke = 80 for i in range(iters_per_stroke): pt.G_pred_canvas = CANVAS_tmp # update x pt.optimizer_x.zero_grad() pt.x_ctt.data = torch.clamp(pt.x_ctt.data, 0.1, 1 - 0.1) pt.x_color.data = torch.clamp(pt.x_color.data, 0, 1) pt.x_alpha.data = torch.clamp(pt.x_alpha.data, 0, 1) pt._forward_pass() _drawing_step_states(pt) pt._backward_x() pt.x_ctt.data = torch.clamp(pt.x_ctt.data, 0.1, 1 - 0.1) pt.x_color.data = torch.clamp(pt.x_color.data, 0, 1) pt.x_alpha.data = torch.clamp(pt.x_alpha.data, 0, 1) pt.optimizer_x.step() pt.step_id += 1 v = pt._normalize_strokes(pt.x) PARAMS = np.concatenate([PARAMS, np.reshape(v, [1, -1, pt.rderr.d])], axis=1) CANVAS_tmp = pt._render(PARAMS)[-1] CANVAS_tmp = utils.img2patches(CANVAS_tmp, pt.m_grid + 1, to_tensor=True).to(device) pt._save_stroke_params(PARAMS) pt.final_rendered_images = pt._render(PARAMS) pt._save_rendered_images() |
13行目の args.img_path = ‘./test_images/kasumi.png’ で油絵化する画像を指定しています。別の画像の油絵化をする場合は、ここを変更して下さい。
下記コードで、レンダリングを開始します。約10分ほど掛かりますので、コーヒーでも飲んでお待ちください。
1 2 |
pt = ProgressivePainter(args=args) optimize_x(pt) |
レンダリングが終了したら、生成した画像を確認してみましょう。下記のコードで、対象画像(input)と生成画像(generated)を表示します。
1 2 3 4 5 6 7 |
# show picture fig = plt.figure(figsize=(8,4)) plt.subplot(1,2,1) plt.imshow(pt.img_), plt.title('input') plt.subplot(1,2,2) plt.imshow(pt.final_rendered_images[-1]), plt.title('generated') plt.show() |
下記のコードで、レンダリングの経過をアニメーション化します。interval=100は、レンダリング画面を0.1秒で切り替える設定です。
1 2 3 4 5 6 7 8 9 10 |
# make animation import matplotlib.animation as animation from IPython.display import HTML fig = plt.figure(figsize=(8,8)) plt.axis('off') ims = [[plt.imshow(img, animated=True)] for img in pt.final_rendered_images[::10]] ani = animation.ArtistAnimation(fig, ims, interval=100) HTML(ani.to_jshtml()) |
まるで、ディープラーニングがナイフや筆を使って油絵を描いているようですね。
では、また。
(オリジナル)https://github.com/jiupinjia/stylized-neural-painting