https://blog.csdn.net/LuohenYJ/article/details/89029816
1 背景
1.1 什么是目標跟蹤
簡而言之,在視頻的連續幀中定位對象稱為跟蹤。該定義聽起來很直接,但在計算機視覺和機器學習中,跟蹤是一個非常廣泛的術語,概念上相似但實現的技術卻大大不同。例如,通常在目標跟蹤有以下方法:
1)密集光流:這些算法有助於估計視頻幀中每個像素的運動情況。
2)稀疏光流:這些算法,如Kanade-Lucas-Tomashi(KLT)特征跟蹤器,跟蹤圖像中幾個特征點的位置。
3)卡爾曼濾波:一種非常流行的信號處理算法,用於根據先前的運動信息預測運動物體的位置。該算法的早期應用之一是導彈制導!還提到這里,阿波羅11號登月艙的降落到月球車載計算機有一個卡爾曼濾波器。https://www.cs.unc.edu/~welch/kalman/siam_cipra.html。
4)均值偏移(Meanshift)和Camshift(Meanshift的改進,連續自適應的MeanShift算法):這些是用於定位密度函數的最大值的算法。它們也用於跟蹤。
5)單目標跟蹤算法:在此類跟蹤器中,第一幀使用矩形表示我們要跟蹤的對象的位置。然后使用跟蹤算法在后續幀中跟蹤對象。在大多數實際應用中,這些跟蹤器與目標檢測算法結合使用。
6)多目標跟蹤算法:在我們有快速對象檢測器的情況下,檢測每個幀中的多個對象然后運行跟蹤查找算法來識別一個幀中的哪個矩形對應於下一幀中的矩形是很有效的。
1.2 跟蹤與檢測
如果你曾經用過OpenCV人臉檢測,你知道它可以實時工作,你可以輕松地在每一幀中檢測到臉部。那么,為什么你需要首先進行跟蹤?讓我們探討一下您可能想要進行視頻跟蹤的原因,而不僅僅是重復檢測。
1)跟蹤比檢測更快:通常跟蹤算法比檢測算法更快。原因很簡單。當您跟蹤在前一幀中檢測到的對象時,您對該對象的外觀了解很多。您還可以知道前一幀中的位置以及其運動的方向和速度。因此,在下一幀中,您可以使用所有這些信息來預測下一幀中對象的位置,並圍繞對象的預期位置進行小搜索,以准確定位對象。一個好的跟蹤算法將使用它對該對象的所有信息,而檢測算法總是從頭開始。因此,在設計高效的系統時,通常在每第n幀上運行物體檢測,而在之間的n-1幀中采用跟蹤算法。
為什么我們不直接檢測第一幀中的對象並隨后跟蹤?確實,跟蹤可以從它擁有的額外信息中獲益,如果它們移動速度太快以至於跟蹤算法無法趕上時,您也可能失去對象的跟蹤。跟蹤算法累積錯誤也很常見,跟蹤對象的邊界框會慢慢偏離其正在跟蹤的對象。為了通過跟蹤算法解決這些問題,每隔一段時間運行一次檢測算法。
2)當檢測失敗時,跟蹤可以提供幫助:如果您在視頻上運行人臉檢測器並且人臉被對象遮擋,則人臉檢測器很可能會失敗。另一方面,良好的跟蹤算法將處理某種程度的遮擋。
3)跟蹤保留標識:對象檢測的輸出是包含對象的矩形數組。但是,該對象沒有附加標識。例如在第一張圖檢測到多個對象會畫檢測框,但是第二張圖又檢測多個對象。但是不知道哪個矩形對應於哪個對象。
2 OpenCV的目標跟蹤函數
2.1 函數調用
OpenCV的擴展庫OpenCV_contrib有一個目標跟蹤API,其中包含許多單個對象跟蹤算法的實現。OpenCV中有8種不同的跟蹤器,分別是BOOSTING,MIL,KCF,TLD,MEDIANFLOW,GOTURN,MOSSE和CSRT。本文只講其中7種,GOTURN涉及到深度學習以后再講。其他算法可以直接用OpenCV contrib庫函數調用。
但是如果使用OpenCV_contrib函數庫需要重新編譯源代碼。
C++需要編譯源代碼,具體見:
windows
https://blog.csdn.net/weixin_42012977/article/details/82992962
linux
https://blog.csdn.net/haoqimao_hard/article/details/82049565
Python稍微很簡單,先卸載安裝的Opencv,然后直接pip/pip3安裝contrib庫:
pip uninstall opencv-python
pip install opencv-contrib-python
在我們提供算法的簡要描述之前,讓我們看一下參數設置和用法。在下面的注釋代碼中,我們首先通過選擇跟蹤器類型來設置跟蹤器,BOOSTING,MIL,KCF,TLD,MEDIANFLOW,GOTURN,MOSSE或CSRT。然后我們打開一個視頻並逐幀讀圖。我們定義一個包含第一幀對象的邊界框,並用第一幀和邊界框初始化跟蹤器。最后,我們從視頻中讀取幀更新跟蹤器以獲得當前幀的新邊界框,並顯示結果。
代碼下載地址:
https://github.com/luohenyueji/OpenCV-Practical-Exercise
// Opencv_Tracker.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
//
#include "pch.h"
#include <opencv2/opencv.hpp>
#include <opencv2/tracking.hpp>
#include <opencv2/core/ocl.hpp>
using namespace cv;
using namespace std;
int main()
{
//跟蹤算法類型
string trackerTypes[7] = { "BOOSTING", "MIL", "KCF", "TLD","MEDIANFLOW", "MOSSE", "CSRT" };
// Create a tracker 創建跟蹤器
string trackerType = trackerTypes[5];
Ptr<Tracker> tracker;
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 == "MOSSE")
tracker = TrackerMOSSE::create();
if (trackerType == "CSRT")
tracker = TrackerCSRT::create();
// Read video 讀視頻
VideoCapture video("video/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 boundibg box 初始檢測框
Rect2d bbox(287, 23, 86, 320);
// Uncomment the line below to select a different bounding box 手動在圖像上畫矩形框
//bbox = selectROI(frame, false);
// Display bounding box 展示畫的2邊緣框
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) 計算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 表示FPS
putText(frame, "FPS : " + to_string(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;
}
}
return 0;
}
Python:
import cv2
import sys
if __name__ == '__main__' :
# Set up tracker.
# Instead of MIL, you can also use
tracker_types = ['BOOSTING', 'MIL','KCF', 'TLD', 'MEDIANFLOW', 'MOSSE', 'CSRT']
tracker_type = tracker_types[4]
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 == "CSRT":
tracker = cv2.TrackerCSRT_create()
if tracker_type == "MOSSE":
tracker = cv2.TrackerMOSSE_create()
# Read video
video = cv2.VideoCapture("video/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
2.2 函數詳解
在本節中,我們將深入研究不同的跟蹤算法。目標不是要對每個跟蹤器有深入的理論理解,而是從實際的角度理解它們。
在跟蹤中,我們的目標是在當前幀中找到前一幀已經識別或者跟蹤到的一個對象。由於我們已經跟蹤了當前幀的對象,因此我們知道它是如何移動的。換句話說,我們知道運動模型的參數。運動模型只是一種有效的方式,表示你知道前一幀中該物體的位置和速度。如果您對該對象一無所知,則可以根據當前運動模型預測新位置,並且獲得的結果將非常接近對象的新位置。
當我們有更多物體的信息,我們可以構建一個外觀模型來表示對象的外觀。該外觀模型可用於在由運動模型預測的位置的小鄰域中搜索,以更准確地預測對象的位置。運動模型預測對象的大致位置。外觀模型可以精確調整此估計值,以便根據外觀提供更准確的估計。如果目標對象非常簡單並且外觀變化不大,我們可以使用一個簡單的模板作為外觀模型並查找該模板。然而,現實生活並非那么簡單。對象的外觀可能會發生巨大變化。為了解決這個問題,在許多現代目標跟蹤器中,外觀模型是以在線方式訓練的分類器。
分類器的工作是將圖像的矩形區域分類為對象或背景。分類器將圖像塊作為輸入,並返回0到1之間的分數,以表示圖像塊包含對象的概率。當絕對確定圖像塊是背景時得分為0,當絕對確定圖像塊是前景對象時得分為1。在機器學習中,我們使用“在線”一詞來指代在運行時即時訓練的算法。一個離線分類可能需要成千上萬的例子訓練分類,而是一個在線分類器使用很少的例子在運行時進行訓練。通過將其分為正(對象)和負(背景)示例來訓練分類器。如果你想建立一個用於檢測貓的分類器,你可以使用包含貓的數千張圖像和數千張不含貓的圖像來訓練它。通過這種方式,分類器學會區分什么是貓而不是什么。但在構建在線分類器的同時,我們沒有數千個正面和負面類的例子。所以我們需要通過在線訓練跟蹤器,來實現目標跟蹤。
1) BOOSTING Tracker/集成學習跟蹤器
該跟蹤器基於AdaBoost的在線版本,即基於HAAR級聯的檢測器法。需要在運行時使用對象的正負示例訓練此分類器。由用戶(或另一個對象檢測算法)提供的初始邊界框被視為對象的正例,並且邊界框外的許多圖像塊被視為背景。給定新幀,分類器在先前位置的鄰域中的每個像素上運行,並且記錄分類器的分數。對象的新位置是得分最大的位置。所以現在我們又有了一個分類器的正面例子。隨着更多幀進入,分類器將使用此數據進行更新。
優點:沒有。這個算法已有十年之久,並且運行正常,但我找不到使用它的充分理由,特別是當基於類似原理的其他高級跟蹤器(MIL,KCF)可用時。
缺點:跟蹤效果很普通,而且無法確定是否跟蹤失敗。
2) MIL Tracker/多實例學習跟蹤器
該跟蹤器在概念上類似於上述的BOOSTING跟蹤器。最大的區別在於,不僅考慮對象的當前位置作為正例,而是在當前位置周圍的小鄰域產生若干潛在的正例。您可能認為這是一個壞主意,因為在大多數這些正樣本的例子中,對象不是居中的。
這是多實例學習跟蹤器(MIL)要解決的問題。在MIL中,您沒有指定正負樣本的圖像塊,而是正負例的圖像集合。正圖像集合中的圖像塊並非都是正例。意思就是正圖像集中有若干圖像塊,但只需要其中一個圖像塊是正例。在我們的示例中,正圖像集以對象當前位置為中心的圖像塊,以及在其周圍的小鄰域中的圖像塊。即使被跟蹤對象的當前位置不准確,當來自當前位置真正的樣本圖像塊被放入正圖像集中,該正圖像集能夠至少包含一個正確的圖像塊。具體算法見:http://vision.ucsd.edu/~bbabenko/new/project_miltrack.shtml
優點:表現非常好。它不會像BOOSTING跟蹤器那樣結果漂移,並且在部分遮擋下可以完成合理的工作。但是相對來多KCF更好,不過MIL低版本的OpenCV也能使用,KCF版本要求更高。
缺點:無法檢測是否跟蹤失敗,速度慢,且無法處理遮擋物體。
3) KCF Tracker/核相關濾波跟蹤器
KCF跟蹤器基於前兩個跟蹤器的思想,該跟蹤器利用MIL跟蹤器中使用的多個正樣本具有大的重疊區域的特性。這種重疊的數據導致了一些很好的數學屬性,這個屬性被跟蹤器利用,實現更快,更准確地跟蹤。
優點:准確性和速度都優於MIL,同時會報告跟蹤失敗。
缺點:完全遮擋下效果不好。
4) TLD Tracker/單目標長時間跟蹤器
顧名思義TLD表示跟蹤,學習和檢測,該跟蹤器將長期跟蹤任務分解為三個部分跟蹤,學習和檢測。跟蹤器在幀與幀之間跟蹤對象。並獲取所有物體的外觀並在必要時糾正跟蹤器。學習估計跟蹤器的錯誤並更新它以避免將來出現這些錯誤。這個跟蹤器的輸出往往會跳躍一下。例如,如果您正在跟蹤行人並且場景中還有其他行人,則此跟蹤器有時可以臨時跟蹤與您要跟蹤的行人不同的行人。從積極的方面來說,這種追蹤器算法可以在更大運動和遮擋范圍跟蹤物體。如果您有一個視頻序列,其中對象隱藏在另一個對象后面,則此跟蹤器可能是一個不錯的選擇。
優點:在多幀的遮擋下工作效果最佳,對於縮放的圖像效果也不錯。
缺點:很多誤報使它幾乎無法使用。
5) MEDIANFLOW Tracker
在內部,該跟蹤器在時間上向前和向后方向上跟蹤對象,並且測量這兩個軌跡之間的差異。 最小化該ForwardBackward錯誤使它們能夠可靠地檢測跟蹤失敗並在視頻序列中選擇可靠的軌跡。
優點:跟蹤失敗會進行報告,當運動是可預測的並且沒有遮擋時效果很好。
缺點:在大規模運動下失敗。
6) MOSSE tracker/相關濾波跟蹤
MOSSE使用自適應相關性進行目標跟蹤,當使用單個幀初始化時產生穩定的相關濾波器。並最小化實際輸出的卷積和期望輸出卷積之間的方差來更新獲得合適的濾波器。
優點:速度很快,容易實現
缺點:性能不那么好。速度不快。
7) CSRT tracker/判別相關濾波器跟蹤
基於判別相關濾波器(DCF-CSR)中,是現在應用最廣的跟蹤算法。
優點:精度很高,比KCF快一點
缺點:速度很慢
2.3 綜合評價
追蹤器選擇方法:
如果追求高准確度,又能忍受慢一些的速度,那么就用CSRT;
如果對准確度的要求不苛刻,想追求速度,那么就選KCF;
純粹想節省時間就用MOSSE。
下表總結了不同版本的OpenCV中可使用的追蹤器和具體速度情況。FPS在CPU(I5)下對640X360的視頻進行跟蹤所獲得的平均結果。

3 參考
https://www.learnopencv.com/object-tracking-using-opencv-cpp-python/
https://www.jqr.com/article/000383
————————————————
