PythonでOpenCVの顔認識を試してみた

#実行環境
**Ubuntu 16.04** 
**Python 3.7**
**opencv-python 4.1.0.25**
#概要
OpenCVに実装されている顔認識ライブラリを利用し、顔のランドマークを検出する。


#ランドマークとは
ランドマークは、地理学上では「目印や象徴となる特徴物」とされるが、
顔認識においては顔の**キーポイント（特徴点）**を指す。
ランドマークを用いることで、表情の認識や人物の識別などの応用ができる。

顔に対してランドマークを検出すると、目、鼻、口、眉、輪郭部分が検出される。※学習データにより異なる
例えば、顔の特徴を**68点**で表した学習データで検出すると、
下図のように検出されたランドマークに番号がデータ(配列の要素番号)として割り振られる。
<img src="https://camo.qiitausercontent.com/fc0074cea496bfcbd46b7a81585ffb6e4a8d80cc/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3237333436322f66613562353639632d613130662d396462382d396566632d6562376132333232633230372e706e67">

#準備
1. カスケードファイルの用意
まず、顔領域を検出するために**カスケードファイル**が必要。
カスケードファイルには物体を分類するための情報が記載されており、
顔のカスケードファイルはOpenCVが[公式配布](https://github.com/opencv/opencv/tree/master/data/haarcascades)している。
今回使用するファイルは**「haarcascade_frontalface_default.xml」**という正面の顔用の分類器。
これを`haarcascades/`下に置く。
　

2. 学習済みデータの用意
検出時は、顔検出のAPIである[Facemark API](https://docs.opencv.org/master/d7/dec/tutorial_facemark_usage.html)を用いる。
Facemark APIでは、ランドマーク検出のために学習済みのデータを利用する。
ランドマークのデータとして、[dlibの学習済みデータ](http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2)を`learned-models/`に解凍。

3. パスを指定
1 , 2 を読み込むためのパスを指定しておく。

```python

# OpenCVのカスケードファイルと学習済みモデルのパスを指定
CASCADE_PATH = "./haarcascades/"
CASCADE = cv2.CascadeClassifier(CASCADE_PATH + 'haarcascade_frontalface_default.xml')

LEARNED_MODEL_PATH ="./learned-models/"
PREDICTOR = dlib.shape_predictor(LEARNED_MODEL_PATH + 'shape_predictor_68_face_landmarks.dat')
```
後は関数内で呼び出すだけで検出できる。
ランドマークを検出する関数の実装は以下の通り。
検出された全ての顔に対してランドマークを検出する。

```python:
# 顔の位置を検出　返却値は位置を表すリスト(x,y,w,h)
def face_position(gray_img):
    faces = CASCADE.detectMultiScale(gray_img, minSize=(100, 100))
    return faces

# ランドマーク検出
def facemark(gray_img):
    faces_roi = face_position(gray_img)
    landmarks = []

    for face in faces_roi:
        detector = dlib.get_frontal_face_detector()
        rects = detector(gray_img, 1)
        landmarks = []

        for rect in rects:
            landmarks.append(
                numpy.array([[p.x, p.y] for p in PREDICTOR(gray_img, rect).parts()])            )

    return landmarks
```
今回はやっていないが、`CASCADE.detectMultiScale()`の返却値を使うことで、顔領域を矩形で囲ったりもできる。



準備を終えた段階のディレクトリ階層は以下の通り。

```
- haarcascades/
    | - haarcascade_frontialface_default.xml
- learned-models/
    | - shape_predictor_68_face_landmarks.dat
- img/
    | - input.jpg
- result/
    | - output.jpg(※)
- main.py
```
※「output.jpg」は出力時に生成される

#ソースコード
```python:main.py
#-*- coding: utf-8 -*-
import cv2
import dlib
import numpy

#OpenCVのカスケードファイルと学習済みモデルのパスを指定
CASCADE_PATH = "./haarcascades/"
CASCADE = cv2.CascadeClassifier(CASCADE_PATH + 'haarcascade_frontalface_default.xml')

LEARNED_MODEL_PATH ="./learned-models/"
PREDICTOR = dlib.shape_predictor(LEARNED_MODEL_PATH + 'shape_predictor_68_face_landmarks.dat')

# 顔の位置を検出　返却値は位置を表すリスト(x,y,w,h)
def face_position(gray_img):
    faces = CASCADE.detectMultiScale(gray_img, minSize=(100, 100))
    return faces

# ランドマーク検出
def facemark(gray_img):
    faces_roi = face_position(gray_img)
    landmarks = []

    for face in faces_roi:
        detector = dlib.get_frontal_face_detector()
        rects = detector(gray_img, 1)
        landmarks = []

        for rect in rects:
            landmarks.append(
                numpy.array([[p.x, p.y] for p in PREDICTOR(gray_img, rect).parts()]))

    return landmarks

def main():
    img = cv2.imread("./img/input.jpg")#自分の画像に置き換え
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#処理を早くするためグレースケールに変換
    landmarks = facemark(gray)#ランドマーク検出

    # ランドマークの描画
    for landmark in landmarks:
        for points in landmark:
            cv2.drawMarker(img, (points[0], points[1]), (21, 255, 12))

    # 表示
    cv2.imshow("video frame", img)
    cv2.waitKey(0)

    # 保存
    cv2.imwrite("./result/output.jpg", img)
    cv2.destroyAllWindows()

if __name__ == '__main__':
    main()
```

#検出
以上で検出の準備ができたので、入力画像を用意して`main.py`を実行。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426498/51b3e518-e42d-1677-8b24-04e9e0e92b04.jpeg">
ランドマークが検出できた。
各点の座標情報を元に、表情の推定などができる。

#参考文献

- [PythonでdlibとOpenCVを用いてHelen datasetを学習して顔器官検出](https://qiita.com/kekeho/items/0b2d4ed5192a4c90a0ac)
- [300 Faces in-the-Wild Challenge: The first facial landmark localization
Challenge](https://ibug.doc.ic.ac.uk/media/uploads/documents/sagonas_iccv_2013_300_w.pdf)
[]()