にせねこメモ

はてなダイアリーがUTF-8じゃないので移ってきました。

ChainerCVでFaster R-CNNを使って顔検出してみる

(この記事は手探りで書いてるので大いに勘違いを含んでいる可能性があります。ご了承ください。)

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 などを実行時に自動でダウンロードされるようになっている。

デモ

python demo.py [--gpu <gpu>] [--pretrained_model <model_path>] <image>.jpg

などとすれば、次のように顔と予測された矩形領域とスコアが表示される。
f:id:nixeneko:20170724160758p:plain
なお、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、青が検出結果である。スコアが低いほど色が薄くなっている。

割とうまく行った例

f:id:nixeneko:20170724162340p:plain

うまく行かない例

f:id:nixeneko:20170724162432p:plain
ブラーのかかった顔、小さい顔などは2重に検出されていたり、検出されなかったりする。また、検出されてもずれがおおきかったりしているようだ。
見たところ、検出できる領域の最小があまり小さくなく(あまり小さい領域は検出できない)、動かす幅にも制限がある気がする。パラメータの設定次第では改善するのかもしれない? 割と大きなサイズをもった顔はちゃんと検出されているようだ。

*1:再現できているとは言っていない。