(この記事は手探りで書いてるので大いに勘違いを含んでいる可能性があります。ご了承ください。)
VGG16をベースとしたFaster R-CNNを使って顔検出を行うというテクニカルリポートがあった。
ここでは、WIDER FACEという顔検出用のデータセットの上でトレーニングを行っている。
これを再現してみたい*1。
手法
ChainerCVにFasterRCNNVGG16というそのものズバリのものがあるので、これを使ってトレーニングをしてみて、顔検出をやってみる。
今回はChainerCVの、Faster RCNNのサンプルコードchainercv/examples/faster_rcnn/を改変することによって行った。サンプルコードでは、PASCAL VOCデータセットに従ってトレーニングをし、20種類の物体が認識できる様だった。
ここでは、要するに、顔だけを検出するので、物体認識において検出できるクラスの数を1とすれば、とりあえずは動くはずである。
あんまりよくわかってないので、ハイパーパラメータはサンプルコードのものをほぼそのまま利用している。
環境
依存ライブラリ
- Chainer 2.0.0
- CuPy 1.0.0.1
- ChainerCV 0.5.1
- SciPy
ソース
github.com
なお、トレーニング済みのモデルはデータ容量が大きいのでリポジトリに含んでいないので、以下のリンクからダウンロードして trained_model/snapshot_model.npz に置くか、あるいは demo.py などを実行時に自動でダウンロードされるようになっている。
- モデルデータ: snapshot_model.npz
(2018-04-04追記)
chainercv v0.8.0ではトレーニング済みのモデルデータとして上の代わりに次のリンクのものを使う必要がある。
- 新モデルデータ: snapshot_model_20180404.npz
これは、chainercvがv0.6.0より後、v0.8.0までの間に、FasterRCNNVGG16の構造が変化してしまったからのようである。ついでにdemo.pyとtrain.pyをv0.8.0で動く様に修正した。
(追記終)
デモ
python demo.py [--gpu <gpu>] [--pretrained_model <model_path>] <image>.jpg
などとすれば、次のように顔と予測された矩形領域とスコアが表示される。
なお、faceと表示されてるのはPASCAL VOCの多クラス検出のコードを流用したためで、冗長になっている。
トレーニング手順
まず、顔検出用のWIDER FACEデータセットをダウンロードする。
ここから
- Wider Face Training Images
- Wider Face Validation Images
- Face annotations
をダウンロードしてきて展開する。
そして、次のようにトレーニング向け/評価向けデータセットの、画像ファイルの含まれるフォルダとアノテーションデータのパスを指定して train.py を実行する。
python train.py --gpu=0 --train_data_dir="WIDER_train" --train_annotation="wider_face_split/wider_face_train.mat" --val_data_dir="WIDER_val" --val_annotation="wider_face_split/wider_face_val.mat"
やや細かい説明
chainercv/examples/faster_rcnn/のサンプルでは、Pascal VOCデータセットで物体認識をトレーニングしているので、ここで使われいてるVOCDetectionDatasetと互換性のある出力をするデータセットを、WIDER FACEデータセットを読み込ませるに際して用意している。
これが wider_face_dataset.py である。chainercv/chainercv/datasets/voc/voc_detection_dataset.pyを改変して作成した。
さて、このデータセットの上でトレーニングしたところ、RuntimeErrorが出たので、エラーが出た画像ファイルをトレーニング時に使用しない様に抜くようにしている。エラーがでたファイルは blacklist.txt にリストアップしてある。抜かない場合、トレーニング途中でlossがnanになってしまい失敗した。
なお、ライブラリのchainercv/links/model/faster_rcnn/utils/bbox2loc.pyの
dh = xp.log(base_height / height) dw = xp.log(base_width / width)
を、
_dh = xp.fmax(base_height / height, xp.finfo(xp.float32).eps) _dw = xp.fmax(base_width / width, xp.finfo(xp.float32).eps) dh = xp.log(_dh) dw = xp.log(_dw)
などと変更して、logに与える値が0以下にならないようにしたらnanにならなくなった。
検出結果
緑がground truth、青が検出結果である。スコアが低いほど色が薄くなっている。
割とうまく行った例
うまく行かない例
ブラーのかかった顔、小さい顔などは2重に検出されていたり、検出されなかったりする。また、検出されてもずれがおおきかったりしているようだ。
見たところ、検出できる領域の最小があまり小さくなく(あまり小さい領域は検出できない)、動かす幅にも制限がある気がする。パラメータの設定次第では改善するのかもしれない? 割と大きなサイズをもった顔はちゃんと検出されているようだ。
*1:再現できているとは言っていない。