Movatterモバイル変換


[0]ホーム

URL:


bohemia日記

おうちハックとか画像処理、DeepLearningなど

この広告は、90日以上更新していないブログに表示しています。

ディープラーニングでおそ松さんの六つ子は見分けられるのか 〜実施編〜

前回、おそ松さんたちをディープラーニングで見分けるため、準備編としておそ松さんたちの顔画像を5644枚集めました。今回はそれを用いて、ディープラーニングで学習させ、判別器を作って検証します。

集めた画像

人物枚数
おそ松1126f:id:bohemian916:20151121163835p:plain:w100
から松769f:id:bohemian916:20151121163952p:plain:w100
チョロ松1047f:id:bohemian916:20151121164053p:plain:w100
一松736f:id:bohemian916:20151121164114p:plain:w100
十四松855f:id:bohemian916:20151121164151p:plain:w100
とど松729f:id:bohemian916:20151121164220p:plain:w100
その他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を使わせていただきました。f:id:bohemian916:20151122172627p:plain

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からf:id:bohemian916:20151122142816p:plain訓練データでのエラー率は数%なので、ちゃんと認識できています。

もう何枚かf:id:bohemian916:20151122151617p:plain

f:id:bohemian916:20151122143902p:plain

訓練データ話数外より

問題は、訓練データに含まれていない話数の顔が認識できるかどうかです。5話の途中までを訓練データで用いたので、6話から抽出して解析してみます。

おじいちゃんの執事に一松と間違えられる十四松。ちゃんと十四松として認識されています! 画像見るだけじゃ自分もわからないのにすごい!f:id:bohemian916:20151122150619p:plain

かと思えば、一松をとど松として認識しています。f:id:bohemian916:20151122145336p:plain

誤認識部分のスコアを見てみると、
todomatsu score:56.3%
karamatsu score:37.9%
ichimatsu score: 3.8%
osomatsu score: 1.9%
jushimatsu score: 0.2%
となっています。他の正確に認識できている顔のスコアはほぼ100%なので、それに比べれば確かさは低くなってはいます。他の結果を見る限り、特徴の少ない画像は、とど松のスコアが高くなる傾向がある気がします。

こちらは全員ちゃんと認識されています。f:id:bohemian916:20151122150146p:plain

一松以外みんなとど松になっています。f:id:bohemian916:20151122150301p:plain

から松だらけ。自分も見分けやすい一松と十四松は正解率高め。f:id:bohemian916:20151122163634p:plain

やってみて

正直、学習させてもあまり判別できないんじゃないかと懸念してたので、思ってたよりはちゃんと見分けられているという感想です。人の目で見て分かりづらい顔画像は、おそらく

  • 画像における見分けられる顔の特徴が少ない
  • 見分けるポイントがわかっていない

のどちらかだと思います。前者は、そもそもちゃんと書き分けてない場合で、ディープラーニングで解析しても判断しづらいのでしょう。後者は、ちゃんと描き分けてるけど、人がそれを理解できていない場合です。この場合は、ディープラーニングが特徴を見つけ出して判別できる、ということだと思います。

精度については、まだ改善の余地はあるかと思います。学習データである画像の質をあげる必要があります。キャプチャから切り取っていると、どうしても動かないキャラはずっと同じ画像になってしまうので、実質枚数があまり多くありません。例えば6人いる状態で、60フレームほどチョロ松のみが喋ったとします。すると60フレーム分切り取ると360枚分の顔画像が生成されるのですが、295枚は重複画像です。またチョロ松の60枚も、喋っているコマはせいぜい口閉じ、口半開け、口開けの3〜4種類程度しか存在しません。なので360枚中、実質9種類程度しかありません。 今回の画像収集で極力同一コマの画像は避けていたのですが、それでも多いです。ですので、同じ画像は排除し、一つの画像からノイズをかけたり各種フィルタをかけて新たに画像を生成するのがよいかと思います。

また前記事で生成したおそ松さん用顔検出器の精度もあまりよくなく、取り逃がしている写真が結構あります。ですので、検出器の精度を向上させるための顔画像選定も必要になってきます。もう少し精度向上に向けて頑張ります。

参考

PFN発のディープラーニングフレームワークchainerで画像分類をするよ(chainerでニューラルネット1)

ChainerのNINで自分の画像セットを深層学習させて認識させる

Googleの公開した人工知能ライブラリTensorFlowを触ってみた

引用をストックしました

引用するにはまずログインしてください

引用をストックできませんでした。再度お試しください

限定公開記事のため引用できません。

読者です読者をやめる読者になる読者になる

[8]ページ先頭

©2009-2026 Movatter.jp