【Python】デレマスアイドルで学ぶ顔認識
この投稿は 東京工業大学アイドルマスター研究会 Advent Calendar 2021 13日目となります。
現在、東京工業大学アイドルマスター研究会(テクマス)では、12月1日から25日まで毎日1~2個ずつリレー形式で記事を更新していくアドベントカレンダーという企画を行っています。
記事の一覧は AdC2021 のタグで確認できるので、ぜひそちらも見に行ってみてください!
目次
長いので、結果だけ気になる方は 使ってみる からどうぞ。自己紹介/Abstraction
おはようございます。副代表2年目のこーちゃんです。Twitterは@fl_cl_skとか@fl_cl_pとかです。最近は専ら一個目のアカウントを使っています。
突然ですが、アイドルの顔面って素敵ですよね。
橘ありすさんの顔は、良い。
そこで、アイドルの顔面を畳み込みニューラルネットワークに食わせてその良さを機械にも教え込んでやりましょう。
上の画像からは佐藤心さんの顔も抽出できます。
結城晴さんは横を向いているのでしんどい。
手法
学習用の画像を用意し、 OpenCV に搭載されている顔検出用モデル
lbpcascades
を二次元画像用に調整したモデル
lbpcascade_animeface
を用いて顔部分を切り抜きます。
切り抜いた画像を利用して、 Keras に搭載されている学習済みモデル
Inception-V3
を、デレマスアイドルの顔の識別用に転移学習 / ファインチューニングします。
環境について
今回はWindows10のWSL2を利用します。ディストリビューションは Ubuntu 20.04 です。Pythonのバージョンは 3.8.8 を使いました。 Tensorflow, Keras のバージョンはともに 2.7.0 です。
詳しい方はお気づきかもしれませんが、Windows10のWSL2では CUDA on WSL が利用できません(最近のアップデートで使えるようになった?らしい)。そのため今回は全部CPUで学習させました。
また、 Jupyter Lab を利用してコーディングをしていきました。細かい環境構築については割愛しますが、質問等ありましたらお気軽にどうぞ。
データ収集・準備
学習用データとしてモバマスに実装されている(ほぼ)全カードの画像を用意しました。モバマスのカードをまとめているウェブサイトからスクレイピングしましたが、サーバー負荷の原因となってしまうと良くないので細かいコードは省略します。私はひと晩かけてゆっくり保存しました。また、モバマスにはASのアイドル等も実装されているので、そこらへんもまとめて持ってきました。
次に、保存してきた画像から顔を切り抜いていきます。 OpenCV と、二次元画像の顔検出ができるモデル lbpcascades を利用します。出力される画像のサイズは 256*256 にしました。
import cv2
import os
import glob
cascade = cv2.CascadeClassifier('lbpcascade_animeface.xml')
load_img_list = []
face_cut = []
count = 0
# 切り抜いた画像は data/train/face/アイドル名 以下に保存される
data_face_path = 'data/train/face/'
# オリジナル画像は data/train/original/橘ありす/[ヴィンテージタイム]橘ありす.png のように保存されている
files = glob.glob('data/train/original/*/*.*')
for file in files:
idol_name, card_name = file.split('/')[3:5]
os.makedirs(f'{data_face_path}/{idol_name}', exist_ok=True)
original = cv2.imread(file)
face_rects = cascade.detectMultiScale(original, scaleFactor=1.11)
print(card_name, len(face_rects))
if(len(face_rects) !=0):
for face_rect in face_rects:
x = face_rect[0]
y = face_rect[1]
w = face_rect[2]
h = face_rect[3]
face = original[y:y + h, x:x + w]
face = cv2.resize(face, dsize =(256, 256))
cv2.imwrite(f'{data_face_path}/{idol_name}/face_{card_name}', face)
これで、顔を切り抜いた画像が生成できます。正しく検出できなかったり、顔でない場所が切り抜かれてしまうこともありますが、今回は選別が面倒なので特に気にせず進めます。
筆者はこのカードから逃げていたので執筆中に初めて向き合ったのですが、あまりにもえっちすぎて動けなくなってしまったらしい。
モデル学習
データ読み込み
さて、本題に入っていきます。先程作ったデータを ImageDataGenerator を用いて読み込みます。これを用いると、Data Augmentation、即ち水増しが簡単に行なえます。例えば width_shift_range=0.2 とすると、横幅をランダムで 0.8~1.2 倍してくれます。水増しをすることでモデルの過学習を抑制し、汎用性を向上させることができます。また、訓練用データと検証用データへの分割も自動でやってくれます。
from tensorflow.keras.preprocessing import image
batch_size=32
seed=1
datagen = image.ImageDataGenerator(
rescale=1./255,
validation_split=0.1,
horizontal_flip=True,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
fill_mode='nearest'
)
train_generator = datagen.flow_from_directory(
'data/train/face',
target_size=(256, 256),
class_mode='categorical',
batch_size=batch_size,
subset='training',
seed=seed,
)
val_generator = datagen.flow_from_directory(
'data/train/face',
target_size=(256, 256),
class_mode='categorical',
batch_size=batch_size,
subset='validation',
seed=seed,
)
Found 5477 images belonging to 212 classes. Found 499 images belonging to 212 classes.
モデル準備
次に、今回学習させるモデルを用意します。畳み込みニューラルネットワーク(CNN)を用いますが、新たなモデルをいちから作るのではなく、「学習済みモデル」を活用します。 Keras には学習済みモデルがいくつか搭載されていますが、今回は InceptionV3 を使います。これは、 ImageNet と呼ばれるカラー写真のデータベースをもとに学習したモデルです。めちゃめちゃ層があります。このモデルの重みを再学習させ、効率的にモデルを作ります。
from tensorflow.keras.applications.inception_v3 import InceptionV3
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, GlobalAveragePooling2D
base_model = InceptionV3(weights='imagenet', include_top=False, input_shape=(256, 256, 3))
# 特徴量から推定結果を求める全結合層を再定義する
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dense(1024, activation='relu')(x)
predictions = Dense(len(set(train_generator.labels)), activation='softmax')(x)
model = Model(inputs=base_model.input, outputs=predictions)
予習
本格的な学習に入る前に、先程追加した全結合層のみ先行して学習させます。これをすると良いらしいです、多分。また、学習度合いを評価する値として loss, accuracy, val_loss, val_accuracy が存在します。 val_ とついているものは検証用データによるもの、ついていないものは訓練用データによるものです。 loss は予測した値と真の値の差を表します。今回は複数クラスへの分類なので
categorical_crossentropy
を用いました。 accuracy は名前の通り正解率です。
また、学習用のoptimizerとして Momentum SGD を利用しました。確率的な勾配降下に加え前回の移動を慣性として利用することで振動等を抑制できるらしいです。へ~
from tensorflow.keras.optimizers import SGD
# 全結合層以外の重みを固定
for layer in base_model.layers:
layer.trainable = False
model.compile(optimizer=SGD(learning_rate=0.001, momentum=0.9), loss='categorical_crossentropy', metrics=['accuracy'])
model.fit(train_generator, epochs=10, verbose=1, validation_data=val_generator)
10Epoch (10世代) 学習させた結果が以下のとおりです。長いので興味がある人だけ見てください。基本的には val_loss が小さく、 val_accuracy が大きいほど嬉しいです。もちろん accuracy などが大きくてもいいのですが、 accuracy が大きいのにも関わらず val_accuracy が小さいと要注意です。過学習と呼ばれる、学習用のデータセットに過剰に適合してしまい、汎用性が失われた状態になってしまいます。
Epoch 1/10 172/172 [==============================] - 101s 575ms/step - loss: 5.3155 - accuracy: 0.0131 - val_loss: 5.1399 - val_accuracy: 0.0341 Epoch 2/10 172/172 [==============================] - 96s 556ms/step - loss: 5.0802 - accuracy: 0.0314 - val_loss: 4.9781 - val_accuracy: 0.0200 Epoch 3/10 172/172 [==============================] - 94s 544ms/step - loss: 4.8808 - accuracy: 0.0652 - val_loss: 4.7750 - val_accuracy: 0.0641 Epoch 4/10 172/172 [==============================] - 94s 545ms/step - loss: 4.6776 - accuracy: 0.0913 - val_loss: 4.6110 - val_accuracy: 0.0902 Epoch 5/10 172/172 [==============================] - 94s 543ms/step - loss: 4.4640 - accuracy: 0.1223 - val_loss: 4.4066 - val_accuracy: 0.1403 Epoch 6/10 172/172 [==============================] - 94s 543ms/step - loss: 4.2471 - accuracy: 0.1658 - val_loss: 4.2414 - val_accuracy: 0.1683 Epoch 7/10 172/172 [==============================] - 93s 542ms/step - loss: 4.0436 - accuracy: 0.1917 - val_loss: 4.0589 - val_accuracy: 0.1784 Epoch 8/10 172/172 [==============================] - 93s 542ms/step - loss: 3.8365 - accuracy: 0.2319 - val_loss: 3.9492 - val_accuracy: 0.1844 Epoch 9/10 172/172 [==============================] - 93s 542ms/step - loss: 3.6672 - accuracy: 0.2631 - val_loss: 3.7774 - val_accuracy: 0.1984 Epoch 10/10 172/172 [==============================] - 93s 542ms/step - loss: 3.4986 - accuracy: 0.2868 - val_loss: 3.6397 - val_accuracy: 0.2405
現在の学習結果は loss: 3.4986 – accuracy: 0.2868 – val_loss: 3.6397 – val_accuracy: 0.2405 となりました。予習なのでこのくらいでいいでしょう。それでは、本学習に入ります。
このPCを買ってから初めてCPU使用率が70%を上回りました。つよつよCPUでよかった~
ファインチューニング
いよいよモデルを本格的に学習させていきます。全ての層の重みを学習させるのではなく、下の方の層、具体的には250層以降のみ学習させていきます。また、 70Epoch 学習させるのですが、最後に完成するのが最も優秀なモデルとは限りません。過学習が起こってしまうからです。そのため、学習していく中で val_loss が最も小さいモデルを自動で保存するように ModelCheckpoint を作ります。
from tensorflow.keras.callbacks import ModelCheckpoint
checkpoint = ModelCheckpoint(
filepath='model_with_inceptionv3.h5',
monitor='val_loss',
save_best_only=True,
)
for layer in model.layers[:249]:
layer.trainable = False
for layer in model.layers[249:]:
layer.trainable = True
model.compile(optimizer=SGD(learning_rate=0.001, momentum=0.9), loss='categorical_crossentropy', metrics=['accuracy'])
model.fit(train_generator, epochs=70, verbose=1, validation_data=val_generator, callbacks=[checkpoint])
Epoch 1/70 172/172 [==============================] - 114s 645ms/step - loss: 3.9103 - accuracy: 0.2353 - val_loss: 3.1705 - val_accuracy: 0.2886 Epoch 2/70 172/172 [==============================] - 109s 633ms/step - loss: 2.7611 - accuracy: 0.4614 - val_loss: 2.4455 - val_accuracy: 0.4770 Epoch 3/70 172/172 [==============================] - 109s 632ms/step - loss: 2.0461 - accuracy: 0.6211 - val_loss: 1.8998 - val_accuracy: 0.6373 Epoch 4/70 172/172 [==============================] - 109s 631ms/step - loss: 1.5752 - accuracy: 0.7128 - val_loss: 1.6203 - val_accuracy: 0.6653 Epoch 5/70 172/172 [==============================] - 108s 627ms/step - loss: 1.2187 - accuracy: 0.7836 - val_loss: 1.3517 - val_accuracy: 0.7415 Epoch 6/70 172/172 [==============================] - 108s 625ms/step - loss: 1.0062 - accuracy: 0.8169 - val_loss: 1.1345 - val_accuracy: 0.7495 Epoch 7/70 172/172 [==============================] - 108s 626ms/step - loss: 0.8404 - accuracy: 0.8486 - val_loss: 1.0248 - val_accuracy: 0.7916 Epoch 8/70 172/172 [==============================] - 107s 619ms/step - loss: 0.7138 - accuracy: 0.8674 - val_loss: 0.9408 - val_accuracy: 0.8156 Epoch 9/70 172/172 [==============================] - 107s 621ms/step - loss: 0.5898 - accuracy: 0.8967 - val_loss: 0.8675 - val_accuracy: 0.8176 Epoch 10/70 172/172 [==============================] - 107s 623ms/step - loss: 0.5286 - accuracy: 0.9118 - val_loss: 0.8268 - val_accuracy: 0.8297 Epoch 11/70 172/172 [==============================] - 110s 639ms/step - loss: 0.4657 - accuracy: 0.9177 - val_loss: 0.7773 - val_accuracy: 0.8156 Epoch 12/70 172/172 [==============================] - 109s 631ms/step - loss: 0.4082 - accuracy: 0.9330 - val_loss: 0.6919 - val_accuracy: 0.8597 Epoch 13/70 172/172 [==============================] - 107s 624ms/step - loss: 0.3621 - accuracy: 0.9421 - val_loss: 0.7113 - val_accuracy: 0.8497 Epoch 14/70 172/172 [==============================] - 108s 625ms/step - loss: 0.3205 - accuracy: 0.9496 - val_loss: 0.7050 - val_accuracy: 0.8677 Epoch 15/70 172/172 [==============================] - 108s 627ms/step - loss: 0.2828 - accuracy: 0.9544 - val_loss: 0.6494 - val_accuracy: 0.8657 Epoch 16/70 172/172 [==============================] - 108s 625ms/step - loss: 0.2636 - accuracy: 0.9598 - val_loss: 0.6736 - val_accuracy: 0.8537 Epoch 17/70 172/172 [==============================] - 108s 626ms/step - loss: 0.2287 - accuracy: 0.9664 - val_loss: 0.6250 - val_accuracy: 0.8637 Epoch 18/70 172/172 [==============================] - 108s 624ms/step - loss: 0.2145 - accuracy: 0.9673 - val_loss: 0.6093 - val_accuracy: 0.8697 Epoch 19/70 172/172 [==============================] - 108s 626ms/step - loss: 0.1950 - accuracy: 0.9741 - val_loss: 0.6015 - val_accuracy: 0.8677 Epoch 20/70 172/172 [==============================] - 108s 625ms/step - loss: 0.1707 - accuracy: 0.9790 - val_loss: 0.6313 - val_accuracy: 0.8577 Epoch 21/70 172/172 [==============================] - 109s 632ms/step - loss: 0.1519 - accuracy: 0.9805 - val_loss: 0.6314 - val_accuracy: 0.8537 Epoch 22/70 172/172 [==============================] - 108s 625ms/step - loss: 0.1431 - accuracy: 0.9828 - val_loss: 0.6435 - val_accuracy: 0.8457 Epoch 23/70 172/172 [==============================] - 108s 624ms/step - loss: 0.1375 - accuracy: 0.9812 - val_loss: 0.6053 - val_accuracy: 0.8737 Epoch 24/70 172/172 [==============================] - 107s 623ms/step - loss: 0.1215 - accuracy: 0.9869 - val_loss: 0.5984 - val_accuracy: 0.8737 Epoch 25/70 172/172 [==============================] - 107s 622ms/step - loss: 0.1134 - accuracy: 0.9870 - val_loss: 0.6285 - val_accuracy: 0.8577 Epoch 26/70 172/172 [==============================] - 108s 629ms/step - loss: 0.1078 - accuracy: 0.9890 - val_loss: 0.5823 - val_accuracy: 0.8838 Epoch 27/70 172/172 [==============================] - 108s 627ms/step - loss: 0.0997 - accuracy: 0.9883 - val_loss: 0.5572 - val_accuracy: 0.8978 Epoch 28/70 172/172 [==============================] - 108s 627ms/step - loss: 0.0824 - accuracy: 0.9927 - val_loss: 0.5903 - val_accuracy: 0.8798 Epoch 29/70 172/172 [==============================] - 108s 626ms/step - loss: 0.0944 - accuracy: 0.9870 - val_loss: 0.5572 - val_accuracy: 0.8798 Epoch 30/70 172/172 [==============================] - 108s 625ms/step - loss: 0.0843 - accuracy: 0.9901 - val_loss: 0.5756 - val_accuracy: 0.8717 Epoch 31/70 172/172 [==============================] - 108s 625ms/step - loss: 0.0763 - accuracy: 0.9934 - val_loss: 0.5783 - val_accuracy: 0.8697 Epoch 32/70 172/172 [==============================] - 108s 625ms/step - loss: 0.0711 - accuracy: 0.9929 - val_loss: 0.5826 - val_accuracy: 0.8737 Epoch 33/70 172/172 [==============================] - 108s 625ms/step - loss: 0.0711 - accuracy: 0.9934 - val_loss: 0.5319 - val_accuracy: 0.8778 Epoch 34/70 172/172 [==============================] - 108s 625ms/step - loss: 0.0620 - accuracy: 0.9934 - val_loss: 0.5279 - val_accuracy: 0.9058 Epoch 35/70 172/172 [==============================] - 110s 641ms/step - loss: 0.0674 - accuracy: 0.9923 - val_loss: 0.5683 - val_accuracy: 0.8717 Epoch 36/70 172/172 [==============================] - 108s 627ms/step - loss: 0.0607 - accuracy: 0.9921 - val_loss: 0.5715 - val_accuracy: 0.8737 Epoch 37/70 172/172 [==============================] - 107s 622ms/step - loss: 0.0546 - accuracy: 0.9949 - val_loss: 0.5611 - val_accuracy: 0.8898 Epoch 38/70 172/172 [==============================] - 106s 618ms/step - loss: 0.0493 - accuracy: 0.9965 - val_loss: 0.5690 - val_accuracy: 0.8878 Epoch 39/70 172/172 [==============================] - 106s 615ms/step - loss: 0.0538 - accuracy: 0.9945 - val_loss: 0.5794 - val_accuracy: 0.8778 Epoch 40/70 172/172 [==============================] - 107s 620ms/step - loss: 0.0540 - accuracy: 0.9936 - val_loss: 0.5131 - val_accuracy: 0.8878 Epoch 41/70 172/172 [==============================] - 107s 621ms/step - loss: 0.0488 - accuracy: 0.9951 - val_loss: 0.5481 - val_accuracy: 0.8778 Epoch 42/70 172/172 [==============================] - 106s 617ms/step - loss: 0.0471 - accuracy: 0.9942 - val_loss: 0.5189 - val_accuracy: 0.8858 Epoch 43/70 172/172 [==============================] - 106s 613ms/step - loss: 0.0461 - accuracy: 0.9942 - val_loss: 0.5638 - val_accuracy: 0.8737 Epoch 44/70 172/172 [==============================] - 106s 616ms/step - loss: 0.0435 - accuracy: 0.9958 - val_loss: 0.5443 - val_accuracy: 0.8878 Epoch 45/70 172/172 [==============================] - 106s 615ms/step - loss: 0.0424 - accuracy: 0.9958 - val_loss: 0.5390 - val_accuracy: 0.8858 Epoch 46/70 172/172 [==============================] - 106s 614ms/step - loss: 0.0391 - accuracy: 0.9971 - val_loss: 0.5297 - val_accuracy: 0.8918 Epoch 47/70 172/172 [==============================] - 106s 615ms/step - loss: 0.0377 - accuracy: 0.9969 - val_loss: 0.5252 - val_accuracy: 0.8938 Epoch 48/70 172/172 [==============================] - 107s 620ms/step - loss: 0.0349 - accuracy: 0.9980 - val_loss: 0.5341 - val_accuracy: 0.8878 Epoch 49/70 172/172 [==============================] - 107s 622ms/step - loss: 0.0411 - accuracy: 0.9949 - val_loss: 0.5603 - val_accuracy: 0.8938 Epoch 50/70 172/172 [==============================] - 107s 620ms/step - loss: 0.0401 - accuracy: 0.9956 - val_loss: 0.5689 - val_accuracy: 0.8858 Epoch 51/70 172/172 [==============================] - 107s 623ms/step - loss: 0.0376 - accuracy: 0.9956 - val_loss: 0.5428 - val_accuracy: 0.8898 Epoch 52/70 172/172 [==============================] - 107s 624ms/step - loss: 0.0363 - accuracy: 0.9962 - val_loss: 0.5107 - val_accuracy: 0.8878 Epoch 53/70 172/172 [==============================] - 108s 625ms/step - loss: 0.0352 - accuracy: 0.9962 - val_loss: 0.5066 - val_accuracy: 0.9038 Epoch 54/70 172/172 [==============================] - 108s 626ms/step - loss: 0.0285 - accuracy: 0.9982 - val_loss: 0.5585 - val_accuracy: 0.8938 Epoch 55/70 172/172 [==============================] - 107s 623ms/step - loss: 0.0293 - accuracy: 0.9974 - val_loss: 0.5565 - val_accuracy: 0.8978 Epoch 56/70 172/172 [==============================] - 107s 623ms/step - loss: 0.0316 - accuracy: 0.9967 - val_loss: 0.5535 - val_accuracy: 0.8998 Epoch 57/70 172/172 [==============================] - 107s 621ms/step - loss: 0.0316 - accuracy: 0.9963 - val_loss: 0.5298 - val_accuracy: 0.8938 Epoch 58/70 172/172 [==============================] - 107s 621ms/step - loss: 0.0269 - accuracy: 0.9991 - val_loss: 0.5292 - val_accuracy: 0.8838 Epoch 59/70 172/172 [==============================] - 107s 622ms/step - loss: 0.0271 - accuracy: 0.9978 - val_loss: 0.5850 - val_accuracy: 0.8858 Epoch 60/70 172/172 [==============================] - 107s 623ms/step - loss: 0.0280 - accuracy: 0.9973 - val_loss: 0.5839 - val_accuracy: 0.8758 Epoch 61/70 172/172 [==============================] - 107s 620ms/step - loss: 0.0291 - accuracy: 0.9969 - val_loss: 0.5906 - val_accuracy: 0.8858 Epoch 62/70 172/172 [==============================] - 107s 620ms/step - loss: 0.0267 - accuracy: 0.9984 - val_loss: 0.5301 - val_accuracy: 0.8958 Epoch 63/70 172/172 [==============================] - 107s 619ms/step - loss: 0.0238 - accuracy: 0.9978 - val_loss: 0.5541 - val_accuracy: 0.8938 Epoch 64/70 172/172 [==============================] - 106s 614ms/step - loss: 0.0225 - accuracy: 0.9987 - val_loss: 0.5440 - val_accuracy: 0.8918 Epoch 65/70 172/172 [==============================] - 106s 616ms/step - loss: 0.0217 - accuracy: 0.9989 - val_loss: 0.5507 - val_accuracy: 0.8998 Epoch 66/70 172/172 [==============================] - 108s 625ms/step - loss: 0.0197 - accuracy: 0.9989 - val_loss: 0.5542 - val_accuracy: 0.8818 Epoch 67/70 172/172 [==============================] - 107s 620ms/step - loss: 0.0214 - accuracy: 0.9980 - val_loss: 0.5391 - val_accuracy: 0.8978 Epoch 68/70 172/172 [==============================] - 107s 619ms/step - loss: 0.0233 - accuracy: 0.9982 - val_loss: 0.5311 - val_accuracy: 0.9058 Epoch 69/70 172/172 [==============================] - 107s 623ms/step - loss: 0.0226 - accuracy: 0.9980 - val_loss: 0.5170 - val_accuracy: 0.8858 Epoch 70/70 172/172 [==============================] - 107s 624ms/step - loss: 0.0214 - accuracy: 0.9984 - val_loss: 0.5350 - val_accuracy: 0.8938
70Epoch 学習させました。結構時間がかかりましたね。 val_loss が最小になっているのは 53Epoch 目の loss: 0.0352 – accuracy: 0.9962 – val_loss: 0.5066 – val_accuracy: 0.9038 ですので、このモデルが自動で保存されました。では、このモデルを実際に使ってみましょう。
使ってみる
上で学習し、自動的に保存されたモデルは model_with_inceptionv3.h5 という名前で保存されています。これを読み込みましょう。
from tensorflow.keras.models import load_model
model = load_model('model_with_inceptionv3.h5')
まず、試しに検証用データとして用いたモバマスの画像で確認してみましょう。
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import cv2
from matplotlib import pyplot as plt
import pandas as pd
import numpy as np
data_generator = ImageDataGenerator(rescale=1./255, validation_split=0.1).flow_from_directory(
'data/face',
target_size=(256, 256),
class_mode='categorical',
batch_size=1,
subset='validation',
seed=1
)
for _ in range(5):
image = data_generator.next()
plt.axis("off")
plt.imshow(image[0][0])
plt.show()
indices_to_class = {val: key for key, val in data_generator.class_indices.items()}
pred = np.array(sorted([(indices_to_class[i], x) for i, x in enumerate(model.predict(image[0])[0])], key=lambda x: -x[1]))
display(pd.DataFrame({
'名前': pred[:5].T[0],
'推測値': pred[:5].T[1],
}))
いい感じに分類できていますね。次に、確認用のデータを用意します。学習に使用しなかったデレステのカードを引っ張ってきて、 data/test/original/ に保存しておきます。今回は顔が複数検出されることが想定できるので、各カードごとにフォルダを作り、検出された顔を全て保存するようにしてあります。
import cv2
import glob
import os
import shutil
cascade = cv2.CascadeClassifier('lbpcascade_animeface.xml')
load_img_list = []
face_cut = []
count = 0
data_face_path = 'data/test/face'
shutil.rmtree(data_face_path)
os.mkdir(data_face_path)
files = glob.glob('data/test/original/*.*')
for file in files:
file_name = file.split('/')[3]
original = cv2.imread(file)
face_rects = cascade.detectMultiScale(original, scaleFactor=1.11)
if(len(face_rects) != 0):
os.mkdir(f'{data_face_path}/{file_name.split(".")[0]}')
for face_rect in face_rects:
count += 1
x = face_rect[0]
y = face_rect[1]
w = face_rect[2]
h = face_rect[3]
face = original[y:y + h, x:x + w]
face = cv2.resize(face, dsize =(256, 256))
cv2.imwrite(f'{data_face_path}/{file_name.split(".")[0]}/{count}.jpg', face)
続けて、生成した画像を先程のモデルに通してみます。これらの画像は学習に直接用いてはいないので、正確に分類できていれば嬉しいですね。
data_generator2 = ImageDataGenerator(rescale=1./255).flow_from_directory(
'data/test/face',
target_size=(256, 256),
batch_size=1,
seed=1
)
print()
indices_to_class = {val: key for key, val in data_generator.class_indices.items()}
indices_to_class_2 = {val: key for key, val in data_generator2.class_indices.items()}
for _ in range(count):
image = data_generator2.next()
print(indices_to_class_2[np.where(image[-1][0]==1)[0][0]])
plt.axis("off")
plt.imshow(image[0][0])
plt.show()
pred = np.array(sorted([(indices_to_class[i], x) for i, x in enumerate(model.predict(image[0])[0])], key=lambda x: -x[1]))
display(pd.DataFrame({
'名前': pred[:5].T[0],
'推測値': pred[:5].T[1],
}))
結果
いい感じですね。他のカードでも試してみましょう。
橘ありすさんと結婚させていただくことになりました、こーちゃんです。これからも日々精進し、彼女を幸せにしてみせます。今後ともよろしくお願いいたします。
どんどん行きましょう。次はこのカードです。
佐城雪美さんと結婚させていただくことになりました、こーちゃんです。これからも日々精進し、彼女を幸せにしてみせます。今後ともよろしくお願いいたします。
ということで、誤検出です。これは成宮由愛ちゃんのはずですが、榊原里美ちゃんであると認識してしまいました。確かに似ている気もする。試しに、由愛ちゃんのカードと里美ちゃんのカードを持ってきて食わせてみましょう。
圧倒的なスコアで由愛ちゃんになりましたが、2番目の予測結果は里美ちゃんになっていますね。
なるほど、こちらも予測結果に由愛ちゃんがいますが、数値としてはかなり小さいですね。これは勘なのですが、瞳の色あたりが関係している気がします。残念ながら、デレステのカードで目を瞑っているものが2人とも無かったため、今回の真相は不明です。
他の似ているアイドルでも試してみましょう。
やはり十時愛梨さんが上位に出てきました。数値も結構大きいです。この2人は似ているらしいです。一緒に検出された智絵里ちゃんの結果も貼っておきます。道明寺歌鈴さんは顔が検出されませんでした。かなしいね。
十時愛梨さんの方も試してみましょう。
こちらは完全に十時愛梨さんですね。今回記事を書いていて気付いたのですが、目尻の形が違うのでそこで見分けられそうですね。
某双子でも試してみましょうか。とはいっても、彼女たちは瞳の色が違うのでカラーで学習している今回はかなり簡単に見分けられそうです。
このように正しく推測できています。お互いの結果Top5にお互いが存在するの、いいですね。あと凪と依田は似ているらしいです。
おわりに
さて、今回はアイドルの顔面をCNNに食わせて調教してきました。あまり丁寧な実装はしていないのですが、それでもこれだけの精度を出すことができました。今後の課題としましては、
1. 各種ハイパーパラメータを調整し、より精度を上げる
2. グレースケールで学習させる
3. デレマス以外のアイドルでも学習してみる
4. 学習結果をもとに、似ているアイドルランキングを作る
等が考えられます。また、発見もありました。
1. 成宮由愛ちゃんと榊原里美ちゃんは似ている
2. 高森藍子さんと十時愛梨さんは案の定似ている
3. 久川姉妹は意外と見分けがつく
4. 橘ありすさんと結婚した
5. 佐城雪美さんと結婚した
みなさんも是非CNNを利用した画像分類に挑戦してみてください!
明日はねねこさんの記事になります。また、他の記事は AdC2021 から確認できますので、是非読んでみてください。明日もお楽しみに!
おまけ
こちらは[オフタイム・ナギルーム]久川凪(特訓前)なのですが、顔が3件検出されたので結果を報告させていただきます。
凪の部屋には幸子と輝子の生霊が居るらしいです。おばけ要素は小梅ということにして、カワイイボクと142’sが揃いました。対戦ありがとうございました。
ディスカッション
コメント一覧
まだ、コメントがありません