ニートがプログラミングするブログ(はてな出張所)

ニートがプログラミングするブログです。今は主にコンピュータビジョンに関することをやっています。

ディープラーニングを使わない顔認識まとめ


sugyanさんのアイドルデータセットで99.6%程度出たのでまとめておきます。
精度としては1200枚中4枚しか間違わないレベルです。
ちなみに間違えた4枚は次の通りです。

f:id:suzuichiblog:20170125180125j:plain


1.データの水増し
基本的にデータ数は多いほうが良いのでちょっとしたテクニックを使って増やしておきます。
左右反転した画像もトレーニングデータに加えます。
またガンマ補正によって少し暗くしたり明るくした画像もトレーニングデータに加えます。

2.顔検出
顔検出ではdlibを使います。
OpenCVにも顔検出機能がありますがdlibの方が良いでしょう。
理由としてはOpenCVはインストールが面倒で、精度がdlibより低く、次のステップで使う顔の特徴点検出機能がない、という点が上げられます。
python版のdlibならばpip install dlibですぐに使えます。

3.顔の特徴点検出
目の端や鼻の頂点などの特徴点を検出しておきます。
dlibの場合はhttp://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2からdatファイルをダウンロードしておく必要があります。

4.顔の位置あわせ
画像のサイズや、顔の傾きなどが合っているほうが顔認識がしやすいので、回転・平行移動・拡縮をしておきます。
procrustes analysisや参考文献[1]のAppendix Dの方法で相似変換行列(similarity transform matrix)を求めます。
だいたい画像の一辺が230ピクセル程度になるようにしておきます。

5.パッチの切り取り
ステップ3で検出した特徴点の中から、輪郭を除いて適当に13箇所程度選びます。
その点を中心として50x50のパッチを切り取り30x30に縮小します。
ただしトレーニング時には60x60で切り取り36x36に縮小し、この領域内から30x30を切り出します。
こうする理由は特徴点の位置が多少ずれてもきちんと顔が認識できるようにしたいからです。

また各データごとにパッチごとに、チャネル別に平均を求めて、その値を引いておきます。

データの各値はあらかじめ255で割っておきます。

6.特徴量の抽出(DeepID)
DeepIDについては以前の記事を参考にしてください。
ただ今回はverificationは入れていません。
入れても入れなくてもあまり結果に影響しないみたいなので、計算量削減のため省きました。
また、畳み込み層と隠れ層の後の活性化関数をreluや恒等関数からeluに変更しました。
畳み込み層のカーネルサイズは3x3でパディングはありません。
チャネル数も入力層から近い順に10、20、30としています。
重みの初期値はXavierの方法にしました。
最適化手法はAdamを使いました。

特徴点ごとに300次元の特徴量が取れるので、13箇所の合計で3900次元の特徴ベクトルとなります。

この値は少し大きいので0.1を掛けておきます。

7.特徴量の加工
ステップ6で取れた特徴量をそのまま使用しても結構良い精度がでますが少し手を加えます。
具体的には特徴ベクトルの多くの値を0(スパース)にして、全ての値が0以上を取るようにします。
これを実現するために、入力層→隠れ層→出力層というネットワークを作りました。
入力は各特徴点で得られた300次元ベクトルです。
隠れ層も300次元で活性化関数にReLUを使います。
出力層は40人の分類問題なので40次元として、活性化関数にsoftmaxを使います。
ネットワークのトレーニング後に隠れ層の出力を新たな特徴量とします。
このネットワークを特徴点ごとに作るので、13個作ることになります。
新たに得られた300次元の特徴量を連結して3900次元にします。

この値は少し大きいので0.1を掛けておきます。

8.識別器のトレーニング
識別器は2クラスのsoftmax回帰としました。
実際は、片方の重みをwとしたときに、もう片方の重みが-wとなるようにしたsoftmax回帰で、これをlogistic回帰風に実装しました。
説明が下手なのでうまく実装方法を言えませんが、とりあえずsoftmaxにしとけば大丈夫です。
特に制約の無いsoftmax回帰を普通にやれば片方の重みはもう片方の重みにマイナスを掛けたものになると思います。
この識別器をクラス数分用意します。

9.テスト
以上のステップで作ったネットワークを使って、最終的に一番高い値を取ったクラスをその画像の人だとします。
ただしテストに使うのは元の画像とそれを左右反転した画像で、出力確率の和が一番大きいクラスをその画像の人だとします。

10.感想
なんとなく顔認識の勉強を始めて1、2年程度経ちました。
そして今回結構良い成果を出せたと思います。
とはいっても、まだまだ問題点があります。
一つ目は、一応40クラスの中では一番高いスコアを出していても、その値が1e-10のような小さい値を取っているものもあります。
つまり40人の内の誰でもないということになってしまっています。
二つ目は、遮蔽物や表情などの変化に対応できていない点です。
最初にあげた誤識別例のように、メガネやマスクなどで顔の一部が隠れる場合や、目を瞑ったり口をあけたりして顔の形が変わってしまう場合があります。
あるいは結構斜めを向いていたり、顔検出で顔がアップになりすぎたりして、特徴点がうまく検出できない場合もあります。
そういった状況になるとうまく認識が出来なくなってしまいます。
たぶん、こういった問題を解決する手法が出てくると思うので、そういった論文が出たらまた実装しようかと思います。

あまりに良い結果過ぎるのでプログラムミスがあるかもしれませんが、その場合はご容赦ください。

参考文献
[1]Cootes, Tim, E. Baldock, and J. Graham. "An introduction to active shape models." Image processing and analysis (2000): 223-248.