圖像算法之opencv的目標追蹤方法概述

什麼是目標追蹤

在視頻後續幀中定位一個物體,稱爲追蹤。雖然定義簡單,但是目標追蹤是一個相對廣義的定義,比如以下問題 也屬於目標追蹤問題:

稠密光流:此類算法用來評估一個視頻幀中的每個像素的運動向量

稀疏光流:此類算法,像Kanade-Lucas-Tomashi(KLT)特徵追蹤,追蹤一張圖片中幾個特徵點的位置

Kalman Filtering:一個非常出名的信號處理算法基於先前的運動信息用來預測運動目標的位置。早期用於導彈的導航

MeanShift和Camshift:這些算法是用來定位密度函數的最大值,也用於追蹤

單一目標追蹤:此類追蹤器中,第一幀中的用矩形標識目標的位置。然後在接下來的幀中用追蹤算法。日常生活中,此類追蹤器用於與目標檢測混合使用。

多目標追蹤查找算法:如果我們有一個非常快的目標檢測器,在每一幀中檢測多個目標,然後運行一個追蹤查找算法,來識別當前幀中某個矩形對應下一幀中的某個矩形。

追蹤 VS 檢測

如果你使用過opencv 的人臉檢測算法,你就知道算法可以實時,並且很準確的檢測到每一幀中的人臉。那麼,爲什麼首先要追蹤呢?

我們首先考慮幾個問題:

跟蹤比檢測更快:通常跟蹤算法比檢測算法更快。原因很簡單,當你跟蹤前一幀中的某個物體時,你已經知道了此物體的外觀信息。同時你也知道前一幀的位置,以及運行的速度和方向。因而,在下一幀你可以用所有的信息來預測下一幀中物體的位置,以及在一個很小範圍內搜索即可得到目標的位置。好的追蹤算法會利用所有已知信息來追蹤點,但是檢測算法每次都要重頭開始。所以,通常,如果我們在第n幀開始檢測,那麼我們需要在第n-1幀開始跟蹤。那麼爲什麼不簡單地第一幀開始檢測,並從後續所有幀開始跟蹤。因爲跟蹤會利用其已知信息,但是也可能會丟失目標,因爲目標可能被障礙物遮擋,甚至於目標移動速度太快,算法跟不上。通常,跟蹤算法會累計誤差,而且bbox 會慢慢偏離目標。爲了修復這些問題,需要不斷運行檢測算法。檢測算法用大量樣本訓練之後,更清楚目標類別的大體特徵。另一方面,跟蹤算法更清楚它所跟蹤的類別中某一個特定實例。

檢測失敗的話,跟蹤可以幫忙:如果你在視頻中檢測人臉,然後人臉被某個物體遮擋了,人臉檢測算法大概率會失敗。好的跟蹤算法,另一方面可以處理一定程度的遮擋

跟蹤會保存實體:目標檢測算法的輸出是包含物體的一個矩形的數組,。但是沒有此物體的個體信息,

Opencv 3 的跟蹤API

opencv實現了7中跟蹤算法,但是3.4.1以及以上版本纔有完整的7種。BOOSTING, MIL, KCF, TLD, MEDIANFLOW, GOTURN, MOSSE,CSRT。

代碼

C++代碼

#include <opencv2/opencv.hpp>

#include <opencv2/tracking.hpp>

#include <opencv2/core/ocl.hpp>

using namespace cv;

using namespace std;

// Convert to string

#define SSTR( x ) static_cast< std::ostringstream & >( \

( std::ostringstream() << std::dec << x ) ).str()

int main(int argc, char **argv)

{

// List of tracker types in OpenCV 3.4.1

string trackerTypes[8] = {"BOOSTING", "MIL", "KCF", "TLD","MEDIANFLOW", "GOTURN", "MOSSE", "CSRT"};

// vector <string> trackerTypes(types, std::end(types));

// Create a tracker

string trackerType = trackerTypes[2];

Ptr<Tracker> tracker;

#if (CV_MINOR_VERSION < 3)

{

tracker = Tracker::create(trackerType);

}

#else

{

if (trackerType == "BOOSTING")

tracker = TrackerBoosting::create();

if (trackerType == "MIL")

tracker = TrackerMIL::create();

if (trackerType == "KCF")

tracker = TrackerKCF::create();

if (trackerType == "TLD")

tracker = TrackerTLD::create();

if (trackerType == "MEDIANFLOW")

tracker = TrackerMedianFlow::create();

if (trackerType == "GOTURN")

tracker = TrackerGOTURN::create();

if (trackerType == "MOSSE")

tracker = TrackerMOSSE::create();

if (trackerType == "CSRT")

tracker = TrackerCSRT::create();

}

#endif

// Read video

VideoCapture video("videos/chaplin.mp4");

// Exit if video is not opened

if(!video.isOpened())

{

cout << "Could not read video file" << endl;

return 1;

}

// Read first frame

Mat frame;

bool ok = video.read(frame);

// Define initial bounding box

Rect2d bbox(287, 23, 86, 320);

// Uncomment the line below to select a different bounding box

// bbox = selectROI(frame, false);

// Display bounding box.

rectangle(frame, bbox, Scalar( 255, 0, 0 ), 2, 1 );

imshow("Tracking", frame);

tracker->init(frame, bbox);

while(video.read(frame))

{

// Start timer

double timer = (double)getTickCount();

// Update the tracking result

bool ok = tracker->update(frame, bbox);

// Calculate Frames per second (FPS)

float fps = getTickFrequency() / ((double)getTickCount() - timer);

if (ok)

{

// Tracking success : Draw the tracked object

rectangle(frame, bbox, Scalar( 255, 0, 0 ), 2, 1 );

}

else

{

// Tracking failure detected.

putText(frame, "Tracking failure detected", Point(100,80), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0,0,255),2);

}

// Display tracker type on frame

putText(frame, trackerType + " Tracker", Point(100,20), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(50,170,50),2);

// Display FPS on frame

putText(frame, "FPS : " + SSTR(int(fps)), Point(100,50), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(50,170,50), 2);

// Display frame.

imshow("Tracking", frame);

// Exit if ESC pressed.

int k = waitKey(1);

if(k == 27)

{

break;

}

}

}

Python代碼

import cv2

import sys

(major_ver, minor_ver, subminor_ver) = (cv2.__version__).split('.')

if __name__ == '__main__' :

# Set up tracker.

# Instead of MIL, you can also use

tracker_types = ['BOOSTING', 'MIL','KCF', 'TLD', 'MEDIANFLOW', 'GOTURN', 'MOSSE', 'CSRT']

tracker_type = tracker_types[2]

if int(minor_ver) < 3:

tracker = cv2.Tracker_create(tracker_type)

else:

if tracker_type == 'BOOSTING':

tracker = cv2.TrackerBoosting_create()

if tracker_type == 'MIL':

tracker = cv2.TrackerMIL_create()

if tracker_type == 'KCF':

tracker = cv2.TrackerKCF_create()

if tracker_type == 'TLD':

tracker = cv2.TrackerTLD_create()

if tracker_type == 'MEDIANFLOW':

tracker = cv2.TrackerMedianFlow_create()

if tracker_type == 'GOTURN':

tracker = cv2.TrackerGOTURN_create()

if tracker_type == 'MOSSE':

tracker = cv2.TrackerMOSSE_create()

if tracker_type == "CSRT":

tracker = cv2.TrackerCSRT_create()

# Read video

video = cv2.VideoCapture("videos/chaplin.mp4")

# Exit if video not opened.

if not video.isOpened():

print "Could not open video"

sys.exit()

# Read first frame.

ok, frame = video.read()

if not ok:

print 'Cannot read video file'

sys.exit()

# Define an initial bounding box

bbox = (287, 23, 86, 320)

# Uncomment the line below to select a different bounding box

bbox = cv2.selectROI(frame, False)

# Initialize tracker with first frame and bounding box

ok = tracker.init(frame, bbox)

while True:

# Read a new frame

ok, frame = video.read()

if not ok:

break

# Start timer

timer = cv2.getTickCount()

# Update tracker

ok, bbox = tracker.update(frame)

# Calculate Frames per second (FPS)

fps = cv2.getTickFrequency() / (cv2.getTickCount() - timer);

# Draw bounding box

if ok:

# Tracking success

p1 = (int(bbox[0]), int(bbox[1]))

p2 = (int(bbox[0] + bbox[2]), int(bbox[1] + bbox[3]))

cv2.rectangle(frame, p1, p2, (255,0,0), 2, 1)

else :

# Tracking failure

cv2.putText(frame, "Tracking failure detected", (100,80), cv2.FONT_HERSHEY_SIMPLEX, 0.75,(0,0,255),2)

# Display tracker type on frame

cv2.putText(frame, tracker_type + " Tracker", (100,20), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50),2);

# Display FPS on frame

cv2.putText(frame, "FPS : " + str(int(fps)), (100,50), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50), 2);

# Display result

cv2.imshow("Tracking", frame)

# Exit if ESC pressed

k = cv2.waitKey(1) & 0xff

if k == 27 : break

追蹤算法詳細

首先,思考下跟蹤時我們的目標是在當前幀找到在前面的所有或絕大部分幀中正確跟蹤的目標。由於我們追蹤目標到當前幀,所以我們已知它是如何運動的,也即我們知道運行模型的參數。所謂運行模型,就是我們知道前面幀中的目標的位置和速度。即便你對目標一無所知,但是根據當前運行模型也可以預測目標的可能位置,並且這個預測可能會很準確。

但是,我們有更多的信息,比如我們可以對目標進行編碼來代表目標的外觀,這樣的外觀模型。此模型可以用來搜索,由運動模型預測的臨近範圍內的目標來獲得更加準確的預測。

運動模型預測目標的大概位置,外觀模型微調此預估,來獲得一個更準確的預測

如果說目標很簡單,並且外觀改變不大的話,可以簡單地使用一個模板作爲外觀模型並在圖像中搜索模板就可以。分類器的任務就是簡單地判別一個矩形框是目標還是背景。分類器的輸入是一個圖像patch,返回一個0到1之間的分值。分值爲0代表是背景,1則爲目標。在機器學習中,我們通常用在線學習代表算法可以在運行時飛快地訓練完,而離線分類器需要幾千個樣本來訓練一個分類器,而在線算法僅需要幾個樣本就可以。

分類器由正樣本(目標)和負樣本(非目標)來訓練得到,以此分類器學習到目標與非目標之間的差異。在訓練在線分類器時,我們沒有數量衆多的正負樣本。

Boost 追蹤器

此跟蹤器基於在線版本的AdaBoost,這個是以Haar特徵級聯的人臉檢測器內部使用。此分類器需要在運行時以正負樣本來訓練。

其初始框由用戶指定,作爲追蹤的正樣本,而在框範圍之外許多其他patch都作爲背景。

在新的一幀圖像中,分類器在前一幀框的周圍的每個像素上分類,並給出得分。

目標的新位置即得分最高的這樣一來有新的正樣本來重新訓練分類器。

依次類推。

優點:幾乎沒有,幾十年前的技術。 缺點:追蹤性能一般,它無法感知追蹤什麼時候會失敗。

MIL追蹤

算法與Boost很像,唯一的區別是,它會考慮當前標定框周圍小部分框同時作爲正樣本,你可能認爲這個想法比較爛,因爲大部分的這些正樣本其實目標並不在中心。

這就是MIL(Multiple Instance Learning)的獨特之處,在MIL中你不需要指定正負樣本,而是正負樣包(bags)。在正樣本包中的並不全是正樣本,而是僅需要一個樣本是正樣本即可。當前示例中,正樣本包裏面的樣本包含的是處於中心位置的框,以及中心位置周圍的像素所形成的框。即便當前位置的跟蹤目標不準確,從以當前位置爲中心在周圍像素抽取的樣本框所構成的正樣本包中,仍然有很大概率命中一個恰好處於中心位置的框。

優點:性能很好,不會像Boost那樣會偏移,即便出現部分遮擋依然表現不錯。 在Opencv3.0中效果最好的追蹤器,如果在更高版本里,選擇KCF 缺點:沒法應對全遮擋,追蹤失敗也較難的得到反饋。

KCF 追蹤

KCF即Kernelized Correlation Filters,思路借鑑了前面兩個。注意到MIL所使用的多個正樣本之間存在交大的重疊區域。這些重疊數據可以引出一些較好的數學特性,這些特性同時可以用來構造更快更準確的分類器。

優點:速度和精度都比MIL效果好,並且追蹤失敗反饋比Boost和MIL好。Opencv 3.1以上版本最好的分類器。 缺點:完全遮擋之後沒法恢復。

TLD追蹤

TLD即Tracking, learning and detection,如其名此算法由三部分組成追蹤,學習,檢測。追蹤器逐幀追蹤目標,檢測器定位所有到當前爲止觀察到的外觀,如果有必要則糾正追蹤器。學習會評估檢測器的錯誤並更新,以避免進一步出錯。此追蹤器可能會產生跳躍,比如你正在追蹤一個人,但是場景中存在多個人,此追蹤器可能會突然跳到另外一個行人進行追蹤。優勢是:此追蹤器可以應對大尺度變換,運行以及遮擋問題。如果說你的視頻序列中,某個物體隱藏在另外一個物體之後,此追蹤器可能是個好選擇。

優勢:多幀中被遮擋依然可以被檢測到,目標尺度變換也可以被處理。 缺點:容易誤跟蹤導致基本不可用。

MedianFlow 追蹤

此追蹤器在視頻的前向時間和後向時間同時追蹤目標,然後評估兩個方向的軌跡的誤差。最小化前後向誤差,使得其可以有效地檢測追蹤失敗的清情形,並在視頻中選擇相對可靠的軌跡。

實測時發現,此追蹤器在運動可預測以及運動速度較小時性能最好。而不像其他追蹤器那樣,即便追蹤失敗繼續追蹤,此追蹤器很快就知道追蹤失敗

優點:對追蹤失敗反饋敏銳,當場景中運動可預測以及沒有遮擋時,較好 缺點:急速運動場景下會失敗。

GoTurn 追蹤

這個是唯一使用CNN方法的追蹤器,此算法對視點變化,光照變換以及形變等問題的魯棒性較好。但是無法處理遮擋問題。

注意:它使用caffe模型來追蹤,需要下載caffe模型以及proto txt文件。

MOSSE 追蹤

MOSSE即Minimum Output Sum of Squared Error,使用一個自適應協相關來追蹤,產生穩定的協相關過濾器,並使用單幀來初始化。MOSSE魯棒性較好,可以應對光線變換,尺度變換,姿勢變動以及非網格化的變形。它也能基於峯值與旁瓣比例來檢測遮擋,這使得追蹤可以在目標消失時暫停並在目標出現時重啓。MOSSE可以在高FPS(高達450以上)的場景下運行。易於實現,並且與其他複雜追蹤器一樣精確,並且可以更快。但是,在性能尺度上看,大幅度落後於基於深度學習的追蹤器。

CSRT 追蹤

在 Discriminative Correlation Filter with Channel and Spatial Reliability (DCF-CSR)中,我們使用空間依賴圖來調整過濾器支持

總結

如果需要更高的準確率,並且可以容忍延遲的話,使用CSRT

如果需要更快的FPS,並且可以容許稍低一點的準確率的話,使用KCF

如果純粹的需要速度的話,用MOSSE。

更多論文源碼關注微信公衆號:“圖像算法”或者微信搜索賬號imalg_cn關注公衆號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章