LoginSignup
61
50

More than 3 years have passed since last update.

PythonでdlibとOpenCVを用いてHelen datasetを学習して顔器官検出

Last updated at Posted at 2018-07-15

概要

タイトルの通りです。機械学習のライブラリであるdlibで顔器官(顔のパーツ)検出を行います。
ネット上に転がっている学習済みのデータを用いて認識してもいいのですが、今回は学習からさせてみたいと思います。
ググって学習済みのデータを探してみたものの68点しか検出できないデータしか見つけることができず、もっと高精度に認識させたかったんです。後述するHelen datasetを用いれば194点で検出することが可能です。

本記事のライセンス

本記事(ソースコード含む)は, CC0で公開しています.
著作権法上認められる、私が持つすべての権利(その作品に関する権利や隣接する権利を含む。)を、法令上認められる最大限の範囲で放棄します.

(でも少なくとも, どこかで使う際にそのままコピペはちょっとやめてね…多分レポートとかでコピペすると怒られちゃうよ……というお気持ち表明は一応しておきます. 拘束力は無い気がするけど.)

CC0で公開しているのはあくまでこの記事だけです.

Helen dataset

学習するデータセットは、Helen datasetを使います。
Helen datasetはイリノイ大学のVuong Leさんのサイトで公開されているデータセットです。以下の画像のように、顔写真及びそれに対応する各顔パーツの座標を含んでいます。
image.png

以下の画像のように、顔全体を194点で認識できるので、なかなか便利なのではないでしょうか。
face_landmark_parts.png

n個目の座標 対応部位
0~40 顔の輪郭
41~57
58~85 口の外側
86~113 口の内側
114~133 右目
134~153 左目
154~173 右の眉毛
174~193 左の眉毛

環境構築

私の環境はmacOS High Sierraです。他の環境の人は各自読み替えて下さい。
Pythonのバージョンは3.6.5です。

pip install opencv-python
pip install dlib

学習

Helen datasetのサイトへ行って、DownloadからTrain imagesをダウンロードします。Part1からPart5までダウンロードしてみましょう。

その後、座標データをダウンロードします。Downloadの中のc. Annotationと書いてあるところからAll facesをダウンロードします。

更に、OpenCVの顔認識器用のファイルもダウンロードします。

mkdir haarcascades
wget "https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_default.xml" -P haarcascades/

それぞれ解凍して、以下のようなディレクトリ構成で配置して下さい。更に、find.pyとhelen-dataset-study.pyを以下のように作成して下さい。

- haarcascades/
    | - haarcascade_frontialface_default.xml
- learned-models/
    |  - helen-dataset-study.py
    |  - helen-dataset/
    |          | - annotations/
    |          |     | - 1.txt
    |          |     | - 2.txt
    |          |     |    ︙(annotationファイルたちを全部ぶちこむ)
    |          |
    |          | - 232194_1.jpg
    |          | - 1629243_1.jpg
    |          |      ︙(画像たちを全部ぶちこむ)
- find.py

helen-dataset-study.pyに以下のコードをぶち込みます。

learnded-models/helen-dataset-study.py
import dlib
import os
import sys
import multiprocessing
import cv2
import glob

NOW_ABS_FILEPATH = os.path.dirname(os.path.abspath(__file__))
HELEN_IMGS_ABS_FILEPATH = NOW_ABS_FILEPATH + '/helen-dataset/'
HELEN_ANNOTATIONS_ABS_FILEPATH = HELEN_IMGS_ABS_FILEPATH + 'annotations/'
CASCADE_PATH = os.path.dirname(os.path.abspath(__file__)) + "/../haarcascades/"

face_cascade = cv2.CascadeClassifier(
    CASCADE_PATH + 'haarcascade_frontalface_default.xml')
helen_annotations_filelist = glob.glob(HELEN_ANNOTATIONS_ABS_FILEPATH + '*')

xml_template_header = """<?xml version='1.0' encoding='ISO-8859-1'?>
<?xml-stylesheet type='text/xsl' href='image_metadata_stylesheet.xsl'?>
<dataset>
<name>helen dataset</name>
<comment>Created by kekeho.</comment>
<images>
"""

xml_template_footer = """</images>
</dataset>
"""

def generate_xml():
    xml = xml_template_header

    count = 0
    for file_name in helen_annotations_filelist:
        with open(file_name, 'r') as file:
            # count += 1
            # if count == 10:
            #     break

            img_filename = HELEN_IMGS_ABS_FILEPATH + file.readline().replace('\n', '') + \
                '.jpg'  # header is imgfile name

            image_xml = f"""
            <image file='{img_filename}'>
            """

            gray_image = cv2.imread(img_filename, cv2.IMREAD_GRAYSCALE)

            x, y, w, h = [int for i in range(4)]

            if len(face_position(gray_image)) != 1:
                print(
                    f"Image includes more than one face: ignore {img_filename}")
            else:
                x, y, w, h = face_position(gray_image)[0]

                image_xml += f"<box top='{y-50}' left='{x-50}' width='{w+100}' height='{h+100}'>\n"

                i = 0
                for line in file:
                    x, y = line.replace('\n', '').replace(
                        '\r', '').replace(' ', '').split(',')
                    image_xml += f"<part name='{i}' y='{y.split('.')[0]}' x='{x.split('.')[0]}' />\n"
                    i += 1
                image_xml += '</box>\n'
                image_xml += '</image>\n'

                xml += image_xml

    xml += xml_template_footer
    return xml


def face_position(gray_img):
    """Detect faces position
    Return:
        faces: faces position list (x, y, w, h)
    """
    faces = face_cascade.detectMultiScale(gray_img, minSize=(
        int(len(gray_img[0]) / 6), int(len(gray_img) / 6)))
    return faces


def main():

    # generate xml file
    with open('helen-dataset.xml', 'w') as out_xml_file:
        xml = generate_xml()
        out_xml_file.write(xml)

    # set options
    options = dlib.shape_predictor_training_options()
    options.num_threads = multiprocessing.cpu_count()  # cpu threads
    options.be_verbose = True

    train_xml_filename = './helen-dataset.xml'

    print("Start training")
    dlib.train_shape_predictor(
        train_xml_filename, "helen-dataset.dat", options)
    print("Finish")


if __name__ == '__main__':
    main()

上記のプログラムを実行すれば、learned-models/の中にhelen-dataset.datが生成されているはずです。
これが、学習済みファイルになります。

認識

とうとう認識します。
複数人いても認識できるようにしています。

find.py
import cv2
import dlib
import numpy
import os

# Cascade files directory path
CASCADE_PATH = os.path.dirname(os.path.abspath(__file__)) + "/haarcascades/"
LEARNED_MODEL_PATH = os.path.dirname(
    os.path.abspath(__file__)) + "/learned-models/"
predictor = dlib.shape_predictor(
    LEARNED_MODEL_PATH + 'helen-dataset.dat')
face_cascade = cv2.CascadeClassifier(
    CASCADE_PATH + 'haarcascade_frontalface_default.xml')


def face_position(gray_img):
    """Detect faces position
    Return:
        faces: faces position list (x, y, w, h)
    """
    faces = face_cascade.detectMultiScale(gray_img, minSize=(100, 100))
    return faces


def facemark(gray_img):
    """Recoginize face landmark position by i-bug 300-w dataset
    Return:
        randmarks = [
        [x, y],
        [x, y],
        ...
        ]
        [0~40]: chin
        [41~57]: nose
        [58~85]: outside of lips
        [86-113]: inside of lips
        [114-133]: right eye
        [134-153]: left eye
        [154-173]: right eyebrows
        [174-193]: left eyebrows
    """
    faces_roi = face_position(gray_img)
    landmarks = []

    for face in faces_roi:
        x, y, w, h = face
        face_img = gray_img[y: y + h, x: x + w];

        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


if __name__ == '__main__':
    cap = cv2.VideoCapture(0)
    while cap.isOpened():
        ret, frame = cap.read()
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        landmarks = facemark(gray)

        for landmark in landmarks:
            for points in landmark:
                cv2.drawMarker(frame, (points[0], points[1]), (21, 255, 12))
        cv2.imshow("video frame", frame)
        if cv2.waitKey(25) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()

結果

image.png
こんな感じです。勝ちですね。

共有

生成された学習済みファイルをおいておくので、よければお使い下さい。
Google drive

61
50
12

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
61
50