Xcode で dlib を使用して顔認識を行う

## 概要

C++ のすごいライブラリ dlib のデモ webcam_face_pose_ex.cpp を参考に、Xcode で作った既存のプロジェクトに dlib を組み込んで顔認識を行います。

## 背景

OpenCV の `CascadeClassifier` を使った場合は顔のパーツの位置を認識することはできますが、顔の輪郭や眉の位置などの特徴点を取ることができません。

dlib の `get_frontal_face_detector` を使うことによってかなり詳細なパーツの特徴点を取得することができます。

## 環境

- OSX El Capitan (10.11.2)
- Xcode 7.2
- MacBook Air (13-inch, Mid 2013)

## 準備

### OpenCV

OpenCV をインストールします。

```bash
brew tap homebrew/science
brew install opencv
```

`/usr/local/Cellar/opencv/2.4.12/lib` から必要なライブラリのファイルを `Link Binary With Libraries` に追加します。

![スクリーンショット 2015-12-18 14.04.23.png](https://qiita-image-store.s3.amazonaws.com/0/31477/8fd67e75-942a-bd56-6b4d-f4ef075f6f9a.png)
必要なライブラリ

### プロジェクトの設定

OpenCVのヘッダファイルを `Header Search Paths` に追加します。

![スクリーンショット 2015-12-18 14.00.01.png](https://qiita-image-store.s3.amazonaws.com/0/31477/ae937f37-9842-53a0-d7ed-b98da729f1d7.png)

#### 追記 (2016/3/1)

X11 は追加しなくて大丈夫です

### dlib

dlib はソースコードをまるごとプロジェクトのディレクトリにコピーして、 `dlib/all/source.cpp` だけプロジェクトに追加します。

`Header Search Paths` に dlib のディレクトリを追加したり、source.cpp をプロジェクトに追加しなかったりすると `"_USER_ERROR__missing_dlib_all_source_cpp_file__OR__inconsistent_use_of_DEBUG_or_ENABLE_ASSERTS_preprocessor_directives_"` というエラーが出ます。
私はここで詰まってしばらくわからなかったので、Qiita で共有することで解決の助けになれば幸いです。

#### 追記 (2016/3/1)

dlib を X11 なしで使うため、フラグ `DLIB_NO_GUI_SUPPORT` を設定します。

下記のように source.cpp の先頭に書き込むとか、

```cpp:source.cpp
#define DLIB_NO_GUI_SUPPORT
...
```

## コード

```cpp
//
//  main.cpp
//  FaceTracker
//
//  Created by ryohey on 2015/12/11.
//

#include <iostream>
#include <cv.h>
#include <highgui.h>
#include <ctype.h>

#include "dlib/opencv.h"
#include "dlib/image_processing/frontal_face_detector.h"
#include "dlib/image_processing.h"

void drawObjects(cv::Mat& image, std::vector<cv::Rect>& objects, cv::Scalar color, CvPoint offset = cvPoint(0, 0)) {
    
    for (auto r : objects) {
        cv::rectangle(image,
                      cvPoint(r.x + offset.x,           r.y + offset.y),
                      cvPoint(r.x + offset.x + r.width, r.y + offset.y + r.height),
                      color);
    }
}

int main(int argc, char **argv) {
    std::string path(argv[0]);
    const std::string RESOURCE_DIR = path.substr(0, path.find_last_of("\\/") + 1);
    const std::string FACE_LANDMARK_MODEL = "shape_predictor_68_face_landmarks.dat";
    
    dlib::frontal_face_detector detector = dlib::get_frontal_face_detector();
    dlib::shape_predictor pose_model;
    dlib::deserialize(RESOURCE_DIR + FACE_LANDMARK_MODEL) >> pose_model;
    
    const double w = 720, h = 480;
    
    cv::VideoCapture capture(0);
    capture.set(CV_CAP_PROP_FRAME_WIDTH, w);
    capture.set(CV_CAP_PROP_FRAME_HEIGHT, h);
    
    cvNamedWindow("Capture", CV_WINDOW_AUTOSIZE);
    
    while (1)
    {
        cv::Mat frame;
        capture.read(frame);
        
        // dlib
        {
            dlib::cv_image<dlib::bgr_pixel> cimg(frame);
            
            // Detect faces
            std::vector<dlib::rectangle> faces = detector(cimg);
            
            // Find the pose of each face.
            std::vector<dlib::full_object_detection> shapes;
            
            for (auto face: faces) {
                shapes.push_back(pose_model(cimg, face));
            }
            
            std::vector<cv::Rect> objects;
            
            // 点のサイズ
            const int s = 2;
            
            for (auto shape: shapes) {
                auto rect = shape.get_rect();
                objects.push_back((cv::Rect){
                    (int)rect.left(), (int)rect.top(),
                    (int)rect.width(), (int)rect.height()
                });
                
                for (auto i = 0; i < shape.num_parts(); i++) {
                    auto part = shape.part(i);
                    
                    objects.push_back((cv::Rect){
                        (int)part.x(), (int)part.y(),
                        s, s
                    });
                }
            }
            
            if (objects.size() > 0) {
                drawObjects(frame, objects, cv::Scalar(255, 255, 0));
            }
        }
        
        IplImage frame_ = frame;
        cvShowImage("Capture", &frame_);
        
        auto c = cvWaitKey(2);
        if (c == '\x1b') {
            break;
        }
    }
    
    cvDestroyWindow("Capture");
    
    return 0;
}
```

## 結果

[![https://gyazo.com/86f0058c2345be238aaac9e0be496176](https://i.gyazo.com/86f0058c2345be238aaac9e0be496176.gif)](https://gyazo.com/86f0058c2345be238aaac9e0be496176)

Debug ビルドだとかなり遅かったので Release ビルドにしたらすごい速くなりました。Macbook Air ですが解像度 720x480 でスムーズに動きました。

最小の認識サイズがあるのか、320x240 の時は顔をカメラに近づけないと認識しませんでしたが、720x480 の場合だと後ろの方の人もちゃんと認識されました。
横顔は多少弱いようですが、OpenCV の Haar-Like 特徴を使った場合よりもちゃんと追従し、パーツもかなり詳細に取得できています。