論文地址:https://arxiv.org/pdf/1703.07402.pdf
論文官方源碼地址:https://github.com/nwojke/deep_sort
MOT16 benchmark地址:https://motchallenge.net/data/MOT16/
上述最后兩個文件的內容解壓縮后:
- MOT16 benchmark 解壓為MOT16文件夾
- npy文件解壓后將其中的detections文件夾剪切,並放置在resources文件夾中。(新建resources)
源碼解讀:
源碼的入口是deep_sort_app.py 程序,在該程序的入口包含args = parse_args()函數,說明需要在命令行運行時需要輸入命令行參數。如下圖所示:
也正如作者github中關於運行的描述,在命令行或終端運行時需要輸入如下指令。
1 python deep_sort_app.py \ 2 --sequence_dir=./MOT16/test/MOT16-06 \ 3 --detection_file=./resources/detections/MOT16_POI_test/MOT16-06.npy \ 4 --min_confidence=0.3 \ 5 --nn_budget=100 \ 6 --display=True
如果想要在pycharm中運行,需要需要在pycharm中設置命令行參數。步驟如下:
- 選擇Run - Edit Configurations...
2. 在界面中的Parameters:中填寫命令行參數。 如果不懂argparse命令行相關設置也不要緊,可以直接將github中給出的命令行參數去掉 "python deep_sort_app.py \" 即可。
以MOT16-14的數據集為切入點進行解讀,之后不再贅述。
deep_sort_app.py主要由以下幾個函數組成,其中核心程序在於def run()程序中的frame_callba ck()函數中,用於實現幀與幀之間跟蹤器的處理。
*************************************Processing frame 00001******************************************
1. 首先,獲取當前幀數frame_idx的檢測信息,並過濾掉置信度小於閾值的bbox。
這里會將檢測信息中包含的bbox坐標的信息、置信度、features存到一個檢測類的對象中。並將多個bbox的對象存入列表中。[坐標info(array) 置信度(float) features(array) ]
檢測的坐標info中存儲的是bbox[左下坐標的x,y,寬,高]。
在第一幀中,一共有18個b-box。
2. 對這些b-box進行NMS操作
為了便於操作,將各對象中包含的坐標信息和置信度讀取成列表的形式。NMS的操作與yolo中有所不同。NMS操作如下:
- 將box info轉換為[x1, y1, x2, y2]的形式。 【x,y,w,h】 2 【x1, y1, x2, y2】
- 計算bbox的面積。 需要注意的是,使用像素坐標點計算面積需要+1噢 (y2-y1+1)*(x2-x1+1)
- 對置信度進行排序,返回索引的排序結果。
- 進行NMS,每次取置信度最大的bbox與其余的bbox進行iou處理,通過相交面積 / 做iou的bbox的面積產生 overlap。
overlap = (w * h) / area[idxs[:last]] # 相交的面積/比較的框的面積
- 去掉當前置信度最大的和overlap超過1的索引。【個人感覺此處的NMS並不會有什么作用,面積比超過或等於1的檢測框一般在行人檢測不存在】
3. 進行kalman的相關運算:
主要包含兩個部分,分別代表了Kalman濾波中的時間更新和狀態更新。
3.1 時間更新
由於此時並沒有tracker,不需要對狀態進行估計。下述循環也就不存在,該函數直接跳出。
3.2 狀態更新
首先,會進行匹配級聯,返回匹配成功的matches,未匹配的跟蹤器unmatched_tracks,未匹配的檢測器unmatched_detections。
由於第一幀還沒有tracker,在匹配的過程中就不存在已確認和未確認的tracker 。(論文中描述,新生成的跟蹤器前三幀是試探期,只有在三幀內都可以匹配成功才會確認,否則將會被刪除。)
自然,也就不會存在匹配成功的和未匹配的tracker, 只會有未匹配到的檢測器18個(全都是未匹配的檢測框)。
利用檢測框創建新的tracker:
# Update track set.
由於此時匹配的跟蹤器和未匹配的跟蹤器均為[],並不會對匹配的跟蹤器進行更新。只是對未匹配的檢測框創建其對應的新tracker。
detection.to_xyah 是將檢測框的(左下坐標x1, y1, w, h)轉化為(中心點坐標 x, y, 長寬比a, h)
初始化kalman相關的變量。mean是一組8維的kalman濾波狀態變量x。
然后,為當前檢測框 生成新的Track對象,並存儲在tracks列表中:
Tracker類的初始化如下圖所示,可以看出,剛生成的Tracker對象是Tentative(試探性的,同論文中所述一致,剛建立的tracker處於試探期,需要在后續三幀中都可以成功關聯,該tracker才會確認;否則將會被刪除。)另外,每一幀的特征feature也會進行存儲,用於后續余弦距離的計算。
第一幀根據18個detections生成了18個處於試探期的trackers。
由於此時的trackers均處於試探期,沒有確認,所以不會更新其馬氏距離。
在可視化時,會繪制檢測框和跟蹤框兩種。
其中,檢測框繪制顏色為0,0,255。由於此時是BGR的色域,所以檢測框為紅色框。 跟蹤框會為每一個id設置不同的顏色。但需要當前的tracker處於確認的狀態。 所以在前三幀中,只會繪制檢測框,即僅有紅色框,如下圖所示。
第一幀除了創建了Tracker,沒有什么有含金量的內容。步入第二幀。
*************************************Processing frame 00002******************************************
第二幀中,關於檢測結果的提取和NMS操作與第一幀相同,不再贅述。經過NMS后剩余的檢測框detections有12個:
此時,由於第一幀構建了18個trackers,會利用kalman濾波進行估計,得到18個跟蹤預測結果。【即時間更新,tracker.predict()】。而且預測完成后會對每一個tracker的time_since_update+=1。 該操作與SORT中相同,用於衡量其每一次是否關聯成功,后續在匹配中會用到。
其中,kalman濾波的時間更新,self._std_weight_position為初始化的1/20(0.05),self. _std_weight_velocity為初始化的1/160(0.00625),mean[3]是上一狀態估計值的長寬比。其中可以對應到kalman fitter的時間更新第一步和時間更新第二步的操作。【mean 和 covariance的計算】
目前,有18個trackers, 12個detections,將會涉及到對tracker進行update的相關操作。【tracker.update(detections)】
- 首先要進行檢測結果和跟蹤預測結果的匹配級聯(Matching Cascade)
步驟如下:
part 1 : 將現有的trackers划分為confirmed_tracks和unconfirmed_tracks兩種:
此時18個trackers均是unconfirmed_tracks。confirmed_trackers為空列表。
part 2:針對confirmed_trakers使用外觀特征進行級聯匹配。
但由於在第二幀,還沒有confirmed_trackers,所以該步操作並不會進行。返回的匹配結果均為未匹配的檢測框。
part 3:unconfirmed tracks與還沒有匹配上的檢測結果通過IOU計算的方式進行關聯
unmatched_tracks_a並不參與IOU關聯的運算,僅用於后續unmatched_tracks的構成。
iou_track_candidates共有18個(源自第一幀建立的track);
unmatched_detections共有12個 (源自第二幀的檢測結果)。
首先,計算這些框兩兩之間的iou值,並通過1-iou得到matrix_cost。
然后,再將cost_matrix中的值大於0.7的,全部置為0.70001。而這些值表示檢測框與trackers的IOU較小。
P.S. 該矩陣為(18,12)的矩陣
將cost_matrix矩陣作為匈牙利算法的輸入,得到匹配的結果:
從匹配結果上可以看出,第0列是iou_track_candidates的索引(18個), 第1列是unmatched_ detections(12個)的索引。
再對匹配的結果進行進一步的處理: (進一步的關聯處理)
Ⅰ. 將仍然沒有匹配的檢測結果和trackers進行統計。
Ⅱ. 將cost_matrix>0.7的過濾器與追蹤器之間的關聯去掉。【cost_matrix>0.7,說明二者的IOU小於0.3,即二者之間的關聯不大】
Ⅲ. 記錄匹配成功的track id 和 detection id
Ⅳ. 最終返回處理后的結果
part 4 將part 2中處理的結果與part 3 中處理的結果進行整合,得到最終的匹配結果和未匹配結果。
- 匹配級聯完成之后,更新多目標跟蹤器trackers集合
part 1: 針對match的,根據檢測的結果更新track的參數。 【Kalman 狀態更新過程】
主要包含以下步驟:
Ⅰ. 與SORT一樣,更新Kalman濾波中的一系列運動變量、關聯次數以及重置time_since_update.
Ⅱ. 將當前幀的features保存到該跟蹤器中。
Ⅲ.如果已經連續關聯3幀,將該track的狀態由tentative改為confirmed。由於此時還沒有成功關聯3幀,各tracker還是1 tentative的狀態。
此時,每個track中將包含2幀中目標的features
part 2: 針對unmatched tracks:
對於未匹配的跟蹤器,處理手段主要包含以下兩種情況:
Ⅰ. 當tracker處於tentative(試探期)時, 直接將該tracker和其id刪除;
Ⅱ. 當tracker處於確認狀態,並且self.time_since_update大於max_age(源碼中設置的是30),也就是說,如果已確認的tracker 30幀以上未關聯成功,就將該tracker即其id刪除。
此時,[9, 10, 11, 15, 16, 17]這些id的tracker將會被刪除。
part 3: 針對unmatched_detections:
對於未匹配的檢測框,要為其創建新的tracker【處於試探期的】。
因為當前幀沒有unmatched_detection,所以該操作沒有進行。 【該操作與第一幀根據detections 生成tracker的操作相同】
- 更新已確認的距離度量矩陣
由於此時的trackers仍然都處於試探期,所以並不會進行更新。
同樣,該幀的trackers都未得到確認,在顯示時僅會顯示檢測框的紅色,並且在結果保存時,並不會寫到txt中。
*************************************Processing frame 00003******************************************
第三幀中,有14個檢測框,12個未確認的tracker(處於tentative狀態)。
首先,要對12個未確認的tracker進行Kalman狀態估計。然后對每一個tracker的time_since_ update += 1。【更新完成后匹配成功的會置0,不成功的將會進行刪除,或者死緩的操作。】
【tracker.predict()】
重點還是放在12個未確認的tracker和14個detections如何匹配,以及tracker如何更新上!【tracker.update()】
一、 第三幀的檢測結果與跟蹤的預測結果相匹配
step 1: 將現有的trackers划分為已確認和未確認兩組。
step 2:首先對已經確認的trackers進行匹配級聯。
由於此時沒有已經確認的trackers,所以該步依然不會有實質性的操作。返回結果中,匹配的tracker,未匹配的tracker均為空列表[],未匹配的檢測框detections為當前的檢測結果。
step 3: unconfirmed tracks與還沒有匹配上的檢測結果通過IOU計算的方式進行關聯
unmatched_tracks_a並不參與IOU關聯的運算,僅用於后續unmatched_tracks的構成。
此時,unconfirmed_tracks包含12個,未匹配的檢測器包含14個。對它們之間進行IOU關聯運算。
IOU關聯運算,首先計算iou_track_candidates與unmatched_detections兩兩之間的cost_matrix矩陣。cost_matrix矩陣的計算通過iou計算,再通過1-iou。
然后,將cost_matrix矩陣中>0.7的值全部賦值為0.70001。
再將處理后的cost_matrix輸入到匈牙利算法中進行匹配,得到線性的匹配結果indices。左側為跟蹤器的id,右側為檢測器的id。
step 4:對匹配結果進一步處理,獲取最終的匹配,未匹配【檢測、跟蹤】的結果。
首先,對所有的檢測結果id 在線性匹配結果indices中遍歷,將沒有出現的檢測結果記錄到未匹配檢測unmatched_detections中。
其次,對所有跟蹤結果id 在線性匹配結果indices中遍歷,將沒有出現的檢測結果記錄到未匹配跟蹤器unmatched_tracker中。
最后,對cost_matrix中值大於0.7 對應的跟蹤id和檢測id加入到未匹配的行列中。【因為它們之間的iou太小】 將cost_matrix中值小於0.7對應的跟蹤id和檢測id結果對加入到匹配的結果中。並返回結果。
這些計算都是基於cost_matrix和匈牙利算法計算的結果indices。
step 5: 對step2 和step4的結果進行整合
第三幀級聯匹配的結果:
成功匹配的結果有11個,未匹配的檢測結果有3個,未匹配的跟蹤器有1個。
二、根據級聯匹配的結果更新跟蹤器的集合【frame 3】:
1. 狀態的變更(試探 確認 刪除 1 2 3 )
2. 新跟蹤器的創建
step 1:針對matches,根據檢測結果更新track參數。(11個)
A. 根據檢測結果,更新Kalman參數
主要包括以下步驟:
a. 根據檢測的結果更新Kalman狀態參數。 主要是計算狀態變量x(mean)和Pk(covariance);
b. 將當前幀標定的特征保存到當前的跟蹤器中;
c. 更新當前track的狀態是否更新。 是否從試探期轉化到確認期。(3幀 存在)
此時,有11個track已經經歷了3幀,狀態將會被修改為確認狀態Confirmed,在代碼中以2進行保存。
step 2:針對unmatched_tracks (刪1個)
對於沒有成功匹配的跟蹤器,主要由兩種情況:
A. 沒有匹配的跟蹤器是確認狀態Confirmed: 30幀的死緩,如果30幀都沒有成功關聯,則修改其狀態為Delete,在代碼中以3進行保存。
B. 沒有匹配的跟蹤器是試探狀態Tentative: 修改其狀態為Delete,在代碼中以3進行保存。
狀態表:
在第三幀中,一中最后的結果,將會有1個跟蹤器被刪除。
step 3: 針對unmatched_detections: (建3個)
需要創建3個新的跟蹤器(試探期)。
三、 更新Cosine矩陣【需要保存的特征】
此時,已經包含狀態為Confirmed的tracks,便需要對distance metric進行更新。tracks即active_targets中:
A. 對於已經確認的跟蹤器,提取器特征集
B. 計算Cosine metric
最多會保存100條特征集合。
四、 可視化及結果的保存
由於此時已經包含了狀態為Confirmed的跟蹤器,所以在顯示的過程中不會僅顯示紅色的檢測框。並且這些已經確認的跟蹤器也會保存在txt中。
可以看出,第3幀與前兩幀不同的是在更新時,產生了Confirmed狀態的跟蹤器。增添了Cosine矩陣的計算。同時,在顯示時,也增添了 追蹤的結果。
*************************************Processing frame 00004******************************************
由於第3幀中,產生了狀態為Confirmed trackers,所以在第4幀中,將會增添已確認trackers的級聯匹配操作。
關於前3幀相同的操作,這里將會進行簡述,只對新增的對Confirmed狀態的trackers的級聯匹配進行詳述。
第4幀中,一共有14個檢測框。
根據現有的trackers,對每一個trackers進行kalman跟蹤結果預測,也就是時間更新的狀態估計。預測完之后,對每一個trackers的self.time_since_update += 1。 其中,有11個是Confirmed 的狀態,有3個是tentative狀態。
進入14的detections與14個trk的匹配過程。【update】
1. 將現有的跟蹤器划分為已確認和未確認兩組;
此時,已經存在confirmed_tracks。
2. 對confirmed_tracks與detections進行級聯匹配。
前3幀中,由於此時沒有confirmed_tracks,所以不進行該過程的實際操作。第4幀中,存在confirmed_tracks,需要對其進行級聯匹配,這也是第四幀中新增的一部分。
A. 首先獲取處於確認狀態的trackers的indices和 檢測框的indices,將檢測框的indices命名為unmatched_detections:
B. 對已經確認的tracker進行級聯匹配
級聯匹配是什么呢?為什么會有級聯匹配呢?
由於已經確認的tracker存在可能沒有匹配到的狀態,即處於死緩的tracker。所以需要對那些死緩期間的trackers,需要對幾幀沒有匹配到的tracker依次進行級聯。
所以cascade_depth為30,與死緩的幀數一致。當cascade_depth為0時,即為非死緩的tracker。
track_indices_1列表的作用就是統計不同level的tracker。【正常狀態的和不同死緩的】,對他們進行關聯操作,可以看出,正常狀態-1幀未關聯-2幀未關聯- .... - 29幀未關聯,優先級遞減。
關聯操作與檢測框與未確認的跟蹤器的關聯操作是一致的。
結果如下:
可以看出,在有已確認的trackers時,基本上已經把detections的結果大多數都關聯了。留給沒有確認的trackers的detections不多了。這種優勝劣汰的機制也與人的感知十分的神似~
其中,min_cost_matching函數中,包含distance_metric的傳入,其調用函數為gated_metric():
其中,包含余弦距離(外觀特征)和馬氏距離(運動信息)的計算。
余弦距離:
self._metric()函數中調用下述函數實現余弦距離的計算:
3. 對未確認的trackers進行IOU匹配。
匹配結果如下:
4. 獲得最終的匹配情況
5. 進行跟蹤器集合的更新
A. 狀態的轉換
B. 新跟蹤器的生成
6. 更新現有已確認的跟蹤器的x和Pk
7.進行可視化和保存