765プロ御三家をCNNで識別してみた (with Chainer 2.0.0)

研究室のディープラーニング勉強会で各自何かやってみようということになったので，アイマスの元祖御三家をCNNで識別してみました．

結果はこんな感じです

![out4.png](https://qiita-image-store.s3.amazonaws.com/0/136096/a78dbd1d-9f7b-8f23-1124-d2428bf57b6e.png)
（http://idolmaster.jp/images/event/9th/goods/img_goods37.jpg よりこの画像は拝借しました．問題があれば削除します．)

思い立って10時間くらいでできました．いろんな方が公開してくださっているコードに感謝です．

#環境
・MacOS X El Capitan
・python 3.5.2
・OpenCV 3.1.0
・anaconda3-4.2.0
・Chainer 2.0.0
・PIL
など

OpenCVをcondaで入れ，そこにChainerやらなんやらを入れると楽チンです．

#データセット

[python3でgoogleの画像をダウンロード] (http://inmyzakki.hatenablog.com/entry/2017/05/08/231308)
こちらの方のコードを拝借して3人の画像をそれぞれ100枚ほど収集しました．

その他用に他の画像も適当に集めました．

「100枚？少な！」
と思われるかもしれませんが，今回はこれだけでうまくいきました．
特徴がわかりやすいからでしょうか．

#顔切り出し
[OpenCVを使って誰の顔なのかを推定する（Eigenface, Fisherface, LBPH）](http://qiita.com/hitomatagi/items/8f2f37646179aca68649)
こちらのコードをもとに，切り出し，保存しました．

サイズは32x32です．

変更点は[OpenCVによるアニメ顔検出ならlbpcascade_animeface.xml](http://ultraist.hatenablog.com/entry/20110718/1310965532)
で配布されている，「lbpcascade_animeface.xml」を特徴量検出器に使用したことと，カラー保存をできるようにしたことです．

```python:kao.py
#!/usr/bin/python
# -*- coding: utf-8 -*-

import cv2, os
import numpy as np
from PIL import Image

# 元画像
train_path = '元画像フォルダのパス'

# アニメ顔特徴分類器
cascadePath = "lbpcascade_animeface.xml"
faceCascade = cv2.CascadeClassifier(cascadePath)

# 指定されたpath内の画像を取得
def get_images_and_labels(path):
    # 画像を格納する配列
    images = []
    # ラベルを格納する配列
    labels = []
    # ファイル名を格納する配列
    files = []
    i = 0
    for f in os.listdir(path):
        # 画像のパス
        image_path = os.path.join(path, f)
        # 画像を読み込む
        image_pil = Image.open(image_path)
        # NumPyの配列に格納
        image = np.array(image_pil, 'uint8')
        # アニメ顔特徴分類器で顔を検知
        faces = faceCascade.detectMultiScale(image)
        # 検出した顔画像の処理
        for (x, y, w, h) in faces:
            # 顔を 32x32 サイズにリサイズ
            roi = cv2.resize(image[y: y + h, x: x + w], (32, 32), interpolation=cv2.INTER_LINEAR)
            # 画像を配列に格納
            images.append(roi)
            # ファイル名を配列に格納
            files.append(f)
            save_path = './書き出しフォルダのパス/' + str(i) + '.jpg'
            # そのまま保存すると青みがかる（RGBになっていない）
            cv2.imwrite(save_path, roi[:, :, ::-1].copy())
            print(i)

            i+=1

    return images

images = get_images_and_labels(train_path)

# 終了処理
cv2.destroyAllWindows()

```

データの水増しは行わなくても上手くいきました．

#学習
[python: chainerを使って化物語キャラを認識させるよ！ 〜part5 主要キャラで多値分類(未知データに適用編)〜](http://www.mathgram.xyz/entry/chainer/bake/part5)
この記事をもとに，chainer 2.0.0で走るように書き換えました．

ポイントは

```py
    train = tuple_dataset.TupleDataset(X_train, y_train)
    test = tuple_dataset.TupleDataset(X_test, y_test)

    train_iter = chainer.iterators.SerialIterator(train, args.batchsize)
    test_iter = chainer.iterators.SerialIterator(test, args.batchsize,
                                                 repeat=False, shuffle=False)

    # Set up a trainer
    updater = training.StandardUpdater(train_iter, optimizer, device=args.gpu)
    trainer = training.Trainer(updater, (args.epoch, 'epoch'), out="output")

    # Evaluate the model with the test dataset for each epoch
    trainer.extend(extensions.Evaluator(test_iter, model, device=args.gpu))

```

のように，元コードの「学習とテスト」を置き換えて，trainerとupdaterが走るようにしたことです．
あとは

```py
model = L.Classifier(clf_bake())
```

としたり，層をL.Convolution2Dに直したり，def forwardをdef __call__に置き換えたくらいです．

モデルは可視化するとこんな感じ

![cg.png](https://qiita-image-store.s3.amazonaws.com/0/136096/ae5c84d9-4ae3-6013-2570-c2643612fcc7.png)



#学習結果

CPUで30epoch回しました．GPU使わなくても4層くらいなら速いです．
<img width="683" alt="スクリーンショット 2017-07-02 13.45.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/136096/8fc3d092-1599-9fcb-b1ff-2d32a8d6cb69.png">

loss
![loss.png](https://qiita-image-store.s3.amazonaws.com/0/136096/2344dc49-c2db-eb1f-9cd8-b3fc1955b496.png)

accuracy
![accuracy.png](https://qiita-image-store.s3.amazonaws.com/0/136096/1de2bd6e-c8c6-510c-bcad-4ae80f0b0d5a.png)

データセットが100枚ほどであるのにも関わらず，val/accuracyが85％はなかなかすごいのではないでしょうか．自分でも初めに回したときはびっくりしました．

CNNすっごーい！


#未知データ推定
こちらも大半は[python: chainerを使って化物語キャラを認識させるよ！ 〜part5 主要キャラで多値分類(未知データに適用編)〜](http://www.mathgram.xyz/entry/chainer/bake/part5)の記事をもとにしました．


ポイントは，モデルの読み込みを

```py
model = L.Classifier(clf_bake())

chainer.serializers.load_npz("output/model_snapshot_500", model) 
```

のようにしてClassifierを使うことと，

recognition関数の返り値を

```py
def recognition(image, faces):

(中略)

return model.predictor(face_images) , image
```

とすることです．

#感想
すぐに結果が見えるので楽しいですね．
自分でデータセットを用意してやってみると楽しいので是非．

今度はもっと多クラスでやってみようかな．