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


実行環境

Ubuntu 16.04
Python 3.7
opencv-python 4.1.0.25

概要

OpenCVに実装されている顔認識ライブラリを利用し、顔のランドマークを検出する。

ランドマークとは

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

顔に対してランドマークを検出すると、目、鼻、口、眉、輪郭部分が検出される。※学習データにより異なる
例えば、顔の特徴を68点で表した学習データで検出すると、
下図のように検出されたランドマークに番号がデータ(配列の要素番号)として割り振られる。

準備

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

  2. 学習済みデータの用意
    検出時は、顔検出のAPIであるFacemark APIを用いる。
    Facemark APIでは、ランドマーク検出のために学習済みのデータを利用する。
    ランドマークのデータとして、dlibの学習済みデータlearned-models/に解凍。

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


# 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

今回はやっていないが、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」は出力時に生成される

ソースコード

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を実行。

ランドマークが検出できた。
各点の座標情報を元に、表情の推定などができる。

参考文献