OpenCV
tracking
OpenCV3
物体追跡
トラッキング

こんなに簡単!? トラッキング

More than 3 years have passed since last update.


1.はじめに

OpenCVは,エクストラモジュール(opencv_contrib)を追加することでより様々な機能が使えます。今回は,Trackingに注目してみました。

OpenCVのTrackingって使えるのかなーとか性能どうなのとか,気になりますよね?そんな方は,こちらに性能評価が詳しく書かれております。

もしかしたら,この記事もx番煎じかもしれませんが,自分へのメモも兼ねて書きます。


2.物体追跡とは

物体追跡とは,与えられた動画から,指定した対象が各画像間でどのように移動したかを推定することです。物体追跡(Tracking)の流れとしては,簡単に書くと次のようになります。


  1. 物体を指定する(物体を検出する)

  2. 物体の移動距離を推定する

  3. 2.の繰り返し

また,追跡の処理方法も1フレームずつで処理する(逐次処理)か動画すべてを処理する(一括処理)などあります。

OpenCV Tracking APIは前者のものが用意されています。

もし自分で作成する人がいる場合は,

逐次処理でも,領域ベースにするか特徴点ベースにするか悩みどころだと思います。

ここでは,簡単に紹介すると,領域ベースなら


  • 全探索(アクティブ探索)

  • 局所探索(Mean Shift)

  • 確率的探索(Particle Filter)

特徴ベースなら,様々な特徴点の手法がOpenCVには備わっています。

前回,物体検出で紹介したのでこちらを参照してください。

逐次処理/一括処理,領域ベース/特徴点ベースそれぞれメリット・デメリットあるので,目的に応じて利用してください。

より詳しい情報は,書籍やネットで検索すれば多数でてきます。


3.OpenCV Tracking API

Tracking APIには,一つの物体を追跡するTrackerと複数の物体を追跡するMultiTrackerがあります。

ここでは,一つの物体を追跡するTrackerについて説明します。

基本は,


  1. Trackerの生成

  2. Trackerの初期化

  3. Trackerの更新

この流れでプログラムはできます。簡単ですね。

// Trackerの生成

cv::Ptr<cv::Tracker> tracker = cv::Tracker::create("使いたい手法");

// Trakcerの初期化
tracker->init(動画フレーム, 対象領域);

// Trackerの更新
trackerKCF->update(動画フレーム, 対象領域);

使いたい手法には,次の5つが用意されています。


  • MIL

  • BOOSTING

  • MEDIANFLOW

  • TLD

  • KCF

さらに,OpenCV3.1からは対象を指定する関数が用意されました。

3種類の方法があります。

Rect2d cv::selectROI(Mat img, bool fromCenter = true);

Rect2d cv::selectROI(const cv::String& windowName, Mat img, bool showCrossair = true, bool fromCenter = true);
void cv::selectROI(const cv::String& windowName, Mat img, std::vector< Rect2d > & boundingBox, bool fromCenter = true);

1つ目と2つ目は,動画の1フレーム目の画像を渡し,マウスで指定した領域が返る。

3つ目は,動画の1フレーム目の画像と指定領域を渡す。

通常は,1つ目か2つ目を使えば問題無いでしょう。


4.実験結果

今回使用したデータセットは,Bonn Benchmark on TrackingにあるSeq.A,Seq.B,Seq.Cの3つです。

それぞれground truthも用意されています。


Seq.A

Seq.Aの初期フレーム上でのターゲットの領域(始点x,始点y,終点x,終点y)は,(200.34,113.73,245.47,159.32)です。

マウスで指定した領域(始点x,始点y,終点x,終点y)は,(201,113,245,159)です。

切り取った画像を示しておきます。

vid_A_ball_target.jpeg


Seq.B

Seq.Bの初期フレーム上でのターゲットの領域(始点x,始点y,終点x,終点y)は,(124.67,92.308,171.4,150.88)です。

マウスで指定した領域(始点x,始点y,終点x,終点y)は,(125,89,171,151)です。

切り取った画像を示しておきます。

vid_B_cup_target.jpeg


Seq.C

Seq.Cの初期フレーム上でのターゲットの領域(始点x,始点y,終点x,終点y)は,(131.21,50.013,167.71,139.66)です。

マウスで指定した領域(始点x,始点y,終点x,終点y)は,(130,50,163,140)です。

切り取った画像を示しておきます。

vid_C_juice_target.jpeg


結果動画

結果の動画を,Youtubeにアップしました。下の画像をクリックするとYoutubeが開きます。

OpenCV Tracking API


5.おわりに

かなり楽にコードがかけます。趣味プロなどで"何かつくる"などには向いているのではないでしょうか。


実験環境

私の実験環境は次のようになっています。

OS: Windows 10 pro 64bit

CPU: Intel(R) Core(TM) i7-6700K 4.00GHz

RAM: DDR4 8GB×2 (16GB)

開発環境: Visual Studio 2013 (x64)

コンパイルオプション: 実行速度の最大化(/O2),OpenMPなし


コード

今回のコードを参考に示しておきます。


Tracking

#include <opencv2/core.hpp>

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

#pragma comment(lib, "opencv_core310.lib")
#pragma comment(lib, "opencv_imgcodecs310.lib")
#pragma comment(lib, "opencv_videoio310.lib")
#pragma comment(lib, "opencv_highgui310.lib")
#pragma comment(lib, "opencv_tracking310.lib")
#pragma comment(lib, "opencv_imgproc310.lib")

int main(int argc, char* argv[])
{
if (argc < 2) {
std::cout << "exe [video]" << std::endl;
return -1;
}

// Trackerの生成
cv::Ptr<cv::Tracker> trackerKCF = cv::Tracker::create("KCF");
cv::Ptr<cv::Tracker> trackerTLD = cv::Tracker::create("TLD");
cv::Ptr<cv::Tracker> trackerMEDIANFLOW = cv::Tracker::create("MEDIANFLOW");
cv::Ptr<cv::Tracker> trackerBOOSTING = cv::Tracker::create("BOOSTING");
cv::Ptr<cv::Tracker> trackerMIL = cv::Tracker::create("MIL");

cv::VideoCapture cap(argv[1]);
if (!cap.isOpened()) {
std::cout << "ビデオが開けません。" << std::endl;
return -1;
}

cv::Mat frame;
cap >> frame;

// 最初のフレームから追跡対象を選択
cv::Rect2d roi = cv::selectROI("tracker", frame); // マウスで中心を選択してドラッグ
cv::Rect2d roiTLD = roi;
cv::Rect2d roiMEDIANFLOW = roi;
cv::Rect2d roiBOOSTING = roi;
cv::Rect2d roiMIL = roi;

cv::Mat target(frame, roi);
cv::imwrite("target.jpeg", target);
std::cout << "(x, y, width, height) = (" << roi.x << "," << roi.y << "," << roi.width << "," << roi.height << ")" << std::endl;

if (roi.width == 0 || roi.height == 0)
return -1;

// Trackerの初期化
trackerKCF->init(frame, roi);
trackerTLD->init(frame, roiTLD);
trackerMEDIANFLOW->init(frame, roiMEDIANFLOW);
trackerBOOSTING->init(frame, roiBOOSTING);
trackerMIL->init(frame, roiMIL);

// Trackerごとの色
cv::Scalar colorkcf = cv::Scalar(0, 255, 0);
cv::Scalar colortld = cv::Scalar(0, 255, 255);
cv::Scalar colormedianflow = cv::Scalar(0, 0, 255);
cv::Scalar colorboosting = cv::Scalar(255, 255, 0);
cv::Scalar colormit = cv::Scalar(255, 0, 255);

// 動画保存の設定
double fps = cap.get(CV_CAP_PROP_FPS);
cv::Size size = cv::Size(cap.get(CV_CAP_PROP_FRAME_WIDTH), cap.get(CV_CAP_PROP_FRAME_HEIGHT));
const int fourcc = cv::VideoWriter::fourcc('X', 'V', 'I', 'D');
std::string filename = "output.avi";
cv::VideoWriter writer(filename, fourcc, fps, size);

while (1) {
cap >> frame;
if (frame.empty())
break;

// 更新
trackerKCF->update(frame, roi);
trackerTLD->update(frame, roiTLD);
trackerMEDIANFLOW->update(frame, roiMEDIANFLOW);
trackerBOOSTING->update(frame, roiBOOSTING);
trackerMIL->update(frame, roiMIL);

// 矩形で囲む
cv::rectangle(frame, roi, colorkcf, 1, 1);
cv::rectangle(frame, roiTLD, colortld, 1, 1);
cv::rectangle(frame, roiMEDIANFLOW, colormedianflow, 1, 1);
cv::rectangle(frame, roiBOOSTING, colorboosting, 1, 1);
cv::rectangle(frame, roiMIL, colormit, 1, 1);

cv::putText(frame, "- KCF", cv::Point(5, 20), cv::FONT_HERSHEY_SIMPLEX, .5, colorkcf, 1, CV_AA);
cv::putText(frame, "- TLD", cv::Point(65, 20), cv::FONT_HERSHEY_SIMPLEX, .5, colortld, 1, CV_AA);
cv::putText(frame, "- MEDIANFLOW", cv::Point(125, 20), cv::FONT_HERSHEY_SIMPLEX, .5, colormedianflow, 1, CV_AA);
cv::putText(frame, "- BOOSTING", cv::Point(5, 40), cv::FONT_HERSHEY_SIMPLEX, .5, colorboosting, 1, CV_AA);
cv::putText(frame, "- MIL", cv::Point(115, 40), cv::FONT_HERSHEY_SIMPLEX, .5, colormit, 1, CV_AA);

cv::imshow("tracker", frame);
writer << frame;

int key = cv::waitKey(30);
if (key == 0x1b) //ESCキー
break;
}

return 0;
}