智慧交通day02-车流量检测实现10:多目标追踪实现
生活随笔
收集整理的這篇文章主要介紹了
智慧交通day02-车流量检测实现10:多目标追踪实现
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
在這里我們主要實現(xiàn)了一個多目標(biāo)跟蹤器,管理多個卡爾曼濾波器對象,主要包括以下內(nèi)容:
- 初始化:最大檢測數(shù),目標(biāo)未被檢測的最大幀數(shù)
-
目標(biāo)跟蹤結(jié)果的更新,即跟蹤成功和失敗的目標(biāo)的更新
-
初始化
def __init__(self, max_age=1, min_hits=3):"""初始化:設(shè)置SORT算法的關(guān)鍵參數(shù) """# 最大檢測數(shù):目標(biāo)未被檢測到的幀數(shù),超過之后會被刪 self.max_age = max_age# 目標(biāo)命中的最小次數(shù),小于該次數(shù)不返回self.min_hits = min_hits # 卡爾曼跟蹤器self.trackers = [] # 幀計數(shù)self.frame_count = 0
1、目標(biāo)跟蹤結(jié)果的更新:
該方法實現(xiàn)了SORT算法,輸入是當(dāng)前幀中所有物體的檢測框的集合,包括目標(biāo)的score,輸出是當(dāng)前幀標(biāo)的跟蹤框集合,包括目標(biāo)的跟蹤的id要求是即使檢測框為空,也必須對每一幀調(diào)用此方法,返回一個類似的輸出數(shù)組,最后一列是目標(biāo)對像的id。需要注意的是,返回的目標(biāo)對象數(shù)量可能與檢測框的數(shù)量不同.
def update(self, dets):self.frame_count += 1# 在當(dāng)前幀逐個預(yù)測軌跡位置,記錄狀態(tài)異常的跟蹤器索引# 根據(jù)當(dāng)前所有的卡爾曼跟蹤器個數(shù)(即上一幀中跟蹤的目標(biāo)個數(shù))創(chuàng)建二維數(shù)組:行號為卡爾曼濾波器的標(biāo)識索引,列向量為跟蹤框的位置和IDtrks = np.zeros((len(self.trackers), 5)) # 存儲跟蹤器的預(yù)測to_del = [] # 存儲要刪除的目標(biāo)框ret = [] # 存儲要返回的追蹤目標(biāo)框# 循環(huán)遍歷卡爾曼跟蹤器列表for t, trk in enumerate(trks):# 使用卡爾曼跟蹤器t產(chǎn)生對應(yīng)目標(biāo)的跟蹤框pos = self.trackers[t].predict()[0]# 遍歷完成后,trk中存儲了上一幀中跟蹤的目標(biāo)的預(yù)測跟蹤框trk[:] = [pos[0], pos[1], pos[2], pos[3], 0]# 如果跟蹤框中包含空值則將該跟蹤框添加到要刪除的列表中if np.any(np.isnan(pos)):to_del.append(t)# numpy.ma.masked_invalid 屏蔽出現(xiàn)無效值的數(shù)組(NaN 或 inf)# numpy.ma.compress_rows 壓縮包含掩碼值的2-D 數(shù)組的整行,將包含掩碼值的整行去除# trks中存儲了上一幀中跟蹤的目標(biāo)并且在當(dāng)前幀中的預(yù)測跟蹤框trks = np.ma.compress_rows(np.ma.masked_invalid(trks))# 逆向刪除異常的跟蹤器,防止破壞索引for t in reversed(to_del):self.trackers.pop(t)# 將目標(biāo)檢測框與卡爾曼濾波器預(yù)測的跟蹤框關(guān)聯(lián)獲取跟蹤成功的目標(biāo),新增的目標(biāo),離開畫面的目標(biāo)matched, unmatched_dets, unmatched_trks = associate_detections_to_trackers(dets, trks)# 將跟蹤成功的目標(biāo)框更新到對應(yīng)的卡爾曼濾波器for t, trk in enumerate(self.trackers):if t not in unmatched_trks:d = matched[np.where(matched[:, 1] == t)[0], 0]# 使用觀測的邊界框更新狀態(tài)向量trk.update(dets[d, :][0])# 為新增的目標(biāo)創(chuàng)建新的卡爾曼濾波器對象進(jìn)行跟蹤for i in unmatched_dets:trk = KalmanBoxTracker(dets[i, :])self.trackers.append(trk)# 自后向前遍歷,僅返回在當(dāng)前幀出現(xiàn)且命中周期大于self.min_hits(除非跟蹤剛開始)的跟蹤結(jié)果;如果未命中時間大于self.max_age則刪除跟蹤器。# hit_streak忽略目標(biāo)初始的若干幀i = len(self.trackers)for trk in reversed(self.trackers):# 返回當(dāng)前邊界框的估計值d = trk.get_state()[0]# 跟蹤成功目標(biāo)的box與id放入ret列表中if (trk.time_since_update < 1) and (trk.hit_streak >= self.min_hits or self.frame_count <= self.min_hits):ret.append(np.concatenate((d, [trk.id + 1])).reshape(1, -1)) # +1 as MOT benchmark requires positivei -= 1# 跟蹤失敗或離開畫面的目標(biāo)從卡爾曼跟蹤器中刪除if trk.time_since_update > self.max_age:self.trackers.pop(i)# 返回當(dāng)前畫面中所有目標(biāo)的box與id,以二維矩陣形式返回if len(ret) > 0:return np.concatenate(ret)return np.empty((0, 5))我們將上述兩個方法封裝在一個類中。
總結(jié)
了解sort進(jìn)行多目標(biāo)跟蹤的實現(xiàn)
功能實現(xiàn)
# 1.SORT目標(biāo)跟蹤: # 1.第一幀剛開始時:對第一幀所有的檢測框生成對應(yīng)的新跟蹤框。 # 2.第二幀開始到以后所有幀: # 上一幀成功跟蹤并且保留下來的的跟蹤框 在當(dāng)前幀中 進(jìn)行新一輪的預(yù)測新的跟蹤框, # 并且針對所預(yù)測的新跟蹤框和當(dāng)前幀中的檢測框進(jìn)行iou計算和使用匈牙利算法對該兩者進(jìn)行關(guān)聯(lián)匹配, # 通過上述操作后成功返回跟蹤目標(biāo)成功的跟蹤框(即和當(dāng)前幀中的目標(biāo)檢測框相匹配的跟蹤框), # 并且另外發(fā)現(xiàn)了新出現(xiàn)目標(biāo)的檢測框、跟蹤目標(biāo)失敗的跟蹤框(即目標(biāo)離開了畫面/兩者匹配度IOU值小于iou閾值), # 那么首先使用當(dāng)前幀中的檢測框?qū)Α俺晒﹃P(guān)聯(lián)匹配的跟蹤框中的”狀態(tài)向量進(jìn)行更新, # 然后對新增目標(biāo)的檢測框生成對應(yīng)新的跟蹤框,最后把跟蹤目標(biāo)失敗的跟蹤框從跟蹤器鏈列表中移除出去。 # 2.傳入的檢測框dets:[檢測框的左上角的x/y坐標(biāo), 檢測框的右下角的x/y坐標(biāo), 檢測框的預(yù)測類別的概率值] # 3.返回值tracks: # 當(dāng)前幀中跟蹤目標(biāo)成功的跟蹤框/預(yù)測框的集合,包含目標(biāo)的跟蹤的id(也即該跟蹤框(卡爾曼濾波實例對象)是創(chuàng)建出來的第幾個) # 第一種返回值方案:[[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo), yolo識別目標(biāo)是某種物體的可信度, trk.id] ...] # 第二種返回值方案(當(dāng)前使用的為該種):[[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo), trk.id] ...] # d:[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo)] # trk.id:卡爾曼濾波器的個數(shù)/目標(biāo)框的個數(shù),也即該跟蹤框(卡爾曼濾波實例對象)是創(chuàng)建出來的第幾個。 #Sort多目標(biāo)跟蹤 管理多個卡爾曼濾波器 class Sort(object):"""Sort 是一個多目標(biāo)跟蹤器的管理類,管理多個 跟蹤器鏈中的多個 KalmanBoxTracker 卡爾曼濾波對象"""#設(shè)置Sort算法的參數(shù)def __init__(self,max_age = 1,min_hits = 3):"""初始化:設(shè)置SORT算法的關(guān)鍵參數(shù):param max_age: 最大檢測數(shù):目標(biāo)未被檢測到的幀數(shù),超過之后會被刪除:param min_hits: 目標(biāo)命中的最小次數(shù),小于該次數(shù)update函數(shù)不返回該目標(biāo)的KalmanBoxTracker卡爾曼濾波對象""""""max_age:跟蹤框的最大連續(xù)跟丟幀數(shù)。如果當(dāng)前跟蹤框連續(xù)N幀大于最大連續(xù)跟丟幀數(shù)的話,則從跟蹤器鏈中刪除該卡爾曼濾波對象的預(yù)測框(跟蹤框)。min_hits:跟蹤框連續(xù)成功跟蹤到目標(biāo)的最小次數(shù)(目標(biāo)連續(xù)命中的最小次數(shù)),也即跟蹤框至少需要連續(xù)min_hits次成功跟蹤到目標(biāo)。trackers:卡爾曼濾波跟蹤器鏈,存儲多個 KalmanBoxTracker 卡爾曼濾波對象frame_count:當(dāng)前視頻經(jīng)過了多少幀的計數(shù)"""# 最大檢測數(shù):目標(biāo)未被檢測到的幀數(shù),超過之后會被刪set.max_age = max_age# 目標(biāo)連續(xù)命中的最小次數(shù),小于該次數(shù)update函數(shù)不返回該目標(biāo)的KalmanBoxTracker卡爾曼濾波對象self.min_hits=min_hits# 卡爾曼濾波跟蹤器鏈,存儲多個 KalmanBoxTracker 卡爾曼濾波對象self.trackers = []#幀計數(shù)self.frane_count = 0"""update(dets):輸入dets:當(dāng)前幀中yolo所檢測出的所有目標(biāo)的檢測框的集合,包含每個目標(biāo)的score以[[x1,y1,x2,y2,score],[x1,y1,x2,y2,score],...]形式輸入的numpy.arrayx1、y1 代表檢測框的左上角坐標(biāo);x2、y2代表檢測框的右上角坐標(biāo);score代表檢測框?qū)?yīng)預(yù)測類別的概率值。輸出ret:當(dāng)前幀中跟蹤目標(biāo)成功的跟蹤框/預(yù)測框的集合,包含目標(biāo)的跟蹤的id(也即該跟蹤框(卡爾曼濾波實例對象)是創(chuàng)建出來的第幾個)第一種返回值方案:[[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo), yolo識別目標(biāo)是某種物體的可信度, trk.id] ...]第二種返回值方案(當(dāng)前使用的為該種):[[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo), trk.id] ...]d:[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo)]trk.id:卡爾曼濾波器的個數(shù)/目標(biāo)框的個數(shù),也即該跟蹤框(卡爾曼濾波實例對象)是創(chuàng)建出來的第幾個。注意:即使檢測框為空,也必須對每一幀調(diào)用此方法,返回一個類似的輸出數(shù)組,最后一列是目標(biāo)對像的id。返回的目標(biāo)對象數(shù)量可能與檢測框的數(shù)量不同。"""#更新數(shù)值def update(self,dets):"""該方法實現(xiàn)了SORT算法,輸入是當(dāng)前幀中所有物體的檢測框的集合,包括目標(biāo)的score,輸出是當(dāng)前幀目標(biāo)的跟蹤框集合,包括目標(biāo)的跟蹤的id要求是即使檢測框為空,也必須對每一幀調(diào)用此方法,返回一個類似的輸出數(shù)組,最后一列是目標(biāo)對像的id注意:返回的目標(biāo)對象數(shù)量可能與檢測框的數(shù)量不同:param dets:以[[x1,y1,x2,y2,score],[x1,y1,x2,y2,score],...]形式輸入的numpy.array:return:"""""" 每經(jīng)過一幀,frame_count+=1"""self.frane_count +=1"""1.trackers:上一幀中的跟蹤器鏈(列表),保存的是上一幀中成功跟蹤目標(biāo)的跟蹤框,也即上一幀中成功跟蹤目標(biāo)的KalmanBoxTracker卡爾曼濾波對象。2.trks = np.zeros((len(trackers), 5))上一幀中的跟蹤器鏈(列表)中的所有跟蹤框(卡爾曼濾波對象)在當(dāng)前幀中成功進(jìn)行predict預(yù)測新跟蹤框后返回的值。所有新跟蹤框的左上角的x坐標(biāo)和y坐標(biāo)、右下角的x坐標(biāo)和y坐標(biāo)、置信度 的一共5個值。1.因為一開始第一幀時,trackers跟蹤器鏈(列表)仍然為空,所以此時的trks初始化如下:np.zeros((0, 5)) 輸出值:array([], shape=(0, 5), dtype=float64)輸出值類型:<class 'numpy.ndarray'>2.np.zeros((len(trackers), 5)) 創(chuàng)建目的:1.用于存儲上一幀中的跟蹤器鏈中所有跟蹤框(KalmanBoxTracker卡爾曼濾波對象)在當(dāng)前幀中進(jìn)行predict預(yù)測新跟蹤框后返回的值,之所以創(chuàng)建的numpy數(shù)組的列數(shù)為5,是因為一個跟蹤框在當(dāng)前幀中進(jìn)行predict預(yù)測新跟蹤框后返回的值為1行5列的矩陣,返回值分別為新跟蹤框的左上角的x坐標(biāo)和y坐標(biāo)、右下角的x坐標(biāo)和y坐標(biāo)、置信度 的一共5個值。2.如果是在視頻的第一幀中,那么因為跟蹤器鏈不存在任何跟蹤框(KalmanBoxTracker卡爾曼濾波對象),因此np.zeros((len(trackers), 5))創(chuàng)建的是空列表:array([], shape=(0, 5), dtype=float64)。3.trackers:跟蹤器鏈(列表)1.跟蹤器鏈中存儲了上一幀中成功跟蹤目標(biāo)并且在當(dāng)前幀中的預(yù)測框(跟蹤框),同時也存儲了“為了當(dāng)前幀中的檢測框中的新增目標(biāo)所創(chuàng)建的”新預(yù)測框(新跟蹤框),但是同時不存儲當(dāng)前幀中預(yù)測跟蹤失敗的預(yù)測框(跟蹤框),同時也不存儲2.跟蹤器鏈實際就是多個的卡爾曼濾波KalmanBoxTracker自定義類的實例對象組成的列表。每個目標(biāo)框都有對應(yīng)的一個卡爾曼濾波器(KalmanBoxTracker實例對象),KalmanBoxTracker類中的實例屬性專門負(fù)責(zé)記錄其對應(yīng)的一個目標(biāo)框中各種統(tǒng)計參數(shù),并且使用類屬性負(fù)責(zé)記錄卡爾曼濾波器的創(chuàng)建個數(shù),增加一個目標(biāo)框就增加一個卡爾曼濾波器(KalmanBoxTracker實例對象)。把每個卡爾曼濾波器(KalmanBoxTracker實例對象)都存儲到跟蹤器鏈(列表)中。"""# 存儲跟蹤器在當(dāng)前幀逐個預(yù)測軌跡位置,記錄狀態(tài)異常的跟蹤器索引# 根據(jù)當(dāng)前所有的卡爾曼跟蹤器個數(shù)(即上一幀中跟蹤的目標(biāo)個數(shù))創(chuàng)建二維數(shù)組:行號為卡爾曼濾波器的標(biāo)識索引,列向量為跟蹤框的位置和IDtrks = np.zeros(len(self.trackers),5)#跟蹤器對當(dāng)前幀的圖像預(yù)測結(jié)果""" to_del:存儲“跟蹤器鏈中某個要刪除的”KalmanBoxTracker卡爾曼濾波對象的索引 """to_del = []#存儲要刪除的目標(biāo)框ret = []#返回的跟蹤目標(biāo)#遍歷卡爾曼濾波器中的跟蹤框"""for t, trk in enumerate(ndarray類型的trks)t:為從0到列表長度-1的索引值trk:ndarray類型的trks中每個(1, 5)形狀的一維數(shù)組"""""" 遍歷trks 用于存儲上一幀中的跟蹤器鏈中所有跟蹤框(KalmanBoxTracker卡爾曼濾波對象)在當(dāng)前幀中進(jìn)行predict預(yù)測新跟蹤框后返回的值 """for t,trk in enumerate(trks):""" 上一幀中的跟蹤器鏈中所有跟蹤框(KalmanBoxTracker卡爾曼濾波對象)在當(dāng)前幀中進(jìn)行predict預(yù)測新跟蹤框 """#使用卡爾曼跟蹤器t產(chǎn)生對應(yīng)目標(biāo)的跟蹤框,即對目標(biāo)進(jìn)行預(yù)測pos = self.trackers[t].predict()[0]""" 新跟蹤框的左上角的x坐標(biāo)和y坐標(biāo)、右下角的x坐標(biāo)和y坐標(biāo)、置信度 的一共5個值。trk中存儲了上一幀中目標(biāo)的跟蹤框在當(dāng)前幀中新的跟蹤框的信息值。"""# 遍歷完成后,trk中存儲了上一幀中跟蹤的目標(biāo)的預(yù)測結(jié)果的跟蹤框trk[:] = [pos[0],pos[1],pos[2],pos[3],0]""" 如果預(yù)測的新的跟蹤框的信息(1行5列一共5個值)中包含空值的話,則將該跟蹤框在跟蹤器鏈(列表)中的索引值t放到to_del列表中。使用np.any(np.isnan(pos))即能判斷這1行5列一共5個值是否包含空值。后面下一步將會根據(jù)to_del列表中保存的跟蹤框的索引值到跟蹤器鏈(列表)中將該跟蹤框從其中移除出去。"""#若預(yù)測結(jié)果pos中包含空值,添加到del中if np.any(np.isnan(pos)):to_del.append(t)""" np.ma.masked_invalid(跟蹤器鏈trks矩陣):將會對跟蹤器鏈trks矩陣中出現(xiàn)了NaN或inf的某行進(jìn)行生成掩碼,用于屏蔽出現(xiàn)無效值該整行的跟蹤器框。np.ma.compress_rows(包含掩碼值的跟蹤器鏈trks矩陣):將包含掩碼值的整行從中進(jìn)行移除出去。最終跟蹤器鏈trks矩陣:只包含“上一幀中的跟蹤器鏈中所有跟蹤框在當(dāng)前幀中成功進(jìn)行predict預(yù)測”的新跟蹤框。"""#trks中去除無效值的行,保存根據(jù)上一幀結(jié)果預(yù)測當(dāng)前幀的內(nèi)容# numpy.ma.masked_invalid 屏蔽出現(xiàn)無效值的數(shù)組(NaN 或 inf)# numpy.ma.compress_rows 壓縮包含掩碼值的2-D 數(shù)組的整行,將包含掩碼值的整行去除# trks中存儲了上一幀中跟蹤的目標(biāo)并且在當(dāng)前幀中的預(yù)測跟蹤框trks = np.ma.compress_rows(np.ma.masked_invalid(trks))"""1.for t in reversed(列表):1.t:列表中的元素值2.要想從List列表中刪除任意索引位置的元素的話,必須不能從列表頭開始遍歷刪除元素,必須從列表尾向列表頭的方向進(jìn)行遍歷刪除元素,因為如果從列表頭開始遍歷刪除元素的話,便會導(dǎo)致后面的元素會移動補(bǔ)充到被刪除元素的索引位置上,那么再向后進(jìn)行遍歷時便會出現(xiàn)漏遍歷的元素,也即防止破壞索引,因此刪除列表中元素時需要從列表尾向列表頭的方向進(jìn)行遍歷。2.for t in reversed(to_del)1.t:列表中的元素值2.此處to_del列表中的元素值保存的是trackers跟蹤器鏈(列表)中要刪除元素的索引值,因此從to_del列表的列表尾向列表頭的方向進(jìn)行遍歷出“trackers跟蹤器鏈(列表)中要刪除元素的”索引值。然后使用trackers.pop(t)根據(jù)trackers跟蹤器鏈(列表)中元素的索引值t自動從列表中移除該元素。3.List pop()方法1.pop()方法語法:list.pop([index=-1])2.pop()函數(shù)用于移除列表中的一個元素(默認(rèn)最后一個元素),并且返回該元素的值。3.pop(可選參數(shù))中參數(shù):可選參數(shù),要移除列表元素的索引值,不能超過列表總長度,默認(rèn)為 index=-1,刪除最后一個列表值。4.pop()返回值:該方法返回從列表中被移除的元素對象。5.pop(要移除的列表中元素的索引值):根據(jù)列表中元素的索引值自動從列表中移除"""#刪除nan的結(jié)果,逆向刪除異常的跟蹤器,防止破壞索引for t in reversed(to_del):"""根據(jù)to_del列表中保存的跟蹤框的索引值到跟蹤器鏈(列表)中將該跟蹤框從其中移除出去。trackers:上一幀中的跟蹤器鏈(列表),保存的是上一幀中成功跟蹤目標(biāo)的跟蹤框,也即成功跟蹤目標(biāo)的KalmanBoxTracker卡爾曼濾波對象。trackers.pop(要移除的某個跟蹤框的索引值):即能根據(jù)該索引值從跟蹤器鏈(列表)中把該跟蹤框移除出去"""# pop(要移除的列表中元素的索引值):根據(jù)列表中元素的索引值自動從列表中移除self.trackers.pop(t)"""matches:[[檢測框的索引值, 跟蹤框的索引值] [檢測框的索引值, 跟蹤框的索引值] 。。。]跟蹤成功并且兩兩匹配組合的IOU值大于iou閾值的檢測框和跟蹤框組成的矩陣unmatched_detections:[檢測框的索引值,。。。]1.新增目標(biāo)的檢測框在detections檢測框列表中的索引位置2.兩兩匹配組合的IOU值小于iou閾值的檢測框在detections檢測框列表中的索引位置unmatched_trackers:[跟蹤框的索引值,。。。]1.跟蹤失敗的跟蹤框/預(yù)測框在trackers跟蹤框列表中的索引位置2.兩兩匹配組合的IOU值小于iou閾值的跟蹤框/預(yù)測框在trackers跟蹤框列表中的索引位置1.matched:跟蹤成功目標(biāo)的矩陣。即前后幀都存在的目標(biāo),并且匹配成功同時大于iou閾值。2.unmatched_detections(列表):1.檢測框中出現(xiàn)新目標(biāo),但此時預(yù)測框(跟蹤框)中仍不不存在該目標(biāo),那么就需要在創(chuàng)建新目標(biāo)對應(yīng)的預(yù)測框/跟蹤框(KalmanBoxTracker類的實例對象),然后把新目標(biāo)對應(yīng)的KalmanBoxTracker類的實例對象放到跟蹤器鏈(列表)中。2.同時如果因為“跟蹤框和檢測框之間的”兩兩組合的匹配度IOU值小于iou閾值,則也要把目標(biāo)檢測框放到unmatched_detections中。3.unmatched_trackers(列表):1.當(dāng)跟蹤目標(biāo)失敗或目標(biāo)離開了畫面時,也即目標(biāo)從檢測框中消失了,就應(yīng)把目標(biāo)對應(yīng)的跟蹤框(預(yù)測框)從跟蹤器鏈中刪除。unmatched_trackers列表中保存的正是跟蹤失敗即離開畫面的目標(biāo),但該目標(biāo)對應(yīng)的預(yù)測框/跟蹤框(KalmanBoxTracker類的實例對象)此時仍然存在于跟蹤器鏈(列表)中,因此就需要把該目標(biāo)對應(yīng)的預(yù)測框/跟蹤框(KalmanBoxTracker類的實例對象)從跟蹤器鏈(列表)中刪除出去。2.同時如果因為“跟蹤框和檢測框之間的”兩兩組合的匹配度IOU值小于iou閾值,則也要把跟蹤目標(biāo)框放到unmatched_trackers中。"""#使用匈牙利算法:將目標(biāo)檢測框和卡爾曼濾波器預(yù)測的跟蹤框進(jìn)行匹配,分別獲取跟蹤成功的目標(biāo),新增的目標(biāo),離開畫面的目標(biāo)matched,unmatched_dets,unmatche_trkes = associate_detection_to_tracker(dets,trks)"""for t, trk in enumerate(trackers列表)t:為從0到列表長度-1的索引值trk:trackers列表中每個KalmanBoxTracker卡爾曼濾波對象"""#將跟蹤成功的目標(biāo)更新到對應(yīng)的卡爾曼濾波器for t,trk in enumerate(self.trackers):""" 1.trackers:上一幀中的跟蹤器鏈(列表),保存的是上一幀中成功跟蹤目標(biāo)的跟蹤框,也即成功跟蹤目標(biāo)的KalmanBoxTracker卡爾曼濾波對象。2.for t, trk in enumerate(trackers):遍歷上一幀中的跟蹤器鏈(列表)中從0到列表長度-1的索引值t 和 每個KalmanBoxTracker卡爾曼濾波對象trk。3.if t not in unmatched_trks:如果上一幀中的跟蹤框(KalmanBoxTracker卡爾曼濾波對)的索引值不在當(dāng)前幀中的unmatched_trackers(列表)中的話,即代表上一幀中的跟蹤框在當(dāng)前幀中成功跟蹤到目標(biāo),并且代表了“上一幀中的跟蹤框在當(dāng)前幀中的”預(yù)測框和當(dāng)前幀中的檢測框的匹配度IOU值大于iou閾值。4.matched[:, 1]:獲取的是跟蹤框的索引值,即[[檢測框的索引值, 跟蹤框的索引值] 。。。]中的跟蹤框的索引值。5.np.where(matched[:, 1] == t)[0]:where返回的為符合條件的“[檢測框的索引值, 跟蹤框的索引值]”數(shù)組在matched矩陣中的索引值,即行值。因此最后使用[0]就是從array([索引值/行值])中把索引值/行值取出來。6.matched[索引值/行值, 0]:根據(jù)索引值/行值獲取出matched矩陣中的[檢測框的索引值, 跟蹤框的索引值],然后獲取出第一列的“檢測框的索引值”。7.dets[d, :]:根據(jù)檢測框的索引值/行值從當(dāng)前幀中的dets檢測框列表獲取出該檢測框的所有列值,最終返回的是一個二維矩陣如下所示:第一種方案:[[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo), yolo識別目標(biāo)是某種物體的可信度]]第二種方案(當(dāng)前使用的為該種):[[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo)]]8.dets[d, :][0]:獲取出[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo)]9.trk.update(檢測框的5個值的列表):使用檢測框進(jìn)行更新狀態(tài)更新向量x(狀態(tài)變量x),也即使用檢測框更新跟蹤框。"""if t not in unmatche_trkes:d = matched[np.where(matched[:, 1] == t)[0], 0]# 使用觀測的邊界框更新狀態(tài)向量trk.update(dets[d, :][0])"""unmatched_detections(列表)保存了出現(xiàn)新目標(biāo)的檢測框的索引值,還保存了“因為跟蹤框和檢測框之間的兩兩組合的匹配度IOU值小于iou閾值的”目標(biāo)檢測框的索引值。dets[i, :]:根據(jù)索引值從當(dāng)前幀中的檢測框列表dets中獲取對應(yīng)的檢測框,即該行的所有列值。該檢測框的值為:第一種方案:[[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo), yolo識別目標(biāo)是某種物體的可信度]]第二種方案(當(dāng)前使用的為該種):[[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo)]]KalmanBoxTracker(dets[i, :]):傳入檢測框進(jìn)行創(chuàng)建該新目標(biāo)對應(yīng)的跟蹤框KalmanBoxTracker卡爾曼濾波對象trk。每個目標(biāo)框都有對應(yīng)的一個卡爾曼濾波器(KalmanBoxTracker實例對象),增加一個目標(biāo)框就增加一個卡爾曼濾波器(KalmanBoxTracker實例對象)。trackers.append(trk):把新增的卡爾曼濾波器(KalmanBoxTracker實例對象trk)存儲到跟蹤器鏈(列表)trackers中"""#為新增目標(biāo)創(chuàng)建新的卡爾曼濾波器的跟蹤器for i in unmatched_dets:trk = KalmanBoxTracker(dets[i,0])self.trackers.append(trk)# 自后向前遍歷,僅返回在當(dāng)前幀出現(xiàn)且命中周期大于self.min_hits(除非跟蹤剛開始)的跟蹤結(jié)果;如果未命中時間大于self.max_age則刪除跟蹤器。# hit_streak忽略目標(biāo)初始的若干幀""" i為trackers跟蹤器鏈(列表)長度,從列表尾向列表頭的方向 每遍歷trackers跟蹤器鏈(列表)一次 即進(jìn)行 i-=1 """i = len(self.trackers)""" reversed逆向遍歷trackers跟蹤器鏈(列表),目的為刪除列表中的元素的同時不會造成漏遍歷元素的問題 """# 逆向遍歷for trk in reversed(self.trackers):""" (跟蹤框)KalmanBoxTracker卡爾曼濾波對象trk.get_state():獲取跟蹤框所預(yù)測的在當(dāng)前幀中的預(yù)測結(jié)果(已經(jīng)從[x,y,s,r]轉(zhuǎn)換為[x1,y1,x2,y2]) [x1,y1,x2,y2]即為[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo)]。get_state()[0] 中使用[0] 是因為返回的為二維矩陣如下: 第一種方案:[[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo), yolo識別目標(biāo)是某種物體的可信度]]第二種方案(當(dāng)前使用的為該種):[[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo)]]"""#返回當(dāng)前邊界框的估計值d = trk.get_state()[0]"""1.trk.time_since_update < 1:1.time_since_update:記錄了該目標(biāo)對應(yīng)的卡爾曼濾波器中的預(yù)測框(跟蹤框)進(jìn)行連續(xù)預(yù)測的次數(shù),每執(zhí)行predict一次即進(jìn)行time_since_update+=1。在連續(xù)預(yù)測(連續(xù)執(zhí)行predict)的過程中,一旦執(zhí)行update的話,time_since_update就會被重置為0。2. time_since_update < 1:該目標(biāo)對應(yīng)的卡爾曼濾波器一旦update更新的話該變量值便重置為0,因此要求該目標(biāo)對應(yīng)的卡爾曼濾波器必須執(zhí)行update更新步驟。update更新代表了使用檢測框來更新狀態(tài)更新向量x(狀態(tài)變量x)的操作,實際即代表了使用“通過yoloV3得到的并且和預(yù)測框(跟蹤框)相匹配的”檢測框來更新該目標(biāo)對應(yīng)的卡爾曼濾波器中的預(yù)測框(跟蹤框)。2.trk.hit_streak >= min_hits:1.hit_streak1.連續(xù)更新的次數(shù),每執(zhí)行update一次即進(jìn)行hit_streak+=1。2.在連續(xù)更新(連續(xù)執(zhí)行update)的過程中,一旦開始連續(xù)執(zhí)行predict兩次或以上的情況下,當(dāng)連續(xù)第一次執(zhí)行predict時,因為time_since_update仍然為0,并不會把hit_streak重置為0,然后才會進(jìn)行time_since_update+=1;當(dāng)連續(xù)第二次執(zhí)行predict時,因為time_since_update已經(jīng)為1,那么便會把hit_streak重置為0,然后繼續(xù)進(jìn)行time_since_update+=1。 2.min_hits跟蹤框連續(xù)成功跟蹤到目標(biāo)的最小次數(shù),也即跟蹤框至少需要連續(xù)min_hits次成功跟蹤到目標(biāo)。3.hit_streak >= min_hits跟蹤框連續(xù)更新的次數(shù)hit_streak必須大于等于min_hits。而小于該min_hits次數(shù)的話update函數(shù)不返回該目標(biāo)的KalmanBoxTracker卡爾曼濾波對象。3.frame_count <= min_hits:因為視頻的一開始frame_count為0,而需要每經(jīng)過一幀frame_count才會+=1。因此在視頻的一開始前N幀中,即使frame_count 小于等于min_hits 也可以。"""# 跟蹤成功目標(biāo)的box與id放入ret列表中if (trk.time_since_update < 1) and (trk.hit_streak >= self.min_hits or self.frame_count <= self.min_hits):""" 1.ret:當(dāng)前幀中跟蹤目標(biāo)成功的跟蹤框/預(yù)測框的集合,包含目標(biāo)的跟蹤的id(也即該跟蹤框(卡爾曼濾波實例對象)是創(chuàng)建出來的第幾個)第一種返回值方案:[[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo), yolo識別目標(biāo)是某種物體的可信度, trk.id] ...]第二種返回值方案(當(dāng)前使用的為該種):[[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo), trk.id] ...]d:[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo)]trk.id:卡爾曼濾波器的個數(shù)/目標(biāo)框的個數(shù),也即該跟蹤框(卡爾曼濾波實例對象)是創(chuàng)建出來的第幾個。2.np.concatenate((d, [trk.id + 1])).reshape(1, -1)[[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo), 該跟蹤框是創(chuàng)建出來的第幾個]]"""ret.append(np.concatenate((d, [trk.id + 1])).reshape(1, -1)) # +1 as MOT benchmark requires positive""" i為trackers跟蹤器鏈(列表)長度,從列表尾向列表頭的方向 每遍歷trackers跟蹤器鏈(列表)一次 即進(jìn)行 i-=1 """i -= 1"""trk.time_since_update > max_age1.time_since_update:記錄了該目標(biāo)對應(yīng)的卡爾曼濾波器中的預(yù)測框(跟蹤框)進(jìn)行連續(xù)預(yù)測的次數(shù),每執(zhí)行predict一次即進(jìn)行time_since_update+=1。在連續(xù)預(yù)測(連續(xù)執(zhí)行predict)的過程中,一旦執(zhí)行update的話,time_since_update就會被重置為0。2.max_age:最大跟丟幀數(shù)。如果當(dāng)前連續(xù)N幀大于最大跟丟幀數(shù)的話,則從跟蹤器鏈中刪除該卡爾曼濾波對象的預(yù)測框(跟蹤框)。3.time_since_update > max_age:每預(yù)測一幀time_since_update就會+=1,只有預(yù)測的跟蹤框跟蹤到目標(biāo)(即預(yù)測的跟蹤框和檢測框相似度匹配)才會執(zhí)行update更新,那么time_since_update才會被重置為0。那么當(dāng)連續(xù)time_since_update幀都沒有跟蹤到目標(biāo)的話,即當(dāng)連續(xù)time_since_update幀大于最大跟丟幀數(shù)時,那么就需要根據(jù)該跟蹤失敗的跟蹤器框的索引把該跟蹤器框從跟蹤器鏈(列表)trackers中進(jìn)行移除出去。"""# 跟蹤失敗或離開畫面的目標(biāo)從卡爾曼跟蹤器中刪除if trk.time_since_update > self.max_age:"""trackers:上一幀中的跟蹤器鏈(列表),保存的是上一幀中成功跟蹤目標(biāo)的跟蹤框,也即成功跟蹤目標(biāo)的KalmanBoxTracker卡爾曼濾波對象。trackers.pop(要移除的某個跟蹤框的索引值):即能根據(jù)該索引值從跟蹤器鏈(列表)中把該跟蹤框移除出去"""# pop(要移除的列表中元素的索引值):根據(jù)列表中元素的索引值自動從列表中移除self.trackers.pop(i)# 返回當(dāng)前畫面中所有目標(biāo)的box與id,以二維矩陣形式返回if len(ret) > 0:""" ret:當(dāng)前幀中跟蹤目標(biāo)成功的跟蹤框/預(yù)測框的集合,包含目標(biāo)的跟蹤的id(也即該跟蹤框(卡爾曼濾波實例對象)是創(chuàng)建出來的第幾個)第一種返回值方案:[[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo), yolo識別目標(biāo)是某種物體的可信度, trk.id] ...]第二種返回值方案(當(dāng)前使用的為該種):[[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo), trk.id] ...]d:[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo)]trk.id:卡爾曼濾波器的個數(shù)/目標(biāo)框的個數(shù),也即該跟蹤框(卡爾曼濾波實例對象)是創(chuàng)建出來的第幾個。[[左上角的x坐標(biāo), 左上角的x坐標(biāo)y坐標(biāo), 右下角的x坐標(biāo), 右下角的y坐標(biāo), yolo識別目標(biāo)是某種物體的可信度, 該跟蹤框是創(chuàng)建出來的第幾個] [...][...]]"""return np.concatenate(ret)return np.empty((0, 5))總結(jié)
以上是生活随笔為你收集整理的智慧交通day02-车流量检测实现10:多目标追踪实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [已解决]fdfs-client-py=
- 下一篇: 成功解决 ProxyError: Con