pythonとdlibでお手軽に顔のランドマークを検出してみた

# はじめに

私が学生の頃は顔のランドマーク検出の研究をひーひー言いながらやっていました。
今ではそれが信じられないくらい簡単になって驚きました。

なので実際にやってみたいと思います。
とりあえず実装したいんじゃ、という人は「[お手軽に顔のランドマーク検出をやってみた](#お手軽に顔のランドマーク検出をやってみた)」から読んでください。


# 顔のランドマーク検出方法について

顔のランドマーク検出の方法は主に以下の3つに分かれるようです。
各々の詳細は[参考リンク](#顔のランドマーク検出について)に載せた論文をご参照ください。


## (1)Ensemble of regression treesを用いた手法

回帰ツリー分析を用いてリアルタイムで高精度なランドマーク検出を実現しています。
dlib、OpenCV(FacemarkKazemi)共に実装されています。
ただし学習モデルがデフォルトで用意されているのはdlibだけです。


## (2)Active appearance modelを用いた手法

物体の形状と外観から学習された統計モデルに基づき物体検出を行います。
顔に限らず高度な物体追跡として長く使われている手法です。
(学生時代に私が研究していたのもこのAAMです)
OpenCV(FacemarkAAM)に実装されていますが、学習モデルは自作する必要があります。
しかし学習モデル生成用のツールや、誰かの作った学習モデルは探せば見つかるので、敷居は低いと思います。


## (3)Local Binary Featuresを用いた手法

回帰学習により非常に高速なランドマーク検出が可能です。
「Ensemble of regression trees」と類似した手法のようですが細かい違いが分かりません。
OpenCV(FacemarkLBF)に実装されており、学習モデルも用意されています。

# お手軽に顔のランドマーク検出をやってみた

今回はpythonでお手軽に実装したいので、dlibで「[(1)Ensemble of regression treesを用いた手法](#1ensemble-of-regression-treesを用いた手法)」を使います。


## 事前準備

### pythonモジュールの入手

モジュールとして顔のランドマーク検出のためdlibとimutils、画像関連のためにOpenCVを追加します。
注意点としてdlibを追加するためにはAnaconda環境のpythonである必要があります。

```bash:pythonモジュールのインストール
pip install dlib
pip install imutils
pip install opencv
pip install libopencv
pip install py-opencv
```


### 学習済みモデルの入手

学習済みモデルはdlibの公式サイトの以下から入手できます。


* **dlib ～Index of /files～**<br>
http://dlib.net/files/
<br>→「shape_predictor_68_face_landmarks.dat.bz2 」を選択

余談ですが上記の学習済みモデルは以下のサイトのデータを元に生成されています。

* **i・bug ～Facial point annotations～**<br>
https://ibug.doc.ic.ac.uk/resources/facial-point-annotations/


### 顔画像の入手

顔画像は以下から「Girl.bmp」を入手し使わさせていただきました。

* **神奈川工科大学 情報学部 情報工学科 ～標準画像／サンプルデータ～**<br>
http://www.ess.ic.kanagawa-it.ac.jp/app_images_j.html

画像処理の定番の「Lenna」じゃないの？ と思う方もいるかもしれません。
しかし「Lenna」は振り向き顔のせいか、思ったよりよい結果が得られなかったので外しています。
気になる方は試してください。

## 静止画における顔のランドマーク検出

静止画から顔のランドマーク検出を実施するサンプルです。
学習済みモデル(shape_predictor_68_face_landmarks.dat)と顔画像(Girl.bmp)は面倒なので同じ階層に置いています。

```python:face_landmark_sample.py
# coding:utf-8

import dlib
from imutils import face_utils
import cv2

# --------------------------------
# 1.顔ランドマーク検出の前準備
# --------------------------------
# 顔検出ツールの呼び出し
face_detector = dlib.get_frontal_face_detector()

# 顔のランドマーク検出ツールの呼び出し
predictor_path = 'shape_predictor_68_face_landmarks.dat'
face_predictor = dlib.shape_predictor(predictor_path)

# 検出対象の画像の呼び込み
img = cv2.imread('Girl.bmp')
# 処理高速化のためグレースケール化(任意)
img_gry = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# --------------------------------
# 2.顔のランドマーク検出
# --------------------------------
# 顔検出
# ※2番めの引数はupsampleの回数。基本的に1回で十分。
faces = face_detector(img_gry, 1)

# 検出した全顔に対して処理
for face in faces:
    # 顔のランドマーク検出
    landmark = face_predictor(img_gry, face)
    # 処理高速化のためランドマーク群をNumPy配列に変換(必須)
    landmark = face_utils.shape_to_np(landmark)

    # ランドマーク描画
    for (i, (x, y)) in enumerate(landmark):
        cv2.circle(img, (x, y), 1, (255, 0, 0), -1)

# --------------------------------
# 3.結果表示
# --------------------------------
cv2.imshow('sample', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

```

結果は次の通りです。

![face_landmark_result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224034/d46e3e58-7e54-0801-e8b6-620b64bd94b6.png)

上図の通りキレイに顔のランドマークが検出されています。
ではランドマーク検出までに各コードで何をしているか解説します。

### 顔のランドマークの番号

顔のランドマーク検出をしたあとは、当然色々な処理をしたいと思います。
その時に気になるのが、各ランドマークをどのように呼び出せばよいかです。

顔のランドマークは[上述したサイト](#学習済みモデルの入手)のデータから学習されています。
そのためランドマークの番号も、学習元のサイトに記載されている番号通りになっています。

![figure_68_markup.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224034/7fab5744-b798-bd25-bc6b-974c429a47a0.jpeg)

ランドマークの番号：https://ibug.doc.ic.ac.uk/resources/facial-point-annotations/

上図を見れば分かる通り、番号は1～68まで割り振られています。
ただし実際に参照するときは、配列は0番から始まるため、0～67となり番号がひとつずれます。

分かりにくいので図とコードを交え実際に、一部を切り取って見ます。


```python:face_landmark_sample2.py
# coding:utf-8

import dlib
from imutils import face_utils
import cv2

# --------------------------------
# 1.顔ランドマーク検出の前準備
# --------------------------------
# 顔検出ツールの呼び出し
face_detector = dlib.get_frontal_face_detector()
# 顔のランドマーク検出ツールの呼び出し
predictor_path = 'shape_predictor_68_face_landmarks.dat'
face_predictor = dlib.shape_predictor(predictor_path)

# 検出対象の画像の呼び込み
img = cv2.imread('Girl.bmp')
# 処理高速化のためグレースケール化(任意)
img_gry = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# --------------------------------
# 2.顔のランドマーク検出
# --------------------------------
# 顔検出
# ※2番めの引数はupsampleの回数
faces = face_detector(img_gry, 1)

# 検出した全顔に対して処理
for face in faces:
    # 顔のランドマーク検出
    landmark = face_predictor(img_gry, face)
    # 処理高速化のためランドマーク群をNumPy配列に変換(必須)
    landmark = face_utils.shape_to_np(landmark)

    # --------------------------------
    # 3.ランドマークから画像を切り取り
    # --------------------------------
    # 番号1のランドマークのX座標取得
    landmark_n1_x = landmark[0][0]

    # 番号17のランドマークのX座標取得
    landmark_n17_x = landmark[16][0]

    # 番号9のランドマークのY座標取得
    landmark_n9_y = landmark[8][1]

    # 番号28のランドマークのY座標取得
    landmark_n28_y = landmark[27][1]

    # 画像の切り出し
    img2 = img[landmark_n28_y:landmark_n9_y, landmark_n1_x:landmark_n17_x]

    # 結果表示
    cv2.imshow('sample', img2)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

```

結果は次の通りです。

![face_landmark_result2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224034/5343ec63-bf6c-d3d2-803a-f286e7b16199.png)

このようになる理由は以下の通りです。

![face_landmark_how.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224034/ef9dfb3a-891f-1c00-a914-79bf798b302c.png)


ランドマークは1番からスタートしますが、それを格納する配列は0番からスタートします。
そのためこのようなズレが生じます。


## リアルタイムでの顔のランドマーク検出

おまけにカメラ画像から顔のランドマーク検出をするサンプルを載せます。
ネット上に顔を晒す勇気がないため実行結果はなしです。

```python:face_landmark_sample.py
# coding:utf-8

import dlib
from imutils import face_utils
import cv2

# --------------------------------
# 1.顔ランドマーク検出の前準備
# --------------------------------
# 顔ランドマーク検出ツールの呼び出し
face_detector = dlib.get_frontal_face_detector()
predictor_path = 'shape_predictor_68_face_landmarks.dat'
face_predictor = dlib.shape_predictor(predictor_path)


# --------------------------------
# 2.画像から顔のランドマーク検出する関数
# --------------------------------
def face_landmark_find(img):
    # 顔検出
    img_gry = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = face_detector(img_gry, 1)

    # 検出した全顔に対して処理
    for face in faces:
        # 顔のランドマーク検出
        landmark = face_predictor(img_gry, face)
        # 処理高速化のためランドマーク群をNumPy配列に変換(必須)
        landmark = face_utils.shape_to_np(landmark)

        # ランドマーク描画
        for (x, y) in landmark:
            cv2.circle(img, (x, y), 1, (0, 0, 255), -1)

    return img


# --------------------------------
# 3.カメラ画像の取得
# --------------------------------
# カメラの指定(適切な引数を渡す)
cap = cv2.VideoCapture(0)

# カメラ画像の表示 ('q'入力で終了)
while(True):
    ret, img = cap.read()

    # 顔のランドマーク検出(2.の関数呼び出し)
    img = face_landmark_find(img)

    # 結果の表示
    cv2.imshow('img', img)

    # 'q'が入力されるまでループ
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 後処理
cap.release()
cv2.destroyAllWindows()

```


# 参考リンク

## 顔のランドマーク検出の論文関連

* **Facemark : Facial Landmark Detection using OpenCV**
https://www.learnopencv.com/facemark-facial-landmark-detection-using-opencv/
各顔のランドマーク検出のアルゴリズムがどの論文から実装されているか解説されています。
またC++によるFacemakerLBFを用いた顔のランドマーク検出のサンプルも載っています。

* **One Millisecond Face Alignment with an Ensemble of Regression Trees**
http://www.csc.kth.se/~vahidk/face_ert.html
V.KazemiさんとJ. Sullivanさんによって発表された論文です。
今回使用したdlibに実装されている顔のランドマーク検出のアルゴリズムは本論文に基づいています。

* **Optimization problems for fast AAM fitting in-the-wild**
https://ibug.doc.ic.ac.uk/media/uploads/documents/tzimiro_pantic_iccv2013.pdf
G. TzimiropoulosさんとM. Panticさんによって発表された論文です。
OpenCVのFacemakerAAMは本論文に基づき実装されています。

* **One Millisecond Face Alignment with an Ensemble of Regression Trees**
http://www.csc.kth.se/~vahidk/face_ert.html
S. Renによって発表された論文です。
OpenCVのFacemakerLBFは本論文に基づき実装されています。

## コーディング関連

* **dlib C++ Library　～Face Landmark Detection～**
http://dlib.net/face_landmark_detection.py.html
dlib公式サイトに紹介されている、pythonによる顔のランドマーク検出のサンプルです。

* **顔のランドマークを検出 Python + OpenCV + dlib を使う**
https://tech-blog.s-yoshiki.com/2018/10/702/
Python + OpenCV + dlibによる顔のランドマーク検出のソースコードが日本語で案内されています。
コーディングの際に参考にさせていただきました。

* **PyImageSearch**
**Facial landmarks with dlib, OpenCV, and Python**
https://www.pyimagesearch.com/2017/04/03/facial-landmarks-dlib-opencv-python/
**(Faster) Facial landmark detector with dlib**
https://www.pyimagesearch.com/2018/04/02/faster-facial-landmark-detector-with-dlib/
Python + OpenCV + dlibによる顔のランドマーク検出のソースコードが英語で案内されています。
詳しく解説されているため、各処理の理解に繋がり、大変参考になりました。