OpenCVでキャプチャした画像から、Dlibで顔と輪郭などを検出する

#はじめに
本ページは主に下記URLの、
https://www.pyimagesearch.com/2017/04/10/detect-eyes-nose-lips-jaw-dlib-opencv-python/
の内容をフォークしたものです。主な内容は画像からの顔検出ですが、今回はOpenCVではなくDlibというツールを用いています。モジュールのインストールは、

```
pip install dlib
```

これで簡単にできました。実はこっちの方が顔検出の精度が良いです（既存のライブラリを用いた場合の話ですが）。そこらへんの議論は、
http://blog.dlib.net/2014/02/dlib-186-released-make-your-own-object.html
ここが大変参考になりました。また今回のコードの変更点は、

- 顔や輪郭検出をPC内蔵カメラからキャプチャした画像から行えるようにした
- スペースキーを押すことで、画像を連番の画像名で保存できるようにした

となります。

#顔検出
下の二行が主に顔検出を行う処理です。

```
detector = dlib.get_frontal_face_detector()
dets, scores, idx detector.run(img, 0)
```

検出された顔部分の情報はdetsに保存されます。顔検出のアルゴリズムとしてはSVMみたいなマージン最大化とHOG特徴量を用いているっぽいです。[論文](http://arxiv.org/abs/1502.00046)が公開されてます。

#輪郭などの検出
実際のところ輪郭とそれ以外にあご、目、鼻、眉、口を一気に検出してくれます。ここでは先ほどのアルゴリズムとはまた別の勾配ブースティング木というアルゴリズムを用いているそうです。また、このライブラリを用いるにはデータセットが必要ですが、iBUG 300-Wというところが公開している[既存のもの](https://ibug.doc.ic.ac.uk/resources/facial-point-annotations/)がございます。データセットのダウンロードは下記URLを踏んでください。
http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2.
輪郭などの検出には下記のような宣言が必要になります。

```
predictor_path = "./shape_predictor_68_face_landmarks.dat"
predictor = dlib.shape_predictor(predictor_path)
```

また、輪郭などの検出は顔検出より切り取った情報は用いるので、先ほどのdetsを用いて、

```
shape = predictor(img_rgb, rect)
shape = face_utils.shape_to_np(shape)
```

shapeに保存した後、OpenCVやnumpyで扱いやすい形に変換します。また、shapeには68個の輪郭情報が含まれていてshape[0:68]と指定するとあご、目、鼻、眉、口、全体となりますが各部位と数字は常に対応しており、


- 口 = 48:68
- 右眉 = 17:22
- 左眉 = 22:27
- 右目 = 36:42
- 左目 = 42:48
- 鼻 = 27:35
- あご = 0:17

各部位をしていすることも可能です。

#コード
実装してみたものが下記になります。ここでは口のみを指定して切り出しています。スペースボタンを押すことで切り出した画像を保存できます。また、ESCを押すとウィンドウを閉じることができます。

```python:capture_face_landmarks.py
#! /usr/bin/python
# -*- coding: utf-8 -*-
import cv2
import dlib
import numpy as np
import imutils
from imutils import face_utils

def face_shape_detector_dlib(img):
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # frontal_face_detectorクラスは矩形, スコア, サブ検出器の結果を返す
    dets, scores, idx = detector.run(img_rgb, 0)
    if len(dets) > 0:
        for i, rect in enumerate(dets):
            shape = predictor(img_rgb, rect)
            shape = face_utils.shape_to_np(shape)
            clone = img.copy()
            cv2.putText(clone, "mouth", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
            # landmarkを画像に書き込む
            for (x, y) in shape[48:68]:
                cv2.circle(clone, (x, y), 1, (0, 0, 255), -1)
            # shapeで指定した個所の切り取り画像(ROI)を取得
            (x, y, w, h) = cv2.boundingRect(np.array([shape[48:68]])) #口の部位のみ切り出し
            roi = img[y:y + h, x:x + w]
            roi = cv2.resize(roi,(100,100))
        return clone, roi
    else :
        return img, None

def main():
	predictor_path = "./shape_predictor_68_face_landmarks.dat"
	predictor = dlib.shape_predictor(predictor_path)
	detector = dlib.get_frontal_face_detector()
	cap = cv2.VideoCapture(0)
	count = 0

	while True:
		ret, frame = cap.read()
		frame = imutils.resize(frame, width=500)
		frame, roi = face_shape_detector_dlib(frame)
		cv2.imshow('img', frame)
		if roi is not None :
			cv2.imshow('roi', roi)
		else :
			cv2.destroyWindow('roi')
		c = cv2.waitKey(1)
		if c == 27:#ESCを押してウィンドウを閉じる
			break
		if c == 32:#spaceで保存
			count += 1
			cv2.imwrite('./filename%03.f'%(count)+'.jpg', roi) #001~連番で保存
			print('save done')
	cap.release()
	cv2.destroyAllWindows()
	
if __name__ == '__main__':

    main()
```