建議先讀文獻[1],然後配合着看本文。
1. 前言
上一篇文章針對FairMOT進行了測試和訓練,測試的效果很好。這裏對FairMOT的訓練和測試原理進行剖析。
2. 訓練
FairMOT基本採用的是檢測+跟蹤的思路,檢測採用的centernet,跟蹤採用deepsort。但是將兩個任務進行了端到端訓練。其實訓練還是相對簡單的。
初始問題
- 檢測和跟蹤是如何一起端到端訓練的?
2.1 模型結構圖
先來一張論文的模型圖
下圖是我自己理解所畫的輸出部分。詳細內部圖
2.2 詳解
-
檢測部分
檢測部分,可以參考我的看代碼解讀CenterNet :Objects as Points -
reid部分
這篇文章將它處理成了一個分類任務。假設訓練數據集中出現了10000個需要跟蹤的id(一個id,就是出現到消失的物體,不管其是什麼類別),在跟蹤分支中就對有object的點,採用線性層Linear(512×10000)輸出,然後在利用交叉熵損失函數計算id_loss -
loss 部分
如我畫的圖
3. 推理
推理是相對較難的部分。其核心是採用deepsort,文獻1講的很好。
前期準備:
- 卡爾曼濾波
- 匈牙利算法
- deepsort算法流程
3.1 變量說明
狀態變量說明
activated:激活,用於單次誤檢目標的判斷
track_state: 跟蹤狀態,有tracked, lost, remove
4種容器
- unconfirmed_stracks(activated = F, track_state=tracked ) 只出現一次的目標(檢測器誤檢的目標)
- activated_stracks(activate=T, track_state=tracked) 跟蹤狀態良好的tracker
- lost_stracks(activate=T, track_state=lost)激活,但是跟蹤狀態丟失
- refind_stracks(activated=T, track_state=lost->tracked)跟丟之後重新找回的目標
1種tracker容器
tracker[ activated(激活狀態), tracked(跟蹤狀態),mean(卡爾曼均值向量), covariance(卡爾曼協方差矩陣), smooth_feat(tracker的外觀特徵) ]
3.2 具體流程(代碼+解讀)
1. 第一幀初始化
- 用centernet檢測得到目標(x,y,w,h)
- 對每個目標初始化一個tracker,就用n個tracker
- 將n個tracker放入activated_stracks容器中
for inew in u_detection: # 對cosine/iou/uncofirmed_tracker都未匹配的detection重新初始化一個unconfimed_tracker
track = detections[inew]
if track.score < self.det_thresh:
continue
track.activate(self.kalman_filter, self.frame_id) # 激活track,第一幀的activated=T,其他爲False
activated_stacks.append(track)
第二幀
2. 對新檢測目標進行外觀+距離匹配
- 對第2幀進行目標檢測得detections(這也是初始化成tracker的)
- 將[activated_stracks lost_stracks]融合成pool_stracks
- detections和pool_stracks根據feat計算外觀cost_矩陣,就是用feat計算cosine距離
- 利用卡爾曼算法預測pool_stracks的新的mean,covariance
- 計算pool_stracks和detection的距離cost,並將大於距離閾值的外觀cost_矩陣賦值爲inf
- 利用匈牙利算法進行匹配
- 能匹配的
- pool_stracks的track_state==tracked,更新smooth_feat,卡爾曼狀態更新mean,covariance(卡爾曼用),計入activated_stracks
- pool_stracks的track_state==tracked,更新smooth_feat,卡爾曼狀態更新mean,covariance(卡爾曼用),
計入refind_stracks
- 不能匹配的
- 提出不能匹配的,得到新的detections,r_tracked_stracks
- 能匹配的
strack_pool = joint_stracks(tracked_stracks, self.lost_stracks)
dists = matching.embedding_distance(strack_pool, detections) # 計算新檢測出來的目標和tracked_tracker之間的cosine距離
STrack.multi_predict(strack_pool) # 卡爾曼預測
dists = matching.fuse_motion(self.kalman_filter, dists, strack_pool, detections) # 利用卡爾曼計算detection和pool_stacker直接的距離代價
matches, u_track, u_detection = matching.linear_assignment(dists, thresh=0.7) # 匈牙利匹配 // 將跟蹤框和檢測框進行匹配 // u_track是未匹配的tracker的索引,
for itracked, idet in matches: # matches:63*2 , 63:detections的維度,2:第一列爲tracked_tracker索引,第二列爲detection的索引
track = strack_pool[itracked]
det = detections[idet]
if track.state == TrackState.Tracked:
track.update(det, self.frame_id) # 匹配的pool_tracker和detection,更新特徵和卡爾曼狀態
activated_starcks.append(track)
else:
track.re_activate(det, self.frame_id, new_id=False) # 如果是在lost中的,就重新激活
refind_stracks.append(track)
- 對剩下的detections,r_tracked_stracks進行IOU匹配
- 計算detections,r_tracked_stracks計算IOU cost矩陣
- 針對IOU cost進行匈牙利匹配
- 能匹配的
- pool_stracks的track_state==tracked,更新smooth_feat,卡爾曼狀態更新mean,covariance(卡爾曼用),計入activated_stracks
- pool_stracks的track_state==tracked,更新smooth_feat,卡爾曼狀態更新mean,covariance(卡爾曼用),
計入refind_stracks
- 不能匹配的
- r_tracked_stracks狀態track_state改爲lost
- detections再遺留到下一步進行繼續匹配
- 能匹配的
detections = [detections[i] for i in u_detection] # u_detection是未匹配的detection的索引
r_tracked_stracks = [strack_pool[i] for i in u_track if strack_pool[i].state == TrackState.Tracked]
dists = matching.iou_distance(r_tracked_stracks, detections)
matches, u_track, u_detection = matching.linear_assignment(dists, thresh=0.5)
for itracked, idet in matches:
track = r_tracked_stracks[itracked]
det = detections[idet]
if track.state == TrackState.Tracked:
track.update(det, self.frame_id)
activated_starcks.append(track)
else:
track.re_activate(det, self.frame_id, new_id=False) # 前面已經限定了是TrackState.Tracked,這裏是不用運行到的。
refind_stracks.append(track)
for it in u_track:
track = r_tracked_stracks[it]
if not track.state == TrackState.Lost:
track.mark_lost()
lost_stracks.append(track) # 將和tracked_tracker iou未匹配的tracker的狀態改爲lost
- 上一步遺留的detection與unconfirmed_stracks進行IOU匹配
- 計算IOU cost
- 匈牙利匹配
- 能匹配
- 更新 unconfirmed_stracks,更新smooth_feat,卡爾曼狀態更新mean,covariance(卡爾曼用),計入activated_stracks
- 不能匹配
- unconfirmed_stracks直接計入removed_stracks
- 不能匹配的detections,在遺留到下一步
- 能匹配
detections = [detections[i] for i in u_detection] # 將cosine/iou未匹配的detection和unconfirmed_tracker進行匹配
dists = matching.iou_distance(unconfirmed, detections)
matches, u_unconfirmed, u_detection = matching.linear_assignment(dists, thresh=0.7)
for itracked, idet in matches:
unconfirmed[itracked].update(detections[idet], self.frame_id)
activated_starcks.append(unconfirmed[itracked])
for it in u_unconfirmed:
track = unconfirmed[it]
track.mark_removed()
removed_stracks.append(track)
- 上一步遺留的detections,初始化成unconfirmed_stracks中的tracker
for inew inu_detection: # 對cosine/iou/uncofirmed_tracker都未匹配的detection重新初始化一個unconfimed_tracker
track = detections[inew]
if track.score < self.det_thresh:
continue
track.activate(self.kalman_filter, self.frame_id) # 激活track,第一幀的activated=T,其他爲False
activated_starcks.append(track)
- 對15幀連續track_state=lost的tracker,進行刪除
for track in self.lost_stracks:
if self.frame_id - track.end_frame > self.max_time_lost: # 消失15幀之後
track.mark_removed()
removed_stracks.append(track)
4. 最後再來看一下效果
FAIRMOT多目標跟蹤
5. TO DO
- 根據原理來說,該模型是支持多類別,多目標的跟蹤的,找時間實現一下。