Kerasでアニメ画像を学習し予測する(CNN)

## 環境
* Python:3.6
* TensorFlow:1.8.0
* Keras:2.2.5

## アニメ画像切り出し

下記を参考にフォルダ内全ての画像を切り出しする。
[OpenCVでアニメの顔検出 - Qiita](https://qiita.com/mczkzk/items/fda37ac4f9ddab2d7f45)

``` python
import os
import cv2

import glob

list = glob.glob("/Users/katasugirupan/Desktop/sample/*")

classifier = cv2.CascadeClassifier('lbpcascade_animeface.xml')

output_dir = 'corpus'
if not os.path.exists(output_dir):
    os.makedirs(output_dir)

n = 0
for i in list:
    print(i)
    image = cv2.imread(i)
    gray_image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
    faces = classifier.detectMultiScale(gray_image)
    # print(faces)

    for j, (x,y,w,h) in enumerate(faces):
        face_image = image[y:y+h, x:x+w]
        resize_image = cv2.resize(face_image,(64,64),interpolation = cv2.INTER_AREA)
        output_path = os.path.join(output_dir,str(n)+'.jpg'.format(j))
        cv2.imwrite(output_path,resize_image)
    
    n += 1

```
lbpcascade_animeface.xml
[https://github.com/nagadomi/lbpcascade_animeface](https://github.com/nagadomi/lbpcascade_animeface)

## 画像の名前を連番にする
特に必要な作業ではないですが管理がしやすいように、ファイル名を「A0.jpg - A1.jpg ...」に変更しておきます。

``` python
import os
import glob

list = glob.glob("/Users/katasugirupan/Desktop/corpus/*")

output_dir = 'dataset'
if not os.path.exists(output_dir):
    os.makedirs(output_dir)

n = 0
for file_name in list:
    print(file_name)
    os.rename(file_name, '/Users/katasugirupan/Desktop/' + output_dir + '/' + 'A' + str(n) + '.jpg') 
    n += 1
```

## kerasで自前のデータセットを読み込み、表示してみる

``` python
# データセット作成　表示
import keras
from keras.utils import np_utils
from keras.preprocessing.image import array_to_img, img_to_array, load_img
import numpy as np

from PIL import Image

import os
import re

# 画像表示
def ConvertToImg(img):
    return Image.fromarray(np.uint8(img))

def list_pictures(directory, ext='jpg|jpeg|bmp|png|ppm'):
    return [os.path.join(root, f)
            for root, _, files in os.walk(directory) for f in files
            if re.match(r'([/w]+/.(?:' + ext + '))', f.lower())]

X = []

# 画像フォルダから、６４x６４で取り込む
for picture in list_pictures('./dataset'):
    img = img_to_array(load_img(picture, target_size=(64,64)))
    X.append(img)

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

# 画像一つの幅
chr_w = 64
# 画像一つの高さ
chr_h = 64

# 画像をPILで１枚の画像に描画する(15枚*15枚)
canvas = Image.new('RGB', (int(chr_w * 15), int(chr_h * 15)), (255, 255, 255))

# 画像を読み込んで描画
i = 0
for y in range(15):
    for x in range(15):
        chrImg = ConvertToImg(x_train[i])
        canvas.paste(chrImg, (chr_w*x, chr_h*y))
        i = i + 1

canvas.show()
# 表示した画像をJPEGとして保存
canvas.save('test.jpg', 'JPEG', quality=100, optimize=True)
```
今回はMakeGirlsMoeから画像を頂き、切り出しています。
表示するとこのような感じ。
<img width="400" alt="sample.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494076/867508cd-803b-45e9-dcf3-65b15610e932.jpeg">

## kerasでCNNを実装
下記の記事を参考にさせていただきました。
[kerasでCNN 自分で拾った画像でやってみる - Qiita](https://qiita.com/haru1977/items/17833e508fe07c004119)
[Kerasは相撲取りとアイドルを見分けられるかやってみた - Qiita](https://qiita.com/hiroeorz@github/items/2fbb3b8d12b0e20f0384)


私もやってみたい。
と言う事で、Fate0のセイバーが好きなのでセイバー分類機を作成することに。
* セイバーかセイバーではないか分類。
* セイバー : 300枚
* セイバーではない : 600枚
* 学習とテストは合計900枚を利用し、その内10%をテストに使用
* プラス、未知のデータとして検証用にそれぞれ3枚づつ用意
画像はgoogle検索で頑張る。

keras2系で記述しています。

``` python
from keras.models import Sequential
from keras.layers import Activation, Dense, Dropout, Conv2D, Flatten, MaxPooling2D
from keras.utils.np_utils import to_categorical
from keras.optimizers import Adagrad
from keras.optimizers import Adam
import numpy as np
from PIL import Image
import os
import matplotlib.pyplot as plt

# 学習用のデータ作成
X = []
Y = []

for dir in os.listdir("drive/My Drive/keras/data2/train"):

    dir1 = "drive/My Drive/keras/data2/train/" + dir 
    label = 0

    # Labelを付ける
    if dir == "saber": 
        label = 0
    elif dir == "unknown": 
        label = 1

    for file in os.listdir(dir1):
        Y.append(label)
        filepath = dir1 + "/" + file
        image = np.array(Image.open(filepath).resize((64, 64)))
        image = image.transpose(2, 0, 1)
        X.append(image / 255.)

X = np.array(X)
Y = to_categorical(Y) # [1,0],[0,1] One hot

# CNN
model = Sequential()
model.add(Conv2D(32, (3, 3), padding="same", input_shape=(3, 64, 64)))
model.add(Activation("relu"))
model.add(Conv2D(32, (3, 3)))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2, 2), padding=("same")))
model.add(Dropout(0.25))

model.add(Flatten())

model.add(Dense(200))
model.add(Activation("relu"))
model.add(Dropout(0.2))

model.add(Dense(200))
model.add(Activation("relu"))
model.add(Dropout(0.2))

model.add(Dense(2))
model.add(Activation("softmax"))

opt = Adam(lr=0.0001)
model.compile(loss="categorical_crossentropy", optimizer=opt, metrics=["accuracy"])
# 10%はテストに使用
hist = model.fit(X, Y, epochs=50, batch_size=25, validation_split=0.1)

# テスト用ディレクトリの画像でチェック、正解率を表示
total = 0.
ok_count = 0.

for dir in os.listdir("drive/My Drive/keras/data2/test"):

    dir1 = "drive/My Drive/keras/data2/test/" + dir 
    label = 0

    if dir == "saber":
        label = 0
    elif dir == "unknown":
        label = 1

    for file in os.listdir(dir1):
        label_list.append(label)
        filepath = dir1 + "/" + file
        image = np.array(Image.open(filepath).resize((64, 64)))
        print(filepath)
        image = image.transpose(2, 0, 1)
        result = model.predict_classes(np.array([image / 255.]))
        print("label:", label, "result:", result[0])

    total += 1.

    if label == result[0]:
        ok_count += 1.

print("seikai: ", ok_count / total * 100, "%")


# 正解率の推移をプロット
plt.plot(hist.history['acc'])
plt.plot(hist.history['val_acc'])
plt.title('Accuracy')
plt.legend(['train','test'], loc='upper left')
plt.show()

# ロスの推移をプロット
plt.plot(hist.history['loss'])
plt.plot(hist.history['val_loss'])
plt.title('Loss')
plt.legend(['train','test'], loc='upper left')
plt.show()

model.summary()
```

<img width="400" alt="sample2.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494076/405cca74-d43e-1647-cb25-1efd451bd3f7.png">

```
drive/My Drive/keras/data2/test/saber/F302.jpg
label: 0 result: 0
drive/My Drive/keras/data2/test/saber/F303.jpg
label: 0 result: 0
drive/My Drive/keras/data2/test/saber/F301.jpg
label: 0 result: 0
drive/My Drive/keras/data2/test/unknown/A560.jpg
label: 1 result: 1
drive/My Drive/keras/data2/test/unknown/A561.jpg
label: 1 result: 1
drive/My Drive/keras/data2/test/unknown/A562.jpg
label: 1 result: 1
seikai:  100.0 %
```
未知のデータ6枚に対しても正しく認識しているようです。
枚数を増やして検証すれば正解率は下がるかもしれません。

### 終わりに
* 下記のモデルも試して見たいと思うのですが、長くなり疲れてきたので一旦終わりにしようと思います。

<img width="550" alt="sample.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494076/e1fa35a9-6c6c-60ee-e659-4cde96fa189f.png">


* padding='valid'にしたり、もう一段階Denseを増やしたりと色々考えれると思います。

* qiitaの記事がありましたので、私でもCNNを試せました。先人に感謝致します。