読者です 読者をやめる 読者になる 読者になる

のどあめ

ググってもなかなか出てこなかった情報を垂れ流すブログ。

OpenCV 3.0で人物追跡したい!

最近社内でライトニングトークなるものがありました。 そのうち画像を使う機会がありそうで復習したかった半分、 OpenCV3.0使ってみたかった半分で単カメラの人物追跡をテーマに軽く解説をしてきました。

研究室時代ではいろんな論文の実装を組み込んで何とか実装していた単カメラ人物追跡ですが、 OpenCV3.0を使えば、100行くらいで人物追跡できます。素晴らしい限りです。

今回は、その実装について説明したいと思います。

ただし、以下の点には注意です。

  • 理論などはすべて省略します
  • 高速化は考えません(本当は大事)
  • 使っている技術は古いです(2005-2006くらいの技術)

用いた環境

  • C++11
  • OpenCV 3.0
  • boost 1.58 (ファイル読み込みのみ)

人物追跡

ここでいう人物追跡とは、単一のカメラ映像に写る人物の軌跡を取得することです。 人物検出をして、その結果を追跡することで実現できます。

人物検出

画像の中から人物の矩形を取得します。

OpenCVでは、cv::HOGDescriptor::getDefaultPeopleDetectorというズバリなものが用意されていますので、 今回はこれを利用します。

(人物)追跡

人物検出の結果を起点に、そのものが映像中に動いた奇跡を追跡します。 (人物以外でも使えます)

OpenCVでは、cv::Trackerというインタフェースが用意されていますので、 今回はこれを利用します。

実装

実装はこんな感じです。 ykicisk/tracking · GitHub

#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/tracking/tracker.hpp>
#include <boost/filesystem.hpp>

const cv::Size MAX_DETECT_SIZE = cv::Size(100, 200);
const int MAX_MISS_FRAME = 10;
const double MIN_NEW_DETECT_INTERSECTION_RATE = 0.5;

class MyTracker {
private:
    static int next_id;
    int id;
    int n_miss_frame = 0;
    cv::Rect2d rect;
    cv::Ptr<cv::Tracker> cv_tracker;
public:
    // フレーム画像と追跡対象(Rect)で初期化
    MyTracker(const cv::Mat& _frame, const cv::Rect2d& _rect) 
        : id(next_id++), rect(_rect) {
        cv_tracker = cv::Tracker::create("BOOSTING"); //  or "MIL"
        cv_tracker->init(_frame, _rect);
    }
    // 次フレームを入力にして、追跡対象の追跡(true)
    // MAX_MISS_FRAME以上検出が登録されていない場合は追跡失敗(false)
    bool update(const cv::Mat& _frame){
        n_miss_frame++;
        return cv_tracker->update(_frame, rect) && n_miss_frame < MAX_MISS_FRAME;
    }
    // 新しい検出(Rect)を登録。現在位置と近ければ受理してn_miss_frameをリセット(true)
    // そうでなければ(false)
    bool registerNewDetect(const cv::Rect2d& _new_detect){
        double intersection_rate = 1.0 * (_new_detect & rect).area() / (_new_detect | rect).area();
        bool is_registered = intersection_rate > MIN_NEW_DETECT_INTERSECTION_RATE;
        if (is_registered) n_miss_frame = 0;
        return is_registered;
    }
    // trackerの現在地を_imageに書き込む
    void draw(cv::Mat& _image) const{
        cv::rectangle(_image, rect, cv::Scalar(255, 0, 0), 2, 1);
        cv::putText(_image, cv::format("%03d", id), cv::Point(rect.x + 5, rect.y + 17), 
                cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255,0,0), 1, CV_AA);
    }
};
int MyTracker::next_id = 0;


int main(int argc, char* argv[]){
    if(argc != 2){
        std::cout << "usage: " << argv[0] << " videodir" << std::endl;
        exit(1);
    }
    // フレーム画像列のパスを取得
    namespace fs = boost::filesystem;
    std::vector<std::string> frame_paths;
    for(auto it = fs::directory_iterator(argv[1]); it != fs::directory_iterator(); ++it){
        frame_paths.push_back(it->path().string());
    }
    // detector, trackerの宣言
    cv::HOGDescriptor detector;
    detector.setSVMDetector(cv::HOGDescriptor::getDefaultPeopleDetector());
    std::vector<MyTracker> trackers;
    // 1フレームずつループ
    for (auto& frame_path : frame_paths){
        std::cout << "frame : " << frame_path << std::endl;
        cv::Mat frame = cv::imread(frame_path);
        // 人物検出
        std::vector<cv::Rect> detections;
        detector.detectMultiScale(frame, detections);
        // trackerの更新(追跡に失敗したら削除)
        for (auto t_it = trackers.begin(); t_it != trackers.end();){
            t_it = (t_it->update(frame)) ? std::next(t_it) : trackers.erase(t_it);
        }
        // 新しい検出があればそれを起点にtrackerを作成。(既存Trackerに重なる検出は無視)
        for(auto& d_rect : detections){
            if (d_rect.size().area() > MAX_DETECT_SIZE.area()) continue;
            bool exists = std::any_of(trackers.begin(), trackers.end(), 
                    [&d_rect](MyTracker& t){return t.registerNewDetect(d_rect);});
            if(!exists) trackers.push_back(MyTracker(frame, d_rect));
        }
        // 人物追跡と人物検出の結果を表示
        cv::Mat image = frame.clone();
        for(auto& t : trackers) t.draw(image);
        for(auto& d_rect : detections) cv::rectangle(image, d_rect, cv::Scalar(0, 255, 0), 2, 1);
        cv::imshow("demo", image);
        cv::waitKey(1);
    }
    return 0;
}

動画のフレームごとに人物検出と追跡を繰り返して実現します。

そのままだとTrackerがどんどん増えていくので、 Trackerと重なる検出がでてこなくなったTrackerは消すようにしています。

実験

PETS2009 S2.L1のView001で使って動かしてみました。

緑が人物検出の結果、青が追跡結果です。

考察

思ったよりイマイチですね。

人物検出の誤検出・未検出について

OpenCV3.0のdetectMultiScaleを使った人物検出ではこれが限界だと思います。

もっとリッチな人物検出手法をつかったり、 シーンの知識(どの当たりにどんな大きさの人が写るなど)を利用すればもっと検出がよくなると思います。

追跡ミスについて

汎用追跡器を使ったこともありますが、オクルージョンにめちゃくちゃ弱いです。 真ん中のポールを通過した人はもれなくおかしなことになっていますね。

これに解決するためには、もっと人物に特化した追跡器を使ったり、 関連付けられた人物画像を一旦バラバラにして最適化で同一人物をくっつけなおしたりすると良いと思います。

まとめ

  • OpenCV3.0を使えば簡単に単カメラ人物追跡できる
  • 結果はいまいち。実際のカメラは更にいろいろあるので更におかしい事が起こる
  • 人物追跡にかぎらず、この辺りの分野は闇が深い

次回があれば、今回取得した人物画像列について、 複数カメラでの対応付けを行う方法について説明します。

でも次回はありません。