LoginSignup
2
4

More than 3 years have passed since last update.

OpenSiv3D+dlibで顔器官検出する

Last updated at Posted at 2019-12-12

概要

顔をトラッキングしてLive2DっぽいことができるソフトをOpenSiv3Dで作っています。
たまーーにダウンロードされて新人Vtuberが生まれます。
https://youtu.be/tcCwrTjiSQA

画像

今回は、その顔器官検出の部分をゆる~~く解説します。
AdventCalender僕の当番の日まであと30分!書ききれるかな!

準備

OpenCVのダウンロード

https://opencv.org/releases/
からWindow用のOpenCVをダウンロードして展開します。

dlibのダウンロード

http://dlib.net/
からdlibの最新版をダウンロードしてきて展開します。

環境変数にパスを追加

コントロールパネル→システムとセキュリティ→システム→システムの詳細設定から環境変数を設定します。

環境変数 参照する場所
Dlib_ALL source.cppがあるフォルダ C:\dlib-19.18\dlib\all
Dlib_INCLUDE dlibのフォルダ C:\dlib-19.18
OpenCV_INCLUDE OpenCVのincludeフォルダ C:\OpenCV\opencv\build\include

プロジェクトを作る

OpenSiv3DのProjectを作る

みんな大好きOpenSiv3DのProjectを作りましょう。

プロジェクトにパスを通す

プロジェクト→[プロジェクト名]のプロパティ→C/C++→全般→追加のインクルードディレクトリに

$(OpenCV_INCLUDE)
$(Dlib_INCLUDE)
$(Dlib_ALL)

を追加します

source.cppをSourceFileに追加

dlib-19.18/dlib/allの中にあるsource.cppをプロジェクトのSourceFileに追加します。

たぶんdlib動かすのに必須な奴らを全部まとめた奴です。

こいつでincludeするファイルのためにDlib_ALLの環境変数を用意してみました。

shape_predictor_68_face_landmarksをダウンロードしておく

dlibで顔器官検出をするための学習済みデータが配布されているのでそれをダウンロードしておきます。

ダウンロードしたらApp以下にAssetsフォルダを作ってそこに入れておきます。

実装

Face

dlibで検出した顔の点を表現するclassを作ります。

Face.hpp
#pragma once
#include <Siv3D.hpp>

class Face
{
private:
    Array<Vec2> m_facePoints;

public:
    Face() = default;

    Face(const Array<Vec2>& facePoints);

    void draw(Color drawcolor) const;

    Array<Vec2> getFacePoints() const;
};

shape_predictor_68_face_landmarksでは68個のパーツのカメラ画像での座標が取れるのでそれをVec2のArrayとして持っておきます。

Face.cpp
#include "Face.hpp"

Face::Face(const Array<Vec2>& facePoints)
{
    m_facePoints = facePoints;
}

void Face::draw(Color drawcolor) const
{
    if (m_facePoints.size() != 68)
    {
        return;
    }

    //Ear to Ear
    for (size_t i = 1; i < 16; ++i)
    {
        Line(m_facePoints[i], m_facePoints[i - 1]).draw(drawcolor);
    }

    //Nose
    for (size_t i = 28; i < 30; ++i)
    {
        Line(m_facePoints[i], m_facePoints[i - 1]).draw(drawcolor);
    }

    //left eyebrow
    for (size_t i = 18; i < 21; ++i)
    {
        Line(m_facePoints[i], m_facePoints[i - 1]).draw(drawcolor);
    }

    //Right eyebrow
    for (size_t i = 23; i < 26; ++i)
    {
        Line(m_facePoints[i], m_facePoints[i - 1]).draw(drawcolor);
    }

    //Bottom part of the nose
    for (size_t i = 31; i < 35; ++i)
    {
        Line(m_facePoints[i], m_facePoints[i - 1]).draw(drawcolor);
    }

    //Line from the nose to the bottom part above
    Line(m_facePoints[30], m_facePoints[35]).draw(drawcolor);

    //Left eye
    for (size_t i = 37; i < 41; ++i)
    {
        Line(m_facePoints[i], m_facePoints[i - 1]).draw(drawcolor);
    }
    Line(m_facePoints[36], m_facePoints[41]).draw(drawcolor);

    //Right eye
    for (size_t i = 43; i < 47; ++i)
    {
        Line(m_facePoints[i], m_facePoints[i - 1]).draw(drawcolor);
    }
    Line(m_facePoints[42], m_facePoints[47]).draw(drawcolor);

    //Lips outerpart
    for (size_t i = 49; i < 59; ++i)
    {
        Line(m_facePoints[i], m_facePoints[i - 1]).draw(drawcolor);
    }
    Line(m_facePoints[48], m_facePoints[59]).draw(drawcolor);

    //Lips inside part
    for (size_t i = 61; i < 67; ++i)
    {
        Line(m_facePoints[i], m_facePoints[i - 1]).draw(drawcolor);
    }
    Line(m_facePoints[60], m_facePoints[67]).draw(drawcolor);
}

Array<Vec2> Face::getFacePoints() const
{
    return m_facePoints;
}

まぁ、こんな感じでええやろか。

FaceDetector

今日の本題。
顔の検出器を作ります。
方針としては、
コンストラクタでカメラのIndexを指定
start()でカメラを回し始めて
別スレッドでずーーーーっと顔の検出をしてる感じで

FaceDetector.hpp
# pragma once

# include <Siv3D.hpp>
# include <dlib/opencv.h>
# include <opencv2/highgui/highgui.hpp>
# include <dlib/image_processing/frontal_face_detector.h>
# include <dlib/image_processing/render_face_detections.h>
# include <dlib/image_processing.h>
# include <dlib/gui_widgets.h>
# include "Face.hpp"

class FaceDetector
{
private:
    std::thread m_thread;

    std::atomic<bool> m_abort = false;

    bool m_isActive = false;

    mutable std::mutex m_mutex;

    cv::Mat m_temp;

    cv::VideoCapture m_cap;

    dlib::shape_predictor m_pose_model;

    dlib::frontal_face_detector m_detector;

    Face m_detectedFace;

    dlib::cv_image<dlib::bgr_pixel> m_cimg;

    void stop();

    void run();

    void updateFaceData();

    Vec2 ToVec2(const dlib::point& p);

public:

    FaceDetector() = default;

    FaceDetector(int32 cameraNum);

    void start();

    Face getFace() const;

};

dlibの顔検出は少々重いので、別スレッドでやらないとFPSが落ちちゃってカクカクになります。

FaceDetector.cpp
#include "FaceDetector.hpp"

FaceDetector::FaceDetector(int32 cameraNum)
{
    m_cap = cv::VideoCapture(cameraNum);
    m_detector = dlib::get_frontal_face_detector();
    m_detectedFace = Face();
    dlib::deserialize("Assets/shape_predictor_68_face_landmarks.dat") >> m_pose_model;

    start();
}

Face FaceDetector::getFace() const
{
    std::lock_guard<std::mutex> lock(m_mutex);
    return m_detectedFace;
}

void FaceDetector::start()
{
    stop();

    m_isActive = true;

    m_thread = std::thread(&FaceDetector::run, this);
}

void FaceDetector::stop()
{
    if (!m_isActive)
    {
        return;
    }

    m_abort = true;

    m_thread.join();

    m_abort = false;

    m_isActive = false;
}

void FaceDetector::run()
{
    while (!m_abort)
    {
        if (!m_cap.read(m_temp))
        {
            return;
        }

        m_cimg = m_temp;

        updateFaceData();

    }
}

void FaceDetector::updateFaceData()
{
    std::vector<dlib::rectangle> faces = m_detector(m_cimg);

    Array<dlib::full_object_detection> shapes;

    for (unsigned long i = 0; i < faces.size(); ++i)
    {
        shapes.push_back(m_pose_model(m_cimg, faces[i]));
    }

    if(shapes.size() == 0)
    {
        return;
    }

    size_t mostBigFaceIndex = 0;
    double maxFaceSize = (ToVec2(shapes[0].part(0)) - ToVec2(shapes[0].part(16))).length();

    for (size_t i = 1; i < shapes.size(); ++i)
    {
        const double faceSize = (ToVec2(shapes[i].part(0)) - ToVec2(shapes[i].part(16))).length();
        if (faceSize > maxFaceSize)
        {
            mostBigFaceIndex = i;
            maxFaceSize = faceSize;
        }
    }

    Array<Vec2> faceParts;
    for (auto k : step<unsigned long>(68))
    {
        faceParts.push_back(ToVec2(shapes[mostBigFaceIndex].part(k)));
    }
    std::lock_guard<std::mutex> lock(m_mutex);
    m_detectedFace = Face(faceParts);
}

Vec2 FaceDetector::ToVec2(const dlib::point& p)
{
    return Vec2(p.x(), p.y());
}

まぁこんな感じで。

動かしてみる

顔の点を取って番号でも表示してみようかと思います。

Main.cpp

# include <Siv3D.hpp> // OpenSiv3D v0.4.2
# include "FaceDetector.hpp"

void Main()
{
    Scene::SetBackground(Palette::White);
    Font font(20);

    //引数にカメラのIndexを渡す
    FaceDetector faceDetector(0);
    faceDetector.start();

    Array<Vec2> facePoints;
    Face face;

    while (System::Update())
    {
        Face face = faceDetector.getFace();
        facePoints = face.getFacePoints();

        face.draw(Palette::Black);

        for (size_t i = 0; i < facePoints.size(); i++)
        {
            font(i).drawAt(facePoints[i], Palette::Red);
        }
    }
}

スクリーンショット.png

動いた!

終わりに

とりあえず動くところまでは解説できました。
dlibをC++で使っているドキュメントが思っていたより少なくて結構大変でした。
しかもlibをビルドしたあとにソースファイルをインクルードし始めたりして訳が分からん
ちなみにpythonならpip installするだけなので一行で環境が整います。
dlibについてもっとちゃんと勉強したい。

質問とか改善案があればコメントに投げるか僕に連絡ください。

2
4
0

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
2
4