AKB48の顔認識に挑戦してみました。
人の目には認識の難しい(と言ったら怒られるか?)メンバーの認識も機械学習なら出来るのか?というチャレンジです。
学習用画像の収集
学習用画像はgoogle画像検索を用いて収集しました。メンバーはいきなり48人に挑戦するのは、CPU的に過酷と推定されるので、10人程度集めることにしました。
Wikipediaを参考に総選挙の上位のメンバーを中心に13人に絞り収集しました。
def google_image_selenium(keyword): url = "https://www.google.com/search?q=" + keyword + "&source=lnms&tbm=isch&sa=X&ved=0ahUKEwjRpbL78LTbAhXIfrwKHTaVBLQQ_AUICigB&biw=1280&bih=620" driver=browserOpen("h") #ブラウザのインスタンスを起動 driver.get(url) html=driver.page_source soup = BeautifulSoup(html, 'lxml') ##lxmlを指定するほうがよい for link in soup.find_all("img"): try: image_link="" if "src" in link.attrs: image_link=link.attrs["src"] elif "data-src" in link.attrs: image_link = link.attrs["data-src"] if "http" in image_link: print(image_link) data = urllib.request.urlopen(image_link).read() tdatetime = dt.now() filename=keyword+'/'+tdatetime.strftime('%Y%m%d%H%M%S')+'.jpg' with open(filename, mode="wb") as f: f.write(data) time.sleep(1) except urllib.error.URLError as e: print(e) if __name__ == '__main__': menber_list = ["Kojima Mako","Yokoyama Yui", "Hashimoto Haruna", "Yokoyama Yui", "Kashiwagi Yuki", "Mutō Tomu", "Iriyama Anna","Takahashi Juri", "Megu Taniguchi", "Okada Nana", "Kojima Mako", "Miyazaki Miho","Kato Rena","Sasaki Yukari","Ōmori Miyū"] for keyword in menber_list: savedir = "./" + keyword if not os.path.exists(savedir): os.mkdir(savedir) # フォルダーを作る google_image_selenium(keyword)
ブラウザのインスタンスは以前の記事参照。
s51517765.hatenadiary.jp
ここでは、普通のPCとCPUを使っているので、読み込ませる画像をなるべく小さくしたいので、Googleのサムネイルをそのまま取得しました。
高解像度で実施したい場合は、このサムネイルからLinkを取得してLink先の画像を取得する必要があります。
明らかに別人だったり、人でなかったり、他の人が写りこんでいたりする写真は削除してメンバー当たり80枚ぐらい集めました。
google画像検索結果のhtmlからimageのリンクを取得しますが、urllibでは少ししか取れなかったので、seleniumを使用しました。
seleniumで画面スクロールを入れてからhtmlを取得すればさらにたくさん取得することも可能だと思います。
顔の切り取りと水増し
次にOpen CVで顔検出をして、顔の部分を切り取ります。note.nkmk.me
顔認識なんてこれでもすでにAIといっても通用するぐらいだと思うが、これがフリーで使えるとはすごい!
顔認識は最近のデジカメなんか必ず入っている技術ですね。
顔検出したものを切り取って、水増しのために左右に18度づつ振った画像を作成しました。画像の回転はOpen cvまたはPILでできます。PILでのコードは以前のAIプログラマ養成ギプスの記事参照下さい。
なぜわからないのですが、open cvで開けないファイルが結構ありました。
原因を調べたいところだが、長くなりそうなのでとりあえずあきらめました。
これによって、サンプル数が少なくなったメンバーは除外して、最低120枚得られたのが、11人になりました。
OpenCV(3.4.1) Error: Assertion failed (scn == 3 || scn == 4) in cv::cvtColor, file C:\projects\opencv-python\opencv\modules\imgproc\src\color.cpp, line 11147
ディープラーニングで学習
これをMnistの認識で使ったプログラムの改造で学習させます。s51517765.hatenadiary.jp
Mnistでは画像を数値にしたデータを入力として使えたが、今度は普通の画像ファイルなので、同じようなnumpyの配列に変換する必要があります。
globというメソッドを用いて、フォルダのファイルにアクセスし、フォルダをカテゴリとして、ファイル(picture)を読み込み、X-trainという配列に加えていきます。
filelist = glob.glob("./*") for picture in filelist: img = img_to_array(load_img(picture, target_size=(pixel, pixel))) X_train.append(img)
これをMnistと同じニューラルネットワークに入れてみました。
model = Sequential() model.add(Dense(64, activation='relu', input_dim=pixel*pixel*3)) model.add(Dense(len(inputlist), activation='softmax')) model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy']) model.fit(x_train, y_train, batch_size=100, epochs=30, verbose=1) # データを学習
>>> acc: 0.23636363636363636
→約23%
入出力が多くなったので、中間層を増やすといいのではないかとおもい、一層増やしてみます。
model = Sequential() model.add(Dense(512, activation='relu', input_dim=pixel*pixel*3)) model.add(Dense(64, activation='relu', input_dim=512)) model.add(Dense(64, activation='relu', input_dim=64)) model.add(Dense(len(inputlist), activation='softmax')) model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy']) model.fit(x_train, y_train, batch_size=100, epochs=30, verbose=1) # データを学習
>>> acc: 0.33560606060606063
→約34%
もう一層増やしても、ほとんど変わらなかった。
ディープラーニングで学習 その2
違うモデルも試してみようと思います。Kerasの公式サイトより、”VGG-likeなconvnet” を試してみます。
こちらは「畳み込み」をしているのが特徴です。Conv2Dのところ。
これによって、ぼかしやエッジ強調の効果が入ることで画像認識に強くなるそうです。
このサンプルコードにaccuracyの表示を追加して使用しました。
Sequentialモデルのガイド - Keras Documentation
model = Sequential() # 入力: サイズが100x100で3チャンネルをもつ画像 -> (100, 100, 3) のテンソル # それぞれのlayerで3x3の畳み込み処理を適用している model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(40, 40, 3))) model.add(Conv2D(32, (3, 3), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.25)) model.add(Conv2D(64, (3, 3), activation='relu')) model.add(Conv2D(64, (3, 3), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(256, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(11, activation='softmax')) sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True) model.compile(loss='categorical_crossentropy', optimizer=sgd,metrics=['accuracy']) #accuracy を表示するのを追加 model.fit(x_train, y_train, batch_size=32, epochs=10) os.chdir(root) model.save_weights("akb.hdf5") #学習結果を保存 #score = model.evaluate(x_test, y_test, batch_size=32) score = model.evaluate(x_test, y_test, verbose=0) #score = model.evaluate(x_test, y_test) print(score[0]) print(score[1])
>>> Loss 2.19148811860518 >>>acuracy 0.3
→30%
Dropoutを0.1に変更
>>> Loss 1.79073445124523 >>>acuracy 0.39099876634567
→約39%
Dropoutを0.0に変更
>>> Loss 1.8360017863186924 >>>acuracy 0.4090909094973044
→約41%
あまり変わらないのでこの間をとって、
Dropout=0.05にさらにepochs =30に。
>>> loss: 1.9628488020463424 >>> accuracy: 0.681818183443763
このときの学習履歴をグラフにすると、学習結果は98%まで行っているがテストは68%となり、過学習になっていると推定されます。
繰り返し(epochs)は15ぐらいでいいのではないか?と思われます。
とりあえず、この(過学習気味の)学習データを用いて「中締め」として評価してみます。
評価
Twitterから適当な画像を探し、ディープニューラルネットワークに入れてみます。学習に使ったときと同様に、顔認識と切り取り、リサイズをして入力します。
Prediction is Hashimoto Haruna【正解】
Prediction is Iriyama Anna【正解】
Prediction is Hashimoto Harun【不正解】→Kojima Mako
Prediction is Megu Taniguchi【不正解】→Miyazaki Miho
Prediction is Okada Nana【不正解】→Megu Taniguchi
Prediction is Sasaki Yukari【不正解】→Yokoyama Yui
Prediction is Kashiwagi Yuki【正解】
Prediction is Kato Rena【正解】
Prediction is Takahashi Juri【不正解】→Sasaki Yukari
Prediction is Okada Nana【正解】
まとめ
テスト結果は5勝5敗で50%でした。そうはいっても、Kerasを使うことで簡単にディープラーニングができました。
最適化には経験が必要なのではないかと思いますが、普通のPCでもここまでできるとは思いませんでした。
最大で300epochsを学習させましたが、これでも15分程度でした。
これで、11分の1の分類が50%の精度なら遊びとしては十分すぎますね。
最終的なディープラーニングコード
from keras.utils.np_utils import to_categorical from keras.models import Sequential from keras.layers import Dense import os import glob from keras.preprocessing.image import array_to_img, img_to_array, list_pictures, load_img import numpy as np from keras.layers import Conv2D, MaxPooling2D from keras.layers import Dense, Dropout, Flatten from keras.optimizers import SGD #顔認識のための import cv2 face_cascade_path = 'C:/Users/***/Anaconda3/Lib/site-packages/cv2/data' cascade_fie='haarcascade_frontalface_default.xml' face_cascade_path=face_cascade_path +"/"+cascade_fie face_cascade = cv2.CascadeClassifier(face_cascade_path) root = "C:/Users/***/Projects/Akb48" pixel = 40 input_image_count=110 #max120 test_image_count=10 def model_build(): X_train,Y_train=[],[] X_test,Y_test=[],[] inputlist = ["OutHashimoto Haruna", "OutIriyama Anna", "OutKashiwagi Yuki", "OutKato Rena", "OutKojima Mako", "OutMegu Taniguchi", "OutMiyazaki Miho", "OutOkada Nana", "OutSasaki Yukari", "OutTakahashi Juri", "OutYokoyama Yui"] no=0 #正解ラベル print(len(inputlist)) for name in inputlist: print("No=",no, " ",name) image_count=0 os.chdir(root) folder = name os.chdir(folder) filelist = glob.glob("./*") for picture in filelist: img = img_to_array(load_img(picture, target_size=(pixel, pixel))) image_count += 1 if image_count <=input_image_count: X_train.append(img) Y_train.append(no) elif image_count <= input_image_count+test_image_count: #max 120 X_test.append(img) Y_test.append(no) elif image_count > input_image_count+test_image_count: #max 120 break no+=1 # arrayに変換 x_train = np.asarray(X_train) x_test = np.asarray(X_test) y_train = np.asarray(Y_train) y_test = np.asarray(Y_test) # 画素を0~1の範囲に変換(正規化) x_train = x_train.astype('float32') / 255 x_test = x_test.astype('float32') / 255 # 正解ラベルをone-hot-encoding y_train = to_categorical(y_train, 11) y_test = to_categorical(y_test, 11) # VGG-likeなconvnet model = Sequential() # それぞれのlayerで3x3の畳み込み処理を適用している model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(40, 40, 3))) model.add(Conv2D(32, (3, 3), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.05)) model.add(Conv2D(64, (3, 3), activation='relu')) model.add(Conv2D(64, (3, 3), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.05)) model.add(Flatten()) model.add(Dense(256, activation='relu')) model.add(Dropout(0.05)) model.add(Dense(11, activation='softmax')) sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True) model.compile(loss='categorical_crossentropy', optimizer=sgd,metrics=['accuracy']) model.fit(x_train, y_train, batch_size=32, epochs=15) os.chdir(root) model.save_weights("akb.hdf5") #学習結果を保存 score = model.evaluate(x_test, y_test, verbose=0) print("------------------------------") print("Test loss: ",score[0]) print("Test accuracy: ",score[1]) def model_read(): # モデルを読み込む model = Sequential() model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(40, 40, 3))) model.add(Conv2D(32, (3, 3), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.05)) model.add(Conv2D(64, (3, 3), activation='relu')) model.add(Conv2D(64, (3, 3), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.05)) model.add(Flatten()) model.add(Dense(256, activation='relu')) model.add(Dropout(0.05)) model.add(Dense(11, activation='softmax')) sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True) model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy']) # 学習済みモデルの重みのデータを読み込み model.load_weights('akb_300.hdf5') return model def check(file): try: src = cv2.imread(file) src_gray = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(src_gray) x_test=[] inputlist = ["Hashimoto Haruna", "Iriyama Anna", "Kashiwagi Yuki", "Kato Rena", "Kojima Mako","Megu Taniguchi", "Miyazaki Miho", "Okada Nana", "Sasaki Yukari", "Takahashi Juri", "Yokoyama Yui"] if len(faces) > 0: # 顔認識できたら for rect in faces: # 顔だけ切り出して保存 x = rect[0] y = rect[1] width = rect[2] height = rect[3] if width > 40 and height > 40: # 一定サイズに src = src[y:y + height, x:x + width] resize_src = cv2.resize(src, (40, 40)) cv2.imwrite("temp.jpg", resize_src) #なぜかうまくいかないので一旦保存して読み込む img = img_to_array(load_img("temp.jpg", target_size=(pixel, pixel))) model = model_read() x_test.append(img) x_test = np.asarray(x_test) # テストデータをセット x_test = x_test.astype('float32') / 255 # 判定 res = model.predict(x_test) print("res = ", res) y = res.argmax() # 値の中で最も値が大きいものが答え print(y) print("Prediction is ",inputlist[y]) else: print("Face recognition failed!") except: pass if __name__ == "__main__": #model_build() check("kashiwagi.jpg") check("kato.jpg") check("sasaki.jpg") check("okada.jpg")