ミライアカリとココロヤミを画像認識してみた OpenCV Keras DeepLearning CNN　【データセット作成からモデル作成・テストまで】 

#はじめに
ミライアカリとココロヤミは同一人物ですが、
画像認識技術を用いて判別できるのでは？と思い
データセットの作成から、モデル作成・テストまでの一連の流れをやってみました。
![top.jpg](https://qiita-image-store.s3.amazonaws.com/0/189203/dd36f9ad-20a6-56db-9e1f-2b36f1dea36b.jpeg)
すこ

#データセット作成
・画像収集
画像の収集方法ですが、スクレイピングして画像収集するのには手間がかかるので、
動画から画像をフレーム単位で切り取って保存します。
コードはこちら↓
https://github.com/HiroakiFuse/opencv_faceAnime
・Opencvを使ってアニメの顔を検出してデータセット作成
 lbpcascade_animeface.xmlを使用して、
顔の部分を検出し切り取って画像データを保存します。
lbpcascade_animeface.xmlは↓
https://github.com/nagadomi/lbpcascade_animeface
#データセットをもとにモデル作成
モデルの学習は google colaboratoryを使いました。
GPU:NVIDIA Tesla K80が無料で使えるのでオススメです。

#google colaboratoryを使用する
##googledriveにアクセスするための設定
```python:keras.ipynb
!apt-get install -y -qq software-properties-common python-software-properties module-init-tools
!add-apt-repository -y ppa:alessandro-strada/ppa 2>&1 > /dev/null
!apt-get update -qq 2>&1 > /dev/null
!apt-get -y install -qq google-drive-ocamlfuse fuse
```
##認証
```python:keras.ipynb
from google.colab import auth
auth.authenticate_user()
from oauth2client.client import GoogleCredentials
creds = GoogleCredentials.get_application_default()
import getpass
!google-drive-ocamlfuse -headless -id={creds.client_id} -secret={creds.client_secret} < /dev/null 2>&1 | grep URL
vcode = getpass.getpass()
!echo {vcode} | google-drive-ocamlfuse -headless -id={creds.client_id} -secret={creds.client_secret}
```

このコードを実行するとURLが吐き出されるので、クリックして許可してください。

##googledriveの特定フォルダをマウント
```python:keras.ipynb
!cp -f ~/.gdfuse/default/config config
!sed -i -e "s/^root_folder=$/root_folder=[フォルダーＩＤ]/" config

!mkdir -p drive
!google-drive-ocamlfuse -config ./config -cc drive
!ls drive
```
フォルダーＩＤを指定することで、特定のフォルダのみをマウントできる

#Kerasを使ってCNN構築してみる
##必要なパッケージをインポートする
```python:keras.ipynb
# coding:utf-8
import keras
from keras.utils import np_utils
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.models import Sequential,model_from_json
from keras.layers.core import Dense, Dropout, Activation, Flatten
from keras.preprocessing.image import array_to_img, img_to_array, list_pictures, load_img
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
```
##画像の読み込みと行列への変換 データセットの作成
```python:keras.ipynb
X = []
Y = []

# ミライアカリの画像
for picture in list_pictures('./drive/image/mirai/'):
    img = img_to_array(load_img(picture, target_size=(64,64)))
    X.append(img)

    Y.append(0)


# ココロヤミの画像
for picture in list_pictures('./drive/image/kokoro/'):
    img = img_to_array(load_img(picture, target_size=(64,64)))
    X.append(img)

    Y.append(1)


# arrayに変換
X = np.asarray(X)
Y = np.asarray(Y)

# 画素値を0から1の範囲に変換
X = X.astype('float32')
X = X / 255.0

# クラスの形式を変換
Y = np_utils.to_categorical(Y, 2)

# 学習用データとテストデータ
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.2, random_state=1)
```
##モデルの構築
```python:keras.ipynb
# CNNを構築
model = Sequential()

model.add(Conv2D(32, (3, 3), padding='same',
                 input_shape=X_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(2))       # クラスは2個
model.add(Activation('softmax'))

# コンパイル
model.compile(loss='categorical_crossentropy',
              optimizer='SGD',
              metrics=['accuracy'])
#TensorBoardのログ設定
log_filepath = './logs'
tbcb = keras.callbacks.TensorBoard(log_dir=log_filepath,histogram_freq=1, write_graph=True, write_images=True)
cbks = [tbcb]

# 実行。出力はなしで設定(verbose=0)。
history = model.fit(X_train, y_train, batch_size=16, epochs=60,
                   validation_data = (X_test, y_test),verbose = 1)
```
##model accuracyの描画
```python:keras.ipynb
plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])
plt.title('model accuracy')
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.legend(['acc', 'val_acc'], loc='lower right')
plt.show()
```

![model_acc.PNG](https://qiita-image-store.s3.amazonaws.com/0/189203/6394d4df-d4b1-7ead-94b9-0157c1b84115.png)
学習結果：loss: 0.0184 - acc: 0.9963 - val_loss: 0.0289 - val_acc: 0.9851
##モデルを保存
```python:keras.ipynb
model_json_str = model.to_json()
open('vtuber_model.json', 'w').write(model_json_str)
model.save_weights('vtuber_model.h5')
```
##ローカルドライブに保存したモデルを移動させる
```python:keras.ipynb
!cp vtuber_model.json ./drive/vtuber_model.json
!cp vtuber_model.h5 ./drive/vtuber_model.h5
```
ローカルに移動させる理由ですが、google colaboratoryは使用してから12時間後に
インスタンスが初期化されるためです。
#テスト
作成したモデルをもとにテストしてみます。
全体画像から結果を検出するためにOpencvを使って顔を検出し、
そのデータをモデルに投げるようにしました。

##必要なライブラリのインポート

```python:keras_run.ipynb
import numpy as np
import cv2
from keras.models import Sequential, model_from_json
from keras.models import Sequential
from keras.layers.convolutional import Conv2D
from keras.layers.pooling import MaxPool2D
from keras.layers.core import Dense,Activation,Dropout,Flatten
from keras.utils import np_utils
from keras.layers.core import Dense
from keras.optimizers import RMSprop
from keras.preprocessing.image import load_img
from keras.preprocessing.image import load_img, img_to_array
from PIL import Image
import matplotlib.pyplot as plt
```
##学習済みモデルを用いて動作実験してみる
```python:keras_run.ipynb
#画像から顔を検出する
def faceDetection(path,size):
    #cascadeパス
    cas_path = "lbpcascade_animeface.xmlのパスを指定"
    cvImg = cv2.imread(path)
    cascade = cv2.CascadeClassifier(cas_path)
    facerect = cascade.detectMultiScale(cvImg, scaleFactor=1.1, minNeighbors=1, minSize=(1, 1))
    faceData=[]
    for rect in facerect:
        faceImg = cvImg[rect[1]:rect[1]+rect[3],rect[0]:rect[0]+rect[2]]
        resized = cv2.resize(faceImg,None, fx=float(size/faceImg.shape[0]),fy=float( size/faceImg.shape[1]))
        CV_im_RGB = resized[:, :, ::-1].copy()
        pilImg=Image.fromarray(CV_im_RGB)
        faceData.append(pilImg)
    #複数検出される場合があるので最初のfaceDataを投げる
    return faceData[0]

def main():
    #画像パス
    img_path = "画像パス指定"
    #モデルパス
    model_path = "作成したモデルのjsonパス指定"
    #重みパス
    weight_path = "作成したモデルのh5パス指定"
    #ラベル
    label = ['ミライアカリ','ココロヤミ']
    
    #faceDetection呼び出し
    faceImg = faceDetection(img_path,64)
    #検出した顔データの確認
    plt.imshow(faceImg)
    plt.show()
    
    imgarray = []
    imgarray.append(img_to_array(faceImg))
    imgarray = np.array(imgarray) / 255
    imgarray.astype('float32')
    
    #モデルの読み込み
    model = model_from_json(open(model_path,'r').read())
    #重みの読み込み
    model.load_weights(weight_path)
    #コンパイル
    model.compile(loss='categorical_crossentropy',
              optimizer='SGD',
              metrics=['accuracy'])
    #テスト画像を入力し、結果を出力する
    y_pred = model.predict(imgarray)
    print(y_pred)
    #出力結果をクラスラベルから整数値に変換
    y_pred = np.argmax(y_pred, axis=1)
    print(y_pred)
    #Plotで画像表示する
    image = Image.open(img_path)
    img_list = np.asarray(image)
    plt.imshow(img_list)
    plt.show()
    print("予測結果:",label[int(y_pred)])
if __name__ == '__main__':
    main()
```

全体画像から正しく検出するためにopencvで顔を検出し、
そのデータをモデルに投げています。

###うまくいったところ
・![kokoro.PNG](https://qiita-image-store.s3.amazonaws.com/0/189203/c1b4984a-e833-bc72-a1fc-c16eb3f6cfa2.png)
![mirai.PNG](https://qiita-image-store.s3.amazonaws.com/0/189203/538b8c03-9b48-9706-3f76-73e4cce88d6a.png)
###うまくいかなかったところ
![kokoro2.PNG](https://qiita-image-store.s3.amazonaws.com/0/189203/938be833-a868-076b-6d5d-8d282734f587.png)
**なんでぞ！！！**
#ソースコード
※準備中(github整理しますので、もう少しお待ちください)
#思ったこと
学習するデータ量に応じて、testデータの割合を変化させたほうがよさげ。
適切なバッチサイズを決める根拠を学ぶ必要がある。
quiver使って層の視覚化してみてみたい。
#まとめ
※作成中
#今後
ＡＩを用いたWebサービスを作成してみようと思います。
WebフレームワークはFlaskかDjangoでやる予定
作成したら記事に一連の流れを載せようと思います。
#参考サイト
機械学習で乃木坂４６を顔分類してみた
・http://blog.aidemy.net/entry/2017/12/17/214715
kerasでCNN 自分で拾った画像でやってみる
・https://qiita.com/haru1977/items/17833e508fe07c004119
OpenCVでアニメの顔検出
・https://qiita.com/mo4_9/items/fda37ac4f9ddab2d7f45