利用OpenCV實現(xiàn)質心跟蹤算法
目標跟蹤的過程:
1、獲取對象檢測的初始集
2、為每個初始檢測創(chuàng)建唯一的ID
3、然后在視頻幀中跟蹤每個對象的移動,保持唯一ID的分配
本文使用OpenCV實現(xiàn)質心跟蹤,這是一種易于理解但高效的跟蹤算法。
質心跟蹤算法步驟
步驟1:接受邊界框坐標并計算質心
質心跟蹤算法假設我們?yōu)槊恳粠械拿總€檢測到的對象傳入一組邊界框 (x, y) 坐標。
這些邊界框可以由任何類型的對象檢測器(顏色閾值 + 輪廓提取、Haar 級聯(lián)、HOG + 線性 SVM、SSD、Faster R-CNN 等)生成,前提是它們是針對 該視頻。
通過邊界框坐標,就可以計算“質心”(邊界框的中心 (x, y) 坐標)。 如上圖演示了通過一組邊界框坐標計算出質心。
給初始的邊界框分配ID。
步驟2:計算新邊界框和現(xiàn)有對象之間的歐幾里得距離
對于視頻流中的每個后續(xù)幀,首先執(zhí)行步驟1; 然后,我們首先需要確定是否可以將新的對象質心(黃色)與舊的對象質心(紫色)相關聯(lián),而不是為每個檢測到的對象分配一個新的唯一 ID(這會破壞對象跟蹤的目的)。 為了完成這個過程,我們計算每對現(xiàn)有對象質心和輸入對象質心之間的歐幾里得距離(用綠色箭頭突出顯示)。
上圖可以看出,我們這次在圖像中檢測到了三個對象。 靠近的兩對是兩個現(xiàn)有對象。
然后我們計算每對原始質心(黃色)和新質心(紫色)之間的歐幾里得距離。 但是我們如何使用這些點之間的歐幾里得距離來實際匹配它們并關聯(lián)它們呢?
答案在步驟3。
步驟 3:更新現(xiàn)有對象的 (x, y) 坐標
質心跟蹤算法的主要假設是給定對象可能會在后續(xù)幀之間移動,但幀 F_t 和 F_{t + 1} 的質心之間的距離將小于對象之間的所有其他距離。
因此,如果我們選擇將質心與后續(xù)幀之間的最小距離相關聯(lián),我們可以構建我們的對象跟蹤器。
在上圖中,可以看到質心跟蹤器算法如何選擇關聯(lián)質心以最小化它們各自的歐幾里得距離。
但是左下角的孤獨點呢?
它沒有與任何東西相關聯(lián)——我們如何處理它?
步驟4:注冊新對象
如果輸入檢測比跟蹤的現(xiàn)有對象多,我們需要注冊新對象。 “注冊”僅僅意味著我們通過以下方式將新對象添加到我們的跟蹤對象列表中:
為其分配一個新的對象 ID
存儲該對象的邊界框坐標的質心
然后我們可以返回到步驟2,重復執(zhí)行。
上圖演示了使用最小歐幾里得距離關聯(lián)現(xiàn)有對象 ID,然后注冊新對象的過程。
步驟5:注銷舊對象
當舊的對象超出范圍時,注銷舊對象、
項目結構
使用 OpenCV 實現(xiàn)質心跟蹤
新建 centroidtracker.py,寫入代碼:
# import the necessary packages from scipy.spatial import distance as dist from collections import OrderedDict import numpy as np class CentroidTracker(): ? ? def __init__(self, maxDisappeared=50): ? ? ? ? self.nextObjectID = 0 ? ? ? ? self.objects = OrderedDict() ? ? ? ? self.disappeared = OrderedDict() ? ? ? ? # 存儲給定對象被允許標記為“消失”的最大連續(xù)幀數(shù),直到我們需要從跟蹤中注銷該對象 ? ? ? ? self.maxDisappeared = maxDisappeared
導入了所需的包和模塊:distance 、 OrderedDict 和 numpy 。
定義類 CentroidTracker 。構造函數(shù)接受一個參數(shù)maxDisappeared,即給定對象必須丟失/消失的最大連續(xù)幀數(shù),如果超過這個數(shù)值就將這個對象刪除。
四個類變量:
nextObjectID :用于為每個對象分配唯一 ID 的計數(shù)器。如果一個對象離開幀并且沒有返回 maxDisappeared 幀,則將分配一個新的(下一個)對象 ID。
objects :使用對象 ID 作為鍵和質心 (x, y) 坐標作為值的字典。
disappeared:保持特定對象 ID(鍵)已被標記為“丟失”的連續(xù)幀數(shù)(值)。
maxDisappeared :在我們取消注冊對象之前,允許將對象標記為“丟失/消失”的連續(xù)幀數(shù)。
讓我們定義負責向我們的跟蹤器添加新對象的 register 方法:
def register(self, centroid): ? ? ? ? # 注冊對象時,我們使用下一個可用的對象ID來存儲質心 ? ? ? ? self.objects[self.nextObjectID] = centroid ? ? ? ? self.disappeared[self.nextObjectID] = 0 ? ? ? ? self.nextObjectID += 1 ? ? def deregister(self, objectID): ? ? ? ? # 要注銷注冊對象ID,我們從兩個字典中都刪除了該對象ID ? ? ? ? del self.objects[objectID] ? ? ? ? del self.disappeared[objectID]
register 方法接受一個質心,然后使用下一個可用的對象 ID 將其添加到對象字典中。
對象消失的次數(shù)在消失字典中初始化為 0。
最后,我們增加 nextObjectID,這樣如果一個新對象進入視野,它將與一個唯一的 ID 相關聯(lián)。
與我們的注冊方法類似,我們也需要一個注銷方法。
deregister 方法分別刪除對象和消失字典中的 objectID。
質心跟蹤器實現(xiàn)的核心位于update方法中
?def update(self, rects): ? ? ? ? # 檢查輸入邊界框矩形的列表是否為空 ? ? ? ? if len(rects) == 0: ? ? ? ? ? ? # 遍歷任何現(xiàn)有的跟蹤對象并將其標記為消失 ? ? ? ? ? ? for objectID in list(self.disappeared.keys()): ? ? ? ? ? ? ? ? self.disappeared[objectID] += 1 ? ? ? ? ? ? ? ? # 如果達到給定對象被標記為丟失的最大連續(xù)幀數(shù),請取消注冊 ? ? ? ? ? ? ? ? if self.disappeared[objectID] > self.maxDisappeared: ? ? ? ? ? ? ? ? ? ? self.deregister(objectID) ? ? ? ? ? ? # 由于沒有質心或跟蹤信息要更新,請盡早返回 ? ? ? ? ? ? return self.objects ? ? ? ? # 初始化當前幀的輸入質心數(shù)組 ? ? ? ? inputCentroids = np.zeros((len(rects), 2), dtype="int") ? ? ? ? # 在邊界框矩形上循環(huán) ? ? ? ? for (i, (startX, startY, endX, endY)) in enumerate(rects): ? ? ? ? ? ? # use the bounding box coordinates to derive the centroid ? ? ? ? ? ? cX = int((startX + endX) / 2.0) ? ? ? ? ? ? cY = int((startY + endY) / 2.0) ? ? ? ? ? ? inputCentroids[i] = (cX, cY) ? ? ? ? # 如果我們當前未跟蹤任何對象,請輸入輸入質心并注冊每個質心 ? ? ? ? if len(self.objects) == 0: ? ? ? ? ? ? for i in range(0, len(inputCentroids)): ? ? ? ? ? ? ? ? self.register(inputCentroids[i]) ? ? ? ? # 否則,當前正在跟蹤對象,因此我們需要嘗試將輸入質心與現(xiàn)有對象質心進行匹配 ? ? ? ? else: ? ? ? ? ? ? # 抓取一組對象ID和相應的質心 ? ? ? ? ? ? objectIDs = list(self.objects.keys()) ? ? ? ? ? ? objectCentroids = list(self.objects.values()) ? ? ? ? ? ? # 分別計算每對對象質心和輸入質心之間的距離-我們的目標是將輸入質心與現(xiàn)有對象質心匹配 ? ? ? ? ? ? D = dist.cdist(np.array(objectCentroids), inputCentroids) ? ? ? ? ? ? # 為了執(zhí)行此匹配,我們必須(1)在每行中找到最小值,然后(2)根據(jù)行索引的最小值對行索引進行排序,以使具有最小值的行位于索引列表的* front *處 ? ? ? ? ? ? rows = D.min(axis=1).argsort() ? ? ? ? ? ? # 接下來,我們在列上執(zhí)行類似的過程,方法是在每一列中找到最小值,然后使用先前計算的行索引列表進行排序 ? ? ? ? ? ? cols = D.argmin(axis=1)[rows] ? ? ? ? ? ? # 為了確定是否需要更新,注冊或注銷對象,我們需要跟蹤已經(jīng)檢查過的行索引和列索引 ? ? ? ? ? ? usedRows = set() ? ? ? ? ? ? usedCols = set() ? ? ? ? ? ? # 循環(huán)遍歷(行,列)索引元組的組合 ? ? ? ? ? ? for (row, col) in zip(rows, cols): ? ? ? ? ? ? ? ? # 如果我們之前已經(jīng)檢查過行或列的值,請忽略它 ? ? ? ? ? ? ? ? if row in usedRows or col in usedCols: ? ? ? ? ? ? ? ? ? ? continue ? ? ? ? ? ? ? ? # 否則,獲取當前行的對象ID,設置其新的質心,然后重置消失的計數(shù)器 ? ? ? ? ? ? ? ? objectID = objectIDs[row] ? ? ? ? ? ? ? ? self.objects[objectID] = inputCentroids[col] ? ? ? ? ? ? ? ? self.disappeared[objectID] = 0 ? ? ? ? ? ? ? ? # 表示我們已經(jīng)分別檢查了行索引和列索引 ? ? ? ? ? ? ? ? usedRows.add(row) ? ? ? ? ? ? ? ? usedCols.add(col) ? ? ? ? ? ? # 計算我們尚未檢查的行和列索引 ? ? ? ? ? ? unusedRows = set(range(0, D.shape[0])).difference(usedRows) ? ? ? ? ? ? unusedCols = set(range(0, D.shape[1])).difference(usedCols) ? ? ? ? ? ? # 如果對象質心的數(shù)量等于或大于輸入質心的數(shù)量 ? ? ? ? ? ? # 我們需要檢查一下其中的某些對象是否已潛在消失 ? ? ? ? ? ? if D.shape[0] >= D.shape[1]: ? ? ? ? ? ? ? ? # loop over the unused row indexes ? ? ? ? ? ? ? ? for row in unusedRows: ? ? ? ? ? ? ? ? ? ? # 抓取相應行索引的對象ID并增加消失的計數(shù)器 ? ? ? ? ? ? ? ? ? ? objectID = objectIDs[row] ? ? ? ? ? ? ? ? ? ? self.disappeared[objectID] += 1 ? ? ? ? ? ? ? ? ? ? # 檢查是否已將該對象標記為“消失”的連續(xù)幀數(shù)以用于注銷該對象的手令 ? ? ? ? ? ? ? ? ? ? if self.disappeared[objectID] > self.maxDisappeared: ? ? ? ? ? ? ? ? ? ? ? ? self.deregister(objectID) ? ? ? ? ? ? # 否則,如果輸入質心的數(shù)量大于現(xiàn)有對象質心的數(shù)量,我們需要將每個新的輸入質心注冊為可跟蹤對象 ? ? ? ? ? ? else: ? ? ? ? ? ? ? ? for col in unusedCols: ? ? ? ? ? ? ? ? ? ? self.register(inputCentroids[col]) ? ? ? ? # return the set of trackable objects ? ? ? ? return self.objects
update方法接受邊界框矩形列表。 rects 參數(shù)的格式假定為具有以下結構的元組: (startX, startY, endX, endY) 。
如果沒有檢測到,我們將遍歷所有對象 ID 并增加它們的消失計數(shù)。 我們還將檢查是否已達到給定對象被標記為丟失的最大連續(xù)幀數(shù)。 如果是,我們需要將其從我們的跟蹤系統(tǒng)中刪除。 由于沒有要更新的跟蹤信息,我們將提前返回。
否則,我們將初始化一個 NumPy 數(shù)組來存儲每個 rect 的質心。 然后,我們遍歷邊界框矩形并計算質心并將其存儲在 inputCentroids 列表中。 如果沒有我們正在跟蹤的對象,我們將注冊每個新對象。
否則,我們需要根據(jù)最小化它們之間的歐幾里得距離的質心位置更新任何現(xiàn)有對象 (x, y) 坐標。
接下來我們在else中計算所有 objectCentroids 和 inputCentroids 對之間的歐幾里德距離:
獲取 objectID 和 objectCentroid 值。
計算每對現(xiàn)有對象質心和新輸入質心之間的距離。距離圖 D 的輸出 NumPy 數(shù)組形狀將是 (# of object centroids, # of input centroids) 。要執(zhí)行匹配,我們必須 (1) 找到每行中的最小值,以及 (2) 根據(jù)最小值對行索引進行排序。我們對列執(zhí)行非常相似的過程,在每列中找到最小值,然后根據(jù)已排序的行對它們進行排序。我們的目標是在列表的前面具有最小對應距離的索引值。
下一步是使用距離來查看是否可以關聯(lián)對象 ID:
初始化兩個集合以確定我們已經(jīng)使用了哪些行和列索引。
然后遍歷 (row, col) 索引元組的組合以更新我們的對象質心:
如果我們已經(jīng)使用了此行或列索引,請忽略它并繼續(xù)循環(huán)。
否則,我們找到了一個輸入質心:
- 到現(xiàn)有質心的歐幾里得距離最小
- 并且沒有與任何其他對象匹配
- 在這種情況下,我們更新對象質心(第 113-115 行)并確保將 row 和 col 添加到它們各自的 usedRows 和 usedCols 集合中。
在我們的 usedRows + usedCols 集合中可能有我們尚未檢查的索引:
所以我們必須確定哪些質心索引我們還沒有檢查過,并將它們存儲在兩個新的方便集合(unusedRows 和 usedCols)中。
最終檢查會處理任何丟失或可能消失的對象:
如果對象質心的數(shù)量大于或等于輸入質心的數(shù)量:
我們需要循環(huán)遍歷未使用的行索引來驗證這些對象是否丟失或消失。
在循環(huán)中,我們將:
1. 增加他們在字典中消失的次數(shù)。
2. 檢查消失計數(shù)是否超過 maxDisappeared 閾值,如果是,我們將注銷該對象。
否則,輸入質心的數(shù)量大于現(xiàn)有對象質心的數(shù)量,我們有新的對象要注冊和跟蹤.
循環(huán)遍歷未使用的Cols 索引并注冊每個新質心。 最后,我們將可跟蹤對象集返回給調用方法。
實現(xiàn)對象跟蹤驅動程序腳本
已經(jīng)實現(xiàn)了 CentroidTracker 類,讓我們將其與對象跟蹤驅動程序腳本一起使用。
驅動程序腳本是您可以使用自己喜歡的對象檢測器的地方,前提是它會生成一組邊界框。 這可能是 Haar Cascade、HOG + 線性 SVM、YOLO、SSD、Faster R-CNN 等。
在這個腳本中,需要實現(xiàn)的功能:
1、使用實時 VideoStream 對象從網(wǎng)絡攝像頭中抓取幀
2、加載并使用 OpenCV 的深度學習人臉檢測器
3、實例化 CentroidTracker 并使用它來跟蹤視頻流中的人臉對象并顯示結果。
新建 object_tracker.py 插入代碼:
from Model.centroidtracker import CentroidTracker import numpy as np import imutils import time import cv2 # 定義最低置信度 confidence_t=0.5 # 初始化質心跟蹤器和框架尺寸 ct = CentroidTracker() (H, W) = (None, None) # 加載檢測人臉的模型 print("[INFO] loading model...") net = cv2.dnn.readNetFromCaffe("deploy.prototxt", "res10_300x300_ssd_iter_140000_fp16.caffemodel") # 初始化視頻流并允許相機傳感器預熱 print("[INFO] starting video stream...") vs = cv2.VideoCapture('11.mp4') time.sleep(2.0) fps = 30 ? ?#保存視頻的FPS,可以適當調整 size=(600,1066)#寬高,根據(jù)frame的寬和高確定。 fourcc = cv2.VideoWriter_fourcc(*"mp4v") videoWriter = cv2.VideoWriter('3.mp4',fourcc,fps,size)#最后一個是保存圖片的尺寸
導入需要的包。
定義最低置信度。
加載人臉檢測模型
初始化視頻流或者相機(設置成相機對應的ID就會啟動相機)
接下來定義cv2.VideoWriter的參數(shù)。
# 循環(huán)播放圖像流中的幀 while True: ? ? # 從視頻流中讀取下一幀并調整其大小 ? ? (grabbed, frame) ?= vs.read() ? ? if not grabbed: ? ? ? ? break ? ? frame = imutils.resize(frame, width=600) ? ? print(frame.shape) ? ? # 如果幀中尺寸為“無”,則抓住它們 ? ? if W is None or H is None: ? ? ? ? (H, W) = frame.shape[:2] ? ? # 從幀中構造一個Blob,將其通過網(wǎng)絡, ? ? # 獲取輸出預測,并初始化邊界框矩形的列表 ? ? blob = cv2.dnn.blobFromImage(frame, 1.0, (W, H), ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(104.0, 177.0, 123.0)) ? ? net.setInput(blob) ? ? detections = net.forward() ? ? rects = [] ? ? # 循環(huán)檢測 ? ? for i in range(0, detections.shape[2]): ? ? ? ? # 通過確保預測的概率大于最小閾值來過濾掉弱檢測 ? ? ? ? if detections[0, 0, i, 2] >0.5: ? ? ? ? ? ? # 計算對象邊界框的(x,y)坐標,然后更新邊界框矩形列表 ? ? ? ? ? ? box = detections[0, 0, i, 3:7] * np.array([W, H, W, H]) ? ? ? ? ? ? rects.append(box.astype("int")) ? ? ? ? ? ? # 在對象周圍畫一個邊界框,以便我們可視化它 ? ? ? ? ? ? (startX, startY, endX, endY) = box.astype("int") ? ? ? ? ? ? cv2.rectangle(frame, (startX, startY), (endX, endY), ? ? ? ? ? ? ? ? ? ? ? ? ? (0, 255, 0), 2) ? ? # 使用邊界框矩形的計算集更新質心跟蹤器 ? ? objects = ct.update(rects) ? ? # 循環(huán)跟蹤對象 ? ? for (objectID, centroid) in objects.items(): ? ? ? ? # 在輸出幀上繪制對象的ID和對象的質心 ? ? ? ? text = "ID {}".format(objectID) ? ? ? ? cv2.putText(frame, text, (centroid[0] - 10, centroid[1] - 10), ? ? ? ? ? ? ? ? ? ? cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) ? ? ? ? cv2.circle(frame, (centroid[0], centroid[1]), 4, (0, 255, 0), -1) ? ? # 顯示輸出畫面 ? ? cv2.imshow("Frame", frame) ? ? videoWriter.write(frame) ? ? key = cv2.waitKey(1) & 0xFF ? ? # 如果按下“ q”鍵,則退出循環(huán) ? ? if key == ord("q"): ? ? ? ? break videoWriter.release() cv2.destroyAllWindows() vs.release()
遍歷幀并將它們調整為固定寬度(同時保持縱橫比)。
然后將幀通過 CNN 對象檢測器以獲得預測和對象位置(。
初始化一個 rects 列表,即邊界框矩形。
循環(huán)檢測detections,如果檢測超過我們的置信度閾值,表明檢測有效,然后計算邊框。
在質心跟蹤器對象 ct 上調用 update 方法。
接下來在輸出幀上繪制對象的ID和質心,將質心顯示為一個實心圓圈和唯一的對象 ID 號文本。 現(xiàn)在我們將能夠可視化結果并通過將正確的 ID 與視頻流中的對象相關聯(lián)來檢查 CentroidTracker 是否正確地跟蹤對象。
限制和缺點
雖然質心跟蹤器在這個例子中工作得很好,但這種對象跟蹤算法有兩個主要缺點。
1、它要求在輸入視頻的每一幀上運行對象檢測步驟。對于非??焖俚膶ο髾z測器(即顏色閾值和 Haar 級聯(lián)),必須在每個輸入幀上運行檢測器可能不是問題。但是,如果在 資源受限的設備上使用計算量大得多的對象檢測器,例如 HOG + 線性 SVM 或基于深度學習的檢測器,那么幀處理將大大減慢。
2、與質心跟蹤算法本身的基本假設有關——質心必須在后續(xù)幀之間靠得很近。
這個假設通常成立,但請記住,我們用 2D 幀來表示我們的 3D 世界——當一個對象與另一個對象重疊時會發(fā)生什么?
答案是可能發(fā)生對象 ID 切換。如果兩個或多個對象相互重疊到它們的質心相交的點,而是與另一個相應對象具有最小距離,則算法可能(在不知不覺中)交換對象 ID。重要的是要了解重疊/遮擋對象問題并非特定于質心跟蹤 - 它也發(fā)生在許多其他對象跟蹤器中,包括高級對象跟蹤器。然而,質心跟蹤的問題更為明顯,因為我們嚴格依賴質心之間的歐幾里得距離,并且沒有額外的度量、啟發(fā)式或學習模式。
以上就是利用OpenCV實現(xiàn)質心跟蹤算法的詳細內容,更多關于OpenCV質心跟蹤算法的資料請關注腳本之家其它相關文章!
相關文章
Python使用PyQt5/PySide2編寫一個極簡的音樂播放器功能
這篇文章主要介紹了Python中使用PyQt5/PySide2編寫一個極簡的音樂播放器功能,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2020-02-02python通過nmap掃描在線設備并嘗試AAA登錄(實例代碼)
這篇文章主要介紹了python通過nmap掃描在線設備并嘗試AAA登錄,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-12-12用Python實現(xiàn)二叉樹、二叉樹非遞歸遍歷及繪制的例子
今天小編就為大家分享一篇用Python實現(xiàn)二叉樹、二叉樹非遞歸遍歷及繪制的例子,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08