OpenCV トラッキングAPIで顔面追尾

  • 3
    Like
  • 0
    Comment

始めに

OpenCVは非常に高機能かつオープンソースな画像処理ライブラリとして広く使われています。
今回、私は顔面の位置を認識してあれこれしたいと思ったのですが、いろいろと突っかかってしまったので、今後のメモ的な意味も含めてこの記事を書くことにしました。

環境

OS : Microsoft Windows8.1 64bit
CPU : Intel core i3 4005U
開発環境 : Visual Studio 2017
プログラミング言語 : C++
ビルド設定 : x64 Release
OpenCVのバージョン : 3.3.0

画像中の顔の座標を知りたい! 顔面検出器編

私は始め、画像中にある顔をカスケード型識別器を用いて認識しようと思いました。
以下に簡単な顔検出プログラムを置いておきます。

#include <opencv2/opencv.hpp>

cv::Mat detect_face(const cv::Mat &image, const std::string &cascade_file)
{
    cv::CascadeClassifier cascade;
    std::vector<cv::Rect> faces;

    //検出器読み込み
    cascade.load(cascade_file);

    //顔検出
    cascade.detectMultiScale(image, faces, 1, 3, 0, cv::Size(50, 50));

    //結果描画
    for (const cv::Rect &face_rect : faces) 
    {
        cv::rectangle(image, cv::Point(face_rect.x, face_rect.y), cv::Point(face_rect.x + face_rect.width, face_rect.y + face_rect.height), cv::Scalar(0, 200, 0), 3, CV_AA);
    }

    return image;
}

int main(int argc, char const **argv) 
{
    //画像読み込み
    cv::Mat image = cv::imread(argv[1]);

    //検出器のパス
    std::string filename = argv[2];

    //顔検出
    cv::Mat face_image = detect_face(image, filename);

    //表示
    cv::imshow("detect face", face_image);
    cv::waitKey(0);

    return 0;
}

これを少し応用して、カメラから画像を受け取りながら顔認識をさせるようにしたのですが、重すぎ、精度悪すぎという結果に終わってしまいました。(そもそもそういう用途向けに作られていない?)

画像中の顔の座標を知りたい! トラッカ編

顔検出器自体は非常に有用なものなのですが、なんせ今回の目的である、迅速にかつ正確に顔面の位置を取得し続けることに対してはあまり向いていないようでした。なので、他の手段を講じます。
いろいろと顔面追尾などの情報を漁っていると、どうやらOpenCVのエクストラモジュール(opencv_contrib)に物体追跡用のアルゴリズムが実装されているとのことでした。なので、OpenCVをビルドし直し、かれこれ一時間半ほどかけて環境を整え直しました。さらに、色々なドキュメントを見ながらプログラムを書くこと数十分。構想しているものの骨組み中の骨組み程度ですが、一応こんなものができました。

#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/tracking/tracker.hpp>
#include <opencv2/tracking/tldDataset.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

int tracker_init(cv::Ptr<cv::TrackerMedianFlow> &tracker, cv::Rect2d &rect)
{
    cv::VideoCapture cap(0);
    if (!cap.isOpened())
    {
        std::cout << "CAN'T OPEN CAMERA." << std::endl;
        return -1;
    }

    cv::Mat frame;
    cv::namedWindow("Select face");

    while (true)
    {
        cap >> frame;
        if (frame.empty())
        {
            std::cout << "FAILD TO READ A FRAME" << std::endl;
            cv::destroyAllWindows();
            break;
        }

        cv::imshow("Select face", frame);

        switch (cv::waitKey(1))
        {
        case 'q':
            return -1;
        case 't':
            rect = cv::selectROI("tracker", frame);
            tracker->init(frame, rect);
            cv::destroyAllWindows();
            return 1;
        default:
            break;
        }
    }

    return -1;
}

int main(int argc, char **argv)
{
    cv::Ptr<cv::TrackerMedianFlow> tracker = cv::TrackerMedianFlow::create();
    cv::Rect2d roi;

    if (tracker_init(tracker, roi) == -1)
    {
        /*
        * 初期化に失敗
        */
        return -1;
    }

    cv::Scalar color = cv::Scalar(0, 255, 0);
    cv::Mat frame;
    cv::VideoCapture cap(0);
    if (!cap.isOpened())
    {
        std::cout << "CAN'T OPEN CAMERA." << std::endl;
        return -1;
    }

    while (cv::waitKey(1) != 'q')
    {
        cap >> frame;
        if (frame.empty())
        {
            break;
        }

        //更新
        tracker->update(frame, roi);

        //結果表示
        cv::rectangle(frame, roi, color, 1, 1);

        cv::imshow("tracker", frame);
    }

    return 0;
}

このプログラムでは、かなり高速かつ精度良く顔面をトラックすることができました。
やはり餅は餅屋といいますか、それぞれの長所を利用していくべきなんですね。

トラックアルゴリズムのパフォーマンス

今回は、数あるトラックアルゴリズムの中からMedianFlowというアルゴリズムを選びました。なんでこれかというと、数個触ってこれが一番速いし精度もいいと思ったからなのですが、ちゃんと比較を行わないと駄目ですよね。以下に体感でですがランキングを作っておきます。

体感速度・精度のランク

ランク アルゴリズム名
1 MedianFlow
2 Boosting
3 MIL
4 KFC
5 TLD

とりあえずこの5つのアルゴリズムを試してみました。体感的にはこんな感じです。ここで注意が必要なのはBoostingアルゴリズムです。速度はとても優秀なのですが、精度に結構癖があります。認識結果として得られるcv::Rectの位置を見る限り、常にぷるぷると震えています。TLDアルゴリズムはかなり重いです。自分のパソコンのスペックが弱いということもあるかもしれませんが、MedianFlowアルゴリズム等と比べるとかなり重いです。(重すぎて精度の部分がよくわかりませんでした。)
さて、こんな茶番ランキングは放っておいて、より正確なパフォーマンスまとめを公開してくださっているありがたいお方がいらっしゃるので、そちらを確認しましょう。以下にリンクを貼っておきます。本当に感謝です。
http://irohalog.hatenablog.com/entry/opencv_tracking_api

終わりに

今回用いたOpenCVのトラッキングAPIはもちろん顔面以外にも応用することができます。人そのものだったり、はたまた車や自転車だったり。こう考え出すといろいろなアイデアが生まれてきて楽しいですね!では皆さんも楽しいOpenCVライフを!

参考文献

https://taktak.jp/2017/05/13/2280
https://qiita.com/hmichu/items/3615ddcc93851ad59abe
http://irohalog.hatenablog.com/entry/opencv_tracking_api
ありがとうございます。