Help us understand the problem. What is going on with this article?

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を用いた手法」を使います。

事前準備

pythonモジュールの入手

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

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

学習済みモデルの入手

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

  • dlib ~Index of /files~
    http://dlib.net/files/
    →「shape_predictor_68_face_landmarks.dat.bz2 」を選択

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

顔画像の入手

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

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

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

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

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

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

顔のランドマークの番号

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

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

figure_68_markup.jpg

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

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

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

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

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

face_landmark_how.png

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

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

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

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は本論文に基づき実装されています。

コーディング関連

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした