Dlib19.2のDNN顔検出をウェブカメラで試す(in Windows)

これまでに優秀な顔検出モジュールを提供していたdlibですが、ver19.2で遂にDeepLearning(CNN)による顔検出が搭載されました。
元々、structual svmを顔検出に応用したMMODという手法でしたが、今回はこれをCNNのロス関数(mmod loss)として用いたようです。なので優秀なロジックはそのままに、これまでhogでしか表現できなかった画像特徴をCNNの柔軟性で強化したという印象でしょうか。

今回はGPUを使って、ウェブカメラでリアルタイム顔検出を試したいと思います。
Davis氏のブログによると、TitanXで1フレーム45ms(約22fps)なので十分なリアルタイムが期待できます。
http://blog.dlib.net/2016/10/easily-create-high-quality-object.html

#準備するもの
* GPU（Nvidia Titan X推奨）
* Windows 64-bitマシン
* Visual Studio 2015
* CUDA toolkit 8.0
* cuDNN5.0以上(環境に合ったもの)
* opencv3.1 (ウェブカメラIO用)

環境についてはこちらを参考にさせていただきました。
http://elda27.hatenablog.com/entry/2016/10/07/180005

#ビルド
※opencvのビルドは省略します
dlib19.2をビルドします。公式のインストール手順に則ってください。
http://dlib.net/compile.html

コンフィギュレーションは
`cmake -G "Visual Studio 14 2015 Win64" -DCMAKE_PREFIX_PATH=<cuDNNのルートディクレトリ>　..`
のようにcuDNNの展開フォルダを指定してください。(なくても自動で見てくれるっぽいです)

CUDA環境が整っていれば、自動的にCUDAをオンにしてビルドしてくれます。
CUDAなし(CPU版)でビルドしたい場合はオプションに`-D DLIB_USE_CUDA:STRING=OFF`を付けてください。

#Visual Studio環境
Visual StudioにC++コンソールを立ち上げたら、いろいろと設定する項目があります。I/O周りをdlib用に設定するのが面倒です。
またOpenCVの設定は省略します。

* プリコンパイル済みヘッダーを使用している場合は、”プロパティ→C/C++→プリコンパイル済みヘッダー”で”使用しない”にチェックする。
* 追加のインクルードディレクトリの指定
 * ＜dlib root＞
 * ＜dlib root＞/dlib/external/libjpeg
 * ＜dlib root＞/dlib/external/libpng
 * ＜dlib root＞/dlib/external/zlib
* ソースファイルに既存の項目を追加
 * ＜dlib root＞/dlib/all/source.cpp
 * ＜dlib root＞/dlib/external/libjpeg/*
 * ＜dlib root＞/dlib/external/libpng/*
 * ＜dlib root＞/dlib/external/zlib/*
* プリプロセッサの定義に以下を追加
 * DLIB_JPEG_SUPPORT
 * DLIB_PNG_SUPPORT
 * DLIB_USE_CUDA
* 追加のライブラリディレクトリの指定
 * ＜dlib root＞/examples/build/dlib_build/(debug or release)
 * ＜cuda root＞/Toolkit/CUDA/v8.0/lib/x64
 * ＜cuDNN root＞/cuda/lib/x64
* 追加の依存ファイルの指定
 * dlib.lib
 * cudart_static.lib
 * cublas.lib
 * cublas_device.lib
 * cudnn.lib
 * curand.lib
 * opencv_core310.lib
 * opencv_highgui310.lib
 * opencv_imgproc310.lib
 * opencv_videoio310.lib
 * opencv_imgcodecs310.lib

※CPU版ではDLIB_USE_CUDA、cuda関連のlibファイルを外してください。

面倒であれば、＜dlib root＞/example/buildフォルダのソリューションを立ち上げて、dnn_mmod_face_detection_exをいじってしまうのも手です。これならOpenCVの設定をするだけで簡単にいけます。

#実行
実行コードは[ウェブカメラからの器官検出コード](http://dlib.net/webcam_face_pose_ex.cpp.html)と[DNN顔検出コード](http://dlib.net/dnn_mmod_face_detection_ex.cpp.html)を参考に作ってます。

DNNのネットワークが`matrix<rbg_pixel>`フォーマットになっていたため、ビデオキャプチャされるBGR画像をRGB変換してから、dlib形式に渡すようにしています。

また、学習済みモデルを使用するにあたって以下からモデルファイルをダウンロード、解凍してください。
http://dlib.net/files/mmod_human_face_detector.dat.bz2
以下のコードでは、モデルファイルのパスを実行引数に指定して読み込みます。

```lang:webcam_dnn_mmod_face_detection_ex.cpp

#include "stdafx.h"

#include <iostream>
#include <dlib/opencv.h>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <dlib/image_processing/frontal_face_detector.h>
#include <dlib/image_processing/render_face_detections.h>
#include <dlib/image_processing.h>
#include <dlib/dnn.h>
#include <dlib/data_io.h>
#include <dlib/gui_widgets.h>
#include <dlib/matrix.h>

using namespace dlib;
using namespace std;


// ----------------------------------------------------------------------------------------

template <long num_filters, typename SUBNET> using con5d = con<num_filters, 5, 5, 2, 2, SUBNET>;
template <long num_filters, typename SUBNET> using con5 = con<num_filters, 5, 5, 1, 1, SUBNET>;

template <typename SUBNET> using downsampler = relu<affine<con5d<32, relu<affine<con5d<32, relu<affine<con5d<16, SUBNET>>>>>>>>>;
template <typename SUBNET> using rcon5 = relu<affine<con5<45, SUBNET>>>;

using net_type = loss_mmod<con<1, 9, 9, 1, 1, rcon5<rcon5<rcon5<downsampler<input_rgb_image_pyramid<pyramid_down<6>>>>>>>>;

// ----------------------------------------------------------------------------------------

int main(int argc, char** argv)
{
	try
	{
		cv::VideoCapture cap(0);
		if (!cap.isOpened())
		{
			cerr << "Unable to connect to camera" << endl;
			return 1;
		}

		net_type net;
		deserialize(argv[1]) >> net;

		image_window win;
		while (!win.is_closed())
		{
			// Grab a frame
			cv::Mat cvRgbImg, cvBgrImg;
			cap >> cvBgrImg;
			cv::cvtColor(cvBgrImg, cvRgbImg, cv::COLOR_BGR2RGB);
	
			cv_image<rgb_pixel> cimg(cvRgbImg);
			matrix<rgb_pixel> img = dlib::mat(cimg);

			auto dets = net(img);
			win.clear_overlay();
			win.set_image(img);
			for (auto&& d : dets)
				win.add_overlay(d);
		}
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}

    return 0;
}
```

これまでのDlib顔検出と比較すると、かなり際どいアングルまで検出できていました。
やはりhogでは一方向の見え方しか捉えられず、Davis氏が用意した検出器も5方向のhogを統合するやり方で頑張ってました。（高速動作という利点はありますが）

データがあれば、顔以外のマルチアングル物体検出にも有効そうです。
少数枚の学習画像でどこまで性能が出るかも今後試してみたいところです。