Pythonでtwiceの顔判定してみた 

#twiceの顔認識アプリ制作してみた

エンジニアに転職希望の者です。Aidemiyにて勉強中です。

今回、好きなアプリを制作してみようという課題で、私の大好きなtwiceに関するアプリを制作してみました。
制作したアプリは、選んだ画像がtwiceの誰なのかを認識するアプリです。

##twiceとは

2015年に結成した韓国の9人組多国籍ガールズグループです。



|　　 | メンバー　　| 国籍　 |
|:-|:-|:-|
| 1| ナヨン| 韓国|
| 2| ジョンヨン| 韓国|
| 3| ジヒョ　| 韓国|
| 4| ダヒョン　| 韓国|
| 5| チェヨン　| 韓国|
| 6| モモ　| 日本|
| 7| サナ　| 日本|
| 8| ミナ　| 日本|
| 9| ツウィ　|台湾　|



歌、ダンスパフォーマンス、スタイル全てが洗練されており、
メンバーの人柄もよく仲がいいところが魅力的です。（twiceについてなら何時間でも語れます）
世界中で人気のグループで、韓国だけでなく日本でもファンが多いです。

<blockquote class="twitter-tweet"><p lang="ko" dir="ltr">TWICE &amp; Spotify<br>1,000만 구독자 달성!!❤ 🔥 축하해요🥰 원스🍭<br><br>10 Million Followers!!❤ 🔥 Congratulations🥰 ONCE🍭<br><br>TWICE on <a href="https://twitter.com/Spotify?ref_src=twsrc%5Etfw">@Spotify</a><a href="https://twitter.com/hashtag/ONCE?src=hash&amp;ref_src=twsrc%5Etfw">#ONCE</a> <a href="https://twitter.com/hashtag/%EC%9B%90%EC%8A%A4?src=hash&amp;ref_src=twsrc%5Etfw">#원스</a> <a href="https://twitter.com/hashtag/TWICE?src=hash&amp;ref_src=twsrc%5Etfw">#TWICE</a> <a href="https://twitter.com/hashtag/%ED%8A%B8%EC%99%80%EC%9D%B4%EC%8A%A4?src=hash&amp;ref_src=twsrc%5Etfw">#트와이스</a> <a href="https://twitter.com/hashtag/Spotify?src=hash&amp;ref_src=twsrc%5Etfw">#Spotify</a> <a href="https://t.co/qsGLypD2Lq">pic.twitter.com/qsGLypD2Lq</a></p>&mdash; TWICE (@JYPETWICE) <a href="https://twitter.com/JYPETWICE/status/1427804179301486596?ref_src=twsrc%5Etfw">August 18, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

ただ、今回はアプリ制作について書きたいと思います。

##私の環境
Python3
MacBook Pro
Google Colaboratory

##画像集め
最初はメンバーの名前のみで検索をかけていましたが、あまりにも違う人の画像が多かったので、調べるときにtwice ○○(メンバー名)にすることで、たくさんの画像を集めることができました。違う人の画像は手で消して合計メンバーごとに約300枚の画像を集め、今回のアプリ制作に挑みました。

```python
from icrawler.builtin import BingImageCrawler
crawler = BingImageCrawler(storage={"root_dir": "○○(メンバー名)"})
crawler.crawl(keyword="twice ○○(メンバー名)", max_num=500)
```

##画像の水増し
Colaboratoryにて画像の水増し、学習を行いました。

画像が少ないため、水増しをして過学習を減らしました。画像を回転させ、一枚の画像から１０枚の画像を作り水増ししました。

```python

from keras.callbacks import EarlyStopping, ModelCheckpoint
from tensorflow.keras.optimizers import RMSprop
from keras.utils.np_utils import to_categorical
import tensorflow.keras.utils as utils
from tensorflow.keras.layers import Activation, Dropout, Flatten, Dense
from tensorflow.keras.layers import Conv2D, MaxPooling2D
from tensorflow.keras.models import Sequential
import os
import glob
from os import listdir
import numpy as np
from sklearn import model_selection
from keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array, array_to_img


def return_file(path):
    return [filename for filename in listdir(search_dir+path) if not filename.startswith('.')]


search_dir = "/content/drive/MyDrive/"  # 現在のディレクトリ
search_topic = ["nayon","chaeyong","dahyun","jeongyeon", "jihyo", "mina", "momo", "sana", "tzuyu" ]
image_size = 100

X = []
Y = []
for index, classlabel in enumerate(search_topic):

    # 拡張する画像群の読み込み
    images = return_file(search_topic[index])  # ファイル名を指定
    # 拡張する際の設定
    generator = ImageDataGenerator(
        rotation_range=90,  # 90°まで回転
    )
    # 読み込んだ画像を順に拡張
    for i in range(len(images)):
        img = load_img(
            search_dir+"/"+search_topic[index]+"/"+images[i], target_size=(image_size, image_size))
        # 画像を配列化して転置a
        x = img_to_array(img)
        x = np.expand_dims(x, axis=0)

        # 画像の拡張
        g = generator.flow(x, batch_size=1)

        # g.next()の回数分拡張される。1つの入力画像から何枚拡張するかを指定
        for i in range(10):
            bach = g.next()
            g_img = bach[0].astype(np.uint8)
            X.append(g_img)
            Y.append(index)

X = np.array(X)
Y = np.array(Y)

rand_index = np.random.permutation(np.arange(len(X)))
X_shuffle = X[rand_index]
Y_shuffle = Y[rand_index]

X_train, X_test, y_train, y_test = model_selection.train_test_split(
    X_shuffle, Y_shuffle, test_size=0.3)
xy = (X_train, X_test, y_train, y_test)
#Numpy配列をファイルに保存
np.save('./data.npy', xy)

```
###学習
画像を学習し、精度を確認します。

```python

import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
from keras.utils.np_utils import to_categorical
from keras.layers import Dense, Dropout, Flatten, Input
from keras.applications.vgg16 import VGG16
from keras.models import Model, Sequential
from keras import optimizers

X_train, X_test, y_train, y_test = np.load('./data.npy', allow_pickle=True)
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

input_tensor = Input(shape=(image_size, image_size, 3))
vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)
top_model = Sequential()
top_model.add(Flatten(input_shape=vgg16.output_shape[1:]))
top_model.add(Dense(256, activation='sigmoid'))
top_model.add(Dropout(0.1))
top_model.add(Dense(len(search_topic), activation='softmax'))
model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output))

for layer in model.layers[:15]:
 layer.trainable = False

model.compile(loss='categorical_crossentropy',
              optimizer='adadelta',
              metrics=['accuracy'])

es_cb = EarlyStopping(monitor='val_loss', patience=3, verbose=0, mode='auto')

history = model.fit(X_train, y_train, validation_data=(X_test, y_test), validation_split=0.1, shuffle=True,
                    batch_size=32, epochs=200, callbacks=[es_cb])

plt.plot(history.history["accuracy"], label="accuracy", ls="-", marker="o")
plt.plot(history.history["val_accuracy"],
         label="val_accuracy", ls="-", marker="x")
plt.ylabel("accuracy")
plt.xlabel("epoch")
plt.legend(loc="best")
plt.show()


model.save('model.h5')
scores = model.evaluate(X_test, y_test, verbose=1)
print('Test loss:', scores[0])
print('Test accuracy:', scores[1])

```

結果は以下の通りです。
画像を増やしたり水増しすることで、かなりaccuraryとval_accuracyの差が小さくなりました。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1834892/34b0dc7a-9270-03c3-235b-50cd3aaf5fcd.png)

Test loss: 1.0828653573989868
Test accuracy: 0.6795709133148193

##コード制作
ここが一番難しかったです。Aidemyで勉強した『Flask入門』の課題をヒントにしながら、カウンセラーに質問したりしてなんとか作成しました。理解できない点は多すぎて紹介できませんが、とにかく真似をすることを意識して取り組みました。

まずそれぞれのメンバーの画像がちゃんと画像であるか確認します。
学習したモデルをロードして、画像が誰か判断するようにします。

```python
import os
from flask import Flask, request, redirect, render_template, flash
from werkzeug.utils import secure_filename
from tensorflow.keras.models import Sequential, load_model
from tensorflow.keras.preprocessing import image

import numpy as np


classes = ["nayon","chaeyong","dahyun","jeongyeon", "jihyo", "mina", "momo", "sana", "tzuyu" ]
image_size = 100

UPLOAD_FOLDER = "static/uploads"
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])

app = Flask(__name__)

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

model = load_model('./model.h5')#学習済みモデルをロード


@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        if 'file' not in request.files:
            return redirect(request.url)
        file = request.files['file']
        if file.filename == '':
            return redirect(request.url)
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            file.save(os.path.join(UPLOAD_FOLDER, filename))
            filepath = os.path.join(UPLOAD_FOLDER, filename)

            #受け取った画像を読み込み、np形式に変換
            img = image.load_img(filepath, grayscale=False, target_size=(image_size,image_size))
            img = image.img_to_array(img)
            data = np.array([img])
            #変換したデータをモデルに渡して予測する
            result = model.predict(data)[0]
            predicted = result.argmax()
            pred_answer = "これは " + classes[predicted] + " です"

            return render_template("index.html",answer=pred_answer,filepath=filepath)

    return render_template("index.html",answer="")


if __name__ == "__main__":
    port = int(os.environ.get('PORT', 8080))
    app.run(host ='0.0.0.0',port = port)
```


完成したアプリがこちらです。
https://nanae.herokuapp.com/

##アプリを制作してみて
pthyonの勉強はとても面白いと感じたと同時に、横も奥もとても深いことがよく分かりました。精度を上げたいと画像の色調を変えたり、ランダムで画像を動かしたりしましたが、さらに精度が落ちるという結果になりました。今後は、画像をさらに増やすことでまた精度を上げられるのかなと考えています。もっと勉強して、自分の好きなことをやりながら力をつけていきたいと思います。