ふぎのモノづくりにっき

痛車乗りのモノづくり日記。いろいろ試したり、作ったりするのを載せる予定だぞいッ

動画を元にDCGANで高木さんを描いてみた

f:id:nyanpasuAxela:20180603172745j:plain

にゃんぱすー\(๑¯Δ¯๑)

動画ファイルを基にopencvやDCGANなど使って、128×128のカラー画像生成をしてみました。すでにDCGANや各技術は多数の記事が出ていますが、メモとして記載。

これまで、本で機械学習系のことを読んでたので、試しにkerasでDCGANを初実装してみました。機械学習の入門書として、

・ゼロから作るDeep Learning (斎藤 康毅 著)

が分かりやすく、それを読んだ後であれば、

・現場で使えるTensorFlow開発入門 (機械学習の知識ゼロ状態からだと、できても意味わからないと思います)

がkeras機械学習実装入門として分かりやすかったです。

 GANというのは機械学習のネットワークで次の絵のように、ノイズなどを入力として本物に似たデータ(画像など)を生成するGenerator[G]モデルと、本物かGeneratorが作った偽物かを判断するDiscriminator[D]モデル、の2つの別々のモデルから成りたっています。

f:id:nyanpasuAxela:20180603181600p:plain

 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化しました。

f:id:nyanpasuAxela:20180603185158j:plain

方法は「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)

f:id:nyanpasuAxela:20180603193509j:plain

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])    
              


結果
f:id:nyanpasuAxela:20180603172745j:plain

gifで
f:id:nyanpasuAxela:20180603195330g:plain

ほにゃほにゃですが、雰囲気だけでもポくなりました(*´ω`*)

今回はお借りしまくった感じで、まだ前処理とかちゃんとしてないので、そのうちもっと精度上げたいところです(`・ω・´)ゞ