動画を元にDCGANで高木さんを描いてみた
にゃんぱすー\(๑¯Δ¯๑)
動画ファイルを基にopencvやDCGANなど使って、128×128のカラー画像生成をしてみました。すでにDCGANや各技術は多数の記事が出ていますが、メモとして記載。
これまで、本で機械学習系のことを読んでたので、試しにkerasでDCGANを初実装してみました。機械学習の入門書として、
・ゼロから作るDeep Learning (斎藤 康毅 著)
が分かりやすく、それを読んだ後であれば、
・現場で使えるTensorFlow開発入門 (機械学習の知識ゼロ状態からだと、できても意味わからないと思います)
がkeras機械学習実装入門として分かりやすかったです。
GANというのは機械学習のネットワークで次の絵のように、ノイズなどを入力として本物に似たデータ(画像など)を生成するGenerator[G]モデルと、本物かGeneratorが作った偽物かを判断するDiscriminator[D]モデル、の2つの別々のモデルから成りたっています。
GeneratorはDiscriminatorが本物であると判断してしまうような本物に似たデータを作成しようとし、
Discriminatorは入力されたデータ(画像など)を本物か、Generatorが作った偽物かを正しく判断しようとします。
GeneratorとDiscriminatorを交互(もしくは制限を加え)に学習していくことで互いに、Generatorはより本物に似たデータを作れるようになり、Discriminatorは本物であるか偽物であるかより正確に判断できる識別器となっていきます。
GANの分かりやすい情報として、既にいろいろネットに上がっています。
今さら聞けないGAN(1) 基本構造の理解 - Qiita
GANについて概念から実装まで ~DCGANによるキルミーベイベー生成~ - Qiita
はじめてのGAN
DCGAN(元論文 https://arxiv.org/abs/1511.06434 )はこのGANのネットワークの部分に画像機械学習で大成功しているCNNを用いたものです。さらに、よりよく収束するために、
・プーリング層をストライド2のConvolution層に変更
・全結合層を使わない
・BatchNormalizationを多用
・GeneratorにReLU, DiscriminatorにLeakyReLUを多用
しています。
今回行ったことの全体の流れとしては、
動画データ
↓
フリー動画編集ソフトAviutlで間引き&bmp化
↓
pythonでjpg化&同一画像削除
↓
openCVで顔認識&顔画像作成
↓
作成した顔画像データを基にDCGANで模擬高木さん作成
をしました。
まずは動画編集ソフトAviutlでbmp化しました。
方法は「Aviutl 連番BMP出力」とかでググればでてくるプラグインを入れてエクスポートすればできます。
Aviutlはフリーでプラグインがたくさんあり、動画の編集や形式変更などいろいろできるのでとても便利です。
次にpythonで同一画像の削除と、bmpのままだと容量が大きすぎるのでjpg化をしました。(↓がコード, 動画画像の順番と画像名の順番ほぼ同じになっているの想定)
import cv2 import glob import os import numpy as np from keras.preprocessing.image import load_img, array_to_img,img_to_array from PIL import Image filename=glob.glob('D:\高木さん\新しいフォルダー/test9*.bmp') num1=1 img = Image.open(filename[0]) imgArray = img_to_array(img) Abef=imgArray for F in filename: print(F) img = Image.open(F) imgArray = img_to_array(img) print(type(imgArray)) Aaft=imgArray print('aft') dif = Aaft - Abef Abef=imgArray print(np.sum(dif,axis=None)) if abs(np.sum(dif,axis=None)) > 500000: imgImage = array_to_img(imgArray) #print(img.format) imgname=('data/takagisan3/jpg_'+os.path.basename(F)[0:-3]+'jpg') print(imgname) img.save(imgname)
↓作ったjpgファイルからpython opencvを使って顔抽出&正方形に近い形状のデータだけjpgとして保存。
基本的には(OpenCVでアニメの顔検出 - Qiita)をそのままで若干だけ変更しました。
import os import cv2 import glob from keras.preprocessing.image import load_img, array_to_img,img_to_array # 特徴量ファイルをもとに分類器を作成 classifier = cv2.CascadeClassifier('D:\python\lbpcascade_animeface-master\lbpcascade_animeface.xml') filename=glob.glob('D:\\python\\jupyter_notebook\\data\\takagisan3\\*') for F in filename: image = cv2.imread(F) gray_image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) faces = classifier.detectMultiScale(gray_image) if len(faces) > 0: # ディレクトリを作成 output_dir = 'faces' if not os.path.exists(output_dir): os.makedirs(output_dir) for i, (x,y,w,h) in enumerate(faces): # 一人ずつ顔を切り抜く face_image = image[y-int(0.2*h):y+int(1.2*h), x-int(0.2*w):x+int(1.2*w)] if (face_image.shape[0]/(face_image.shape[1]+0.1) > 0.7 and face_image.shape[0]/(face_image.shape[1]+0.1) < 1.3): #正方形に近いものだけ保存 output_path = os.path.join(output_dir,os.path.basename(F)[0:-4]+'{0:03d}.jpg'.format(i)) cv2.imwrite(output_path,face_image)
4時間の動画からおよそ33000枚の顔と認識された画像ができました!
内訳を見てみると、顔以外の画像や使えなさそうな荒い画像、目だけの画像などが多数含まれており3時間ほどかけて手作業で除去しました。
除去結果、大体19000枚の学習用顔データができました♪
ということでDCGANの論文に則り、kerasでプログラム作成をしました。
DCGANの論文は事細かに構造が書かれており、再現したつもりが急激に学習が進みパラメータ調整してもうまく動きませんでした。
ということで、何が原因かよくわからなかったので、キルミーベイベー画像を再現していた良記事(
GANについて概念から実装まで ~DCGANによるキルミーベイベー生成~ - Qiita
)のコードをもとにDCGANを作成しました。結局、なんだかんだで元記事のモデル構造を使わさせていただきました。
↓作ったコード
import os import glob import math import random import numpy as np import matplotlib.pyplot as plt from tensorflow.python import keras from tensorflow.python.keras.optimizers import Adam from tensorflow.python.keras.models import Model, Sequential from tensorflow.python.keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array, array_to_img from tensorflow.python.keras.layers import LeakyReLU,Dropout,ZeroPadding2D from tensorflow.python.keras.layers import Activation, BatchNormalization, Reshape, Flatten, Add, Input, Conv2D, Conv2DTranspose, Dense, Input, MaxPooling2D, UpSampling2D, Lambda data_dir = 'D:/python/jupyter_notebook/data/tmp/' batch_size = 128 img_shape = (128,128,3) gen = ImageDataGenerator(rescale=1/127.5, samplewise_center=True) iters = gen.flow_from_directory( directory=data_dir, classes=['faces'], class_mode=None, color_mode='rgb', target_size=img_shape[:2], batch_size=batch_size, shuffle=True ) x_train_batch=next(iters) for i in range(3): display(array_to_img(x_train_batch[i])) def generator_model(): model = Sequential() model.add(Dense(128 * 32 * 32, activation="relu", input_shape=(100,))) model.add(Reshape((32, 32, 128))) model.add(BatchNormalization(momentum=0.8)) model.add(UpSampling2D()) model.add(Conv2D(128, kernel_size=3, padding="same")) model.add(Activation("relu")) model.add(BatchNormalization(momentum=0.8)) model.add(UpSampling2D()) model.add(Conv2D(64, kernel_size=3, padding="same")) model.add(Activation("relu")) model.add(BatchNormalization(momentum=0.8)) model.add(Conv2D(3, kernel_size=3, padding="same")) model.add(Activation("tanh")) noise = Input(shape=(100,)) img = model(noise) return Model(noise, img) def discriminator_model(img_shape): model = Sequential() model.add(Conv2D(32, kernel_size=3, strides=2, input_shape=img_shape, padding="same")) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(Conv2D(64, kernel_size=3, strides=2, padding="same")) model.add(ZeroPadding2D(padding=((0, 1), (0, 1)))) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(BatchNormalization(momentum=0.8)) model.add(Conv2D(128, kernel_size=3, strides=2, padding="same")) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(BatchNormalization(momentum=0.8)) model.add(Conv2D(256, kernel_size=3, strides=1, padding="same")) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(1, activation='sigmoid')) img = Input(shape=img_shape) validity = model(img) return Model(img, validity) optimizer = Adam(lr=0.0002, beta_1=0.5) generator = generator_model() discriminator = discriminator_model(img_shape) discriminator.trainable = True generator.compile(loss='binary_crossentropy', optimizer=optimizer) discriminator.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy']) z = Input(shape=(100,)) img = generator(z) discriminator.trainable = False valid = discriminator(img) generator_containing_discriminator = Model(z, valid) generator_containing_discriminator.compile(loss='binary_crossentropy', optimizer=optimizer) #saveディレクトリ model_save_dir = 'dcgan/models' img_save_dir = 'dcgan/imgs' #5×5生成画像生成 imgs_shape = (5, 5) n_img_samples = np.prod(imgs_shape) sample_seeds = np.random.uniform(-1, 1, (n_img_samples, 100)) hist = [] logs = [] #保存先フォルダ生成 os.makedirs(model_save_dir, exist_ok=True) os.makedirs(img_save_dir, exist_ok=True) def save_imgs(path, imgs, rows, cols): base_width = imgs.shape[1] base_height = imgs.shape[2] channels = imgs.shape[3] output_shape = ( base_height*rows, base_width*cols, channels ) buffer = np.zeros(output_shape) for row in range(rows): for col in range(cols): img = imgs[row*cols + col] buffer[ row*base_height:(row + 1)*base_height, col*base_width:(col + 1)*base_width ] = img array_to_img(buffer).save(path) Totalstep=500000 for step, batch in enumerate(iters): if len(batch) < batch_size: continue if step > Totalstep: break half_batch = int(batch_size / 2) z_d = np.random.uniform(-1, 1, (half_batch, 100)) g_pred = generator.predict(z_d) discriminator.trainable = True real_loss=discriminator.train_on_batch(batch[:half_batch], np.ones((half_batch, 1))) fake_loss=discriminator.train_on_batch(g_pred, np.zeros((half_batch, 1))) d_loss = 0.5 * np.add(real_loss, fake_loss) z_g = np.random.uniform(-1, 1, (batch_size, 100)) discriminator.trainable = False g_loss = generator_containing_discriminator.train_on_batch(z_g, np.ones((batch_size, 1))) logs.append({'step': step, 'd_loss': d_loss[0], 'acc[%]': 100*d_loss[1] , 'g_loss': g_loss}) print(logs[-1]) if step%200==0: img_path = '{}/generate_{}.png'.format(img_save_dir, step) save_imgs(img_path, generator.predict(sample_seeds), rows=imgs_shape[0], cols=imgs_shape[1]) if step%5000==0: print(step) generator.save('{}/generator_{}.hd5'.format(model_save_dir, step)) discriminator.save('{}/discriminator_{}.hd5'.format(model_save_dir, step)) #↓推測時のみ用 Totalstep=30 generator.load_weights('D:/python/jupyter_notebook/dcgan/models/generator_25000.hd5') for step, batch in enumerate(iters): if len(batch) < batch_size: continue if step > Totalstep: break z_d = np.random.uniform(-1, 1, (batch_size, 100)) g_pred = generator.predict(z_d) img_path = '{}/pred_generate_{}.png'.format(img_save_dir, step) save_imgs(img_path, generator.predict(z_d), rows=imgs_shape[0], cols=imgs_shape[1])
結果
gifで
ほにゃほにゃですが、雰囲気だけでもポくなりました(*´ω`*)
今回はお借りしまくった感じで、まだ前処理とかちゃんとしてないので、そのうちもっと精度上げたいところです(`・ω・´)ゞ