この広告は、90日以上更新していないブログに表示しています。
前回、おそ松さんたちをディープラーニングで見分けるため、準備編としておそ松さんたちの顔画像を5644枚集めました。今回はそれを用いて、ディープラーニングで学習させ、判別器を作って検証します。
| 人物 | 枚数 | 例 |
|---|---|---|
| おそ松 | 1126 | ![]() |
| から松 | 769 | ![]() |
| チョロ松 | 1047 | ![]() |
| 一松 | 736 | ![]() |
| 十四松 | 855 | ![]() |
| とど松 | 729 | ![]() |
| その他 | 383 |
最近GoogleからTensorFlowという新しいディープラーニングのフレームワークが発表されました。会社のブログに使い方書いたのですが、まだ慣れていないので、今回はchainerを使います。こちらだとすぐに高い成果を上げているImageNetのNINモデル、4層畳み込みニューラルネットワークがサンプルで入っていますので、こちらを改良して使います。
imageNetの使い方は、こちらやこちらを参考にしています。
ImageNetで学習を行うには、train.txtとtest.txtの2つが必要です。どちらも
/path/to/image/hoge1.jpg 1/path/to/image/hoge2.jpg 5/path/to/image/hoge3.jpg 2/path/to/image/hoge4.jpg 4
という、画像パス名 ラベル番号 といった形式をしています。
集めた画像のうち、85%を学習データ、15%をテスト用データにします。
基本的には、ImageNetのソースを使いますが、いくつか変更を施します。まずGPUで学習させたあとに生成されるモデルをCPUでも使えるようにして保存します。これはtrain_imagenet.pyの最終モデルを保存するときに、cpuで使える形式にしてから保存します。
# 最終行を変更# Save final modelmodel.to_cpu()pickle.dump(model, open(args.out, 'wb'), -1)
また、nin.pyには直接解析結果を返してくれるメソッドがないので、スコアをソフトマックス関数で処理した結果を返すメソッドを追加します。こちらを参考にしました
def predict(self, x_data, train=False): x = chainer.Variable(x_data, volatile=True) h = F.relu(self.conv1(x)) h = F.relu(self.conv1a(h)) h = F.relu(self.conv1b(h)) h = F.max_pooling_2d(h, 3, stride=2) h = F.relu(self.conv2(h)) h = F.relu(self.conv2a(h)) h = F.relu(self.conv2b(h)) h = F.max_pooling_2d(h, 3, stride=2) h = F.relu(self.conv3(h)) h = F.relu(self.conv3a(h)) h = F.relu(self.conv3b(h)) h = F.max_pooling_2d(h, 3, stride=2) h = F.dropout(h, train=train) h = F.relu(self.conv4(h)) h = F.relu(self.conv4a(h)) h = F.relu(self.conv4b(h)) h = F.reshape(F.average_pooling_2d(h, 6), (x_data.shape[0], 1000)) return F.softmax(h)
それでは平均画像を生成してから、学習を開始します。画像は、サイズを256*256にする必要があるため、こちらのcrop.pyを使ってサイズ調整を行いました。
$ ./crop.py /path/to/image/directory /path/to/image/directory$ ./compute_mean.py train.txt--root /path/to/image/directory$ ./train_imagenet.py train.txt test.txt--batchsize14--val_batchsize80--epoch50--gpu0--root /path/to/image/directory
ログデータから訓練データにおけるエラー率の推移はこのようになりました。プロットはこちらのplot.pyを使わせていただきました。
10000イテレーションあたりでほとんど下がりきっています。
trainでの最終的なエラーレートは0.02857
valでは0.0360です。96.4%の精度がでています。
上記で作成した顔の分類器(ディープラーニング部)と、前記事で作った顔検出器(HOG+SVM)を用いて、キャプチャ画像から認識できるようにします。処理手順としては、
1, 画像サイズを調整する
2, 顔検出器で顔を切り抜く
3, 切り抜いた顔を分類器にかける
4, 元画像に結果を表示する
というようになります。以下がそのソースです。
#!/usr/bin/env python#! -*- coding: utf-8 -*-import osimport sysimport dlibfrom skimage import ioimport numpy as npimport cv2import argparseimport osfrom PIL import Imageimport siximport cPickle as picklefrom six.moves import queueparser = argparse.ArgumentParser( description='Learning convnet from ILSVRC2012 dataset')parser.add_argument('image', help='Path to image')parser.add_argument('--mean', '-m', default='mean.npy', help='Path to the mean file (computed by compute_mean.py)')parser.add_argument('--detector', '-d', default='detector.svm', help='Path to the detector file ')parser.add_argument('--model', '-mo', default='model', help='Path to the model file')args = parser.parse_args()# ファイル読み込みdetector = dlib.simple_object_detector(args.detector)img = cv2.imread(args.image,1)import ninmodel = pickle.load(open(args.model,'rb'))mean_image = pickle.load(open(args.mean, 'rb'))categories = np.loadtxt("labels.txt", str, delimiter="\t")cropwidth = 256 - model.insize out = img.copy()# 画像サイズ変更def resize(img): target_shape = (256, 256) height, width, depth = img.shape print "height:"+str(height) + "width:" + str(width) output_side_length=256 new_height = output_side_length new_width = output_side_length if height > width: new_height = output_side_length * height / width else: new_width = output_side_length * width / height resized_img = cv2.resize(img, (new_width, new_height)) height_offset = (new_height - output_side_length) / 2 width_offset = (new_width - output_side_length) / 2 cropped_img = resized_img[height_offset:height_offset + output_side_length, width_offset:width_offset + output_side_length] return cropped_imgdef read_image(src_img, center=True, flip=False): # Data loading routine image = np.asarray(Image.open(src_img)).transpose(2, 0, 1) #image = src_img.transpose(2, 0, 1) if center: top = left = cropwidth / 2 else: top = random.randint(0, cropwidth - 1) left = random.randint(0, cropwidth - 1) bottom = model.insize + top right = model.insize + left image = image[:, top:bottom, left:right].astype(np.float32) image -= mean_image[:, top:bottom, left:right] image /= 255 return image# 顔検出dets = detector(img)print "faces:" + str(len(dets))height, width, depth = img.shapex = np.ndarray((len(dets), 3, model.insize, model.insize), dtype=np.float32)faces = [() for i in range(len(dets))]for i,d in enumerate(dets): # 顔領域の調整 f_top = max((0, d.top())) f_bottom = min((d.bottom(), height -1)) f_left = max((0, d.left())) f_right = min((d.right(), width -1)) print "%d %d %d %d" %(f_top, f_bottom, f_left, f_right) faces[i] = (f_top, f_bottom, f_left, f_right) face_img = img[f_top:f_bottom, f_left:f_right] resized_face = resize(face_img) cv2.imwrite("temp.jpg", resized_face) x[i] = read_image("temp.jpg")# 顔の分類スコア取得scores = model.predict(x)# 結果表示face_info = []for i,face in enumerate(faces): prediction = zip(scores.data[i], categories) prediction.sort(cmp=lambda x, y: cmp(x[0], y[0]), reverse=True) score, name = prediction[0] for j in range(6): print ('%s score:%4.1f%%' % (prediction[j][1], prediction[j][0] * 100)) if name == "osomatsu": color = (0,0,255) elif name == "karamatsu": color = (255,0,0) elif name == "choromatsu": color = (0,255,0) elif name == "ichimatsu": color = (133,22, 200) elif name == "jushimatsu": color = (0, 255,255) elif name == "todomatsu": color = (167, 160, 255) else : color = (255,255,255) cv2.rectangle(out, (face[2], face[0]), (face[3], face[1]), color, 3) cv2.putText(out,"%s" %(name),(face[2],face[1]+15),cv2.FONT_HERSHEY_COMPLEX, 0.5 ,color) cv2.putText(out,"%4.1f%%" %( score*100),(face[2],face[1]+30),cv2.FONT_HERSHEY_COMPLEX, 0.5 ,color)cv2.imshow('image',out)cv2.waitKey(0)cv2.destroyAllWindows()まずは訓練データに含まれているのもから試してみます。認識結果は、それぞれのカラーで顔を囲うようにします。
もう一度確認しておくと、おそ松、から松、チョロ松、一松、十四松、とど松、です。
OPから
訓練データでのエラー率は数%なので、ちゃんと認識できています。
もう何枚か

問題は、訓練データに含まれていない話数の顔が認識できるかどうかです。5話の途中までを訓練データで用いたので、6話から抽出して解析してみます。
おじいちゃんの執事に一松と間違えられる十四松。ちゃんと十四松として認識されています! 画像見るだけじゃ自分もわからないのにすごい!
かと思えば、一松をとど松として認識しています。
誤認識部分のスコアを見てみると、
todomatsu score:56.3%
karamatsu score:37.9%
ichimatsu score: 3.8%
osomatsu score: 1.9%
jushimatsu score: 0.2%
となっています。他の正確に認識できている顔のスコアはほぼ100%なので、それに比べれば確かさは低くなってはいます。他の結果を見る限り、特徴の少ない画像は、とど松のスコアが高くなる傾向がある気がします。
こちらは全員ちゃんと認識されています。
一松以外みんなとど松になっています。
から松だらけ。自分も見分けやすい一松と十四松は正解率高め。
正直、学習させてもあまり判別できないんじゃないかと懸念してたので、思ってたよりはちゃんと見分けられているという感想です。人の目で見て分かりづらい顔画像は、おそらく
のどちらかだと思います。前者は、そもそもちゃんと書き分けてない場合で、ディープラーニングで解析しても判断しづらいのでしょう。後者は、ちゃんと描き分けてるけど、人がそれを理解できていない場合です。この場合は、ディープラーニングが特徴を見つけ出して判別できる、ということだと思います。
精度については、まだ改善の余地はあるかと思います。学習データである画像の質をあげる必要があります。キャプチャから切り取っていると、どうしても動かないキャラはずっと同じ画像になってしまうので、実質枚数があまり多くありません。例えば6人いる状態で、60フレームほどチョロ松のみが喋ったとします。すると60フレーム分切り取ると360枚分の顔画像が生成されるのですが、295枚は重複画像です。またチョロ松の60枚も、喋っているコマはせいぜい口閉じ、口半開け、口開けの3〜4種類程度しか存在しません。なので360枚中、実質9種類程度しかありません。 今回の画像収集で極力同一コマの画像は避けていたのですが、それでも多いです。ですので、同じ画像は排除し、一つの画像からノイズをかけたり各種フィルタをかけて新たに画像を生成するのがよいかと思います。
また前記事で生成したおそ松さん用顔検出器の精度もあまりよくなく、取り逃がしている写真が結構あります。ですので、検出器の精度を向上させるための顔画像選定も必要になってきます。もう少し精度向上に向けて頑張ります。
PFN発のディープラーニングフレームワークchainerで画像分類をするよ(chainerでニューラルネット1)
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。