DeepSort學習筆記
deepsort作為多目標跟蹤的經(jīng)典算法,相信每個入門MOT領域的人都是從deepsort開始,網(wǎng)上有大量的學習教程,但是相比別人喂我還是更喜歡自己吃。deepsort的代碼是典型的面向對象的編程思想,代碼優(yōu)雅簡潔,注釋多,可以精讀學習。為了防止以后遺忘,特地將自己的學習感悟記錄下來。
需要掌握的先驗知識
deepsort的綜合性很強,涉及的相關算法很多,以下列出一些主要的算法,由于其中的一些公式太難編輯了,后續(xù)自己會專門結合代碼實現(xiàn)寫一下自己的理解。
- 1、卡爾曼濾波:
- 2、馬氏距離:https://zhuanlan.zhihu.com/p/46626607
- 3、PCA主成分分析:https://www.imooc.com/article/29197
- 4、匈牙利算法:https://zhuanlan.zhihu.com/p/62981901
- 5、行人Reid
- 6、MOT評價指標:https://zhuanlan.zhihu.com/p/35391826
主要使用到的類
- 1、
Tracker:跟蹤器類,所有的邏輯都用該類串聯(lián),負責管理一系列track,通過調用Detection、NearestNeighborDistanceMetric、Track類完成detections和tracks的一系列操作。 - 2、
Detection:負責每一個檢測的對象 - 3、
NearestNeighborDistanceMetric:距離計算的類 - 4、
Track:軌跡類,完成對一條軌跡的狀態(tài)管理、初始化、更新、刪除、預測等等。 - 5、
KalmanFilter:卡爾曼濾波 - 6、
Extractor:reid網(wǎng)絡
Pipeline
DeepSort將檢測器輸出的predict box作為輸入,輸出不同的track的運動狀態(tài)u,v,γ,h,x ?,y ?,γ ?,h ?,不同的track以不同的track id標識。主要是用Tracker類的predict和update完成整個流程。千言萬語不如一張圖。

1、Input
將檢測器產(chǎn)生的predict box轉化為Detection對象。
2、tracker.predict
通過流程圖我們可以知道,tracker.predict主要干了三件事:
- 1、使用卡爾曼濾波的
predict方法對每條track進行狀態(tài)預測,基于上一時刻的狀態(tài)對當前時刻的狀態(tài)和不確定性進行預測(根據(jù)線性運動學方程進行預測mean(u,v,γ,h,x ?,y ?,γ ?,h ?),x=vt;預測不確定性covariance)。 - 2、更新track的
age,這個屬性之后沒用到 - 3、更新track的
since_update_times,這個屬性很重要,每track.predict一次,該計數(shù)器加1,如果后續(xù)該track成功update了,即match了detection,該計數(shù)器清零。因此這個屬性是表示了這條track連續(xù)多少次未和detection匹配,用于判斷此條track是否離開了畫面。
3、tracker.update
通過流程圖我們可以知道,tracker.update主要干了六件事:
(1)track和detection的match
這部分是deepsort流程中最重要的一個環(huán)節(jié)。tracker中會保存所有的track,每一次的檢測結果得到的detections都需要和tracker中的track進行匹配,完成detection的分配問題。
-
級聯(lián)匹配(
linear_assignment.matching_cascade):self.time_since_update小的track擁有優(yōu)先匹配權,因此丟失越久的track優(yōu)先級越低。在deepsort的論文中作者解釋:一個track的不確定性(covariance)會隨著它未匹配的次數(shù)增加,即self.time_since_update越大,track的不確定性越大。而當兩個track去競爭同一個detection時,不確定性更高的track和detection的馬氏距離會更小。這樣會破壞track的持續(xù)性,增加不穩(wěn)定性。因此令最近匹配上的track應該有更高的匹配優(yōu)先級。對每條track根據(jù)self.time_since_update進行循環(huán)的級聯(lián)匹配,返回值為matched,unmatched_track,unmatched_detections。-
參與匹配的對象:只有
confirmed狀態(tài)的tracks會參與匹配。 - 距離度量方法:主要使用外觀特征的余弦距離和馬氏距離。
- 余弦距離:用于衡量外觀特征之間的距離。計算每一個detection經(jīng)過reid網(wǎng)絡得到的feature特征向量和track中已經(jīng)存儲的feature特征向量(存儲的數(shù)量通過budget參數(shù)進行設置,默認100)計算余弦距離,每個detection和每個track會有budget個余弦距離,取其中的最小值為該track和detetion的余弦距離。
- 馬氏距離:用于過濾外觀特征匹配上但detection和track在圖像中的location相差很大的配對。計算每個detetion到每個track的馬氏距離,根據(jù)馬氏距離的閾值(3倍的標準差),去除離群點。
- 匈牙利算法:對上述的滿足余弦距離和馬氏距離條件的detection和track使用匈牙利算法進行匹配。
-
參與匹配的對象:只有
-
IOU匹配:返回值為
matched,unmatched_track,unmatched_detections。-
參與匹配的對象:
self.time_since_update==1的track和unconfirmed狀態(tài)的track參與匹配。 - 距離度量的方法:IOU距離
-
IOU距離:計算track和detection的iou,track的x,y,w,h是經(jīng)過卡爾曼濾波
predict的狀態(tài),dsitance = 1 - IOU
-
參與匹配的對象:
需要額外注意的是:
- track的state要轉化為
confirmed時才能進行級聯(lián)匹配,必須使用IOU匹配連續(xù)匹配上_n_hit次才能將track的state由tentative轉換為confirmed。 - state為
confirmed的track,如果有一次未匹配上,即未完成track.update,其self.time_since_update > 1,無法進行IOU匹配。因此只要跟丟了一幀,就必須通過級聯(lián)匹配才能重新激活track。
(2)track.update
track的mean表示運動學狀態(tài)(u,v,γ,h,x ?,y ?,γ ?,h ?),track的state表示track是否屬性狀態(tài)(deleted,tentative, confirmed),需要更新track的五個部分。
- 需要更新的track對象:
matched的track。 - 更新track的狀態(tài)
mean和不確定性covariance:綜合考慮卡爾曼濾波predict預測值和detection的觀測值,執(zhí)行卡爾曼濾波的update過程,更新track的狀態(tài)mean和不確定性covariance。 - 更新track的
features屬性:將本次新增的外觀特征向量添加到track的features屬性中,self.features.append(detection.feature)。 - 更新
self.hits+=1:表明連續(xù)匹配次數(shù)+1 - 更新
self.time_since_update=0:表示連續(xù)0次未匹配上 - 更新track的
state:如果track的狀態(tài)為不確定態(tài)并且連續(xù)_n_init次匹配上(track.state==tentative and self.hits>self._n_init),將track的state變?yōu)?code>confirmed。
(3)track.mark_missed
將符合條件的track的state標記為deleted,主要有兩種情況。
- 參與的track對象:
unmatched_track。 - 如果track的state為
tentative(不確定態(tài)),將track的sate變?yōu)?code>deleted。 - 如果track的
self.time_since_update>max_age,將track的state標記為deleted。
(4)_initiate_track
初始化新的track。主要有兩個部分:
- 參與對象:
unmatched_detections。對于沒有匹配上的detection,認為是新出現(xiàn)的track - 卡爾曼濾波初始化:初始化卡爾曼濾波器的運動方程矩陣
self._motion_mat和狀態(tài)轉移方程矩陣self._update_mat。 - 創(chuàng)建新的track對象,添加到
tracker.tracks中
(5)刪除track
在tracker.tracks刪除state為deleted的track。
(6)self.metric.partial_fit
更新在級聯(lián)匹配中,用于和新的detection的外觀特征向量計算余弦距離的track的外觀特征向量(self.budget,默認最新的100個)。
- 參與對象:
confirmed狀態(tài)的track - 更新
self.metric.samples:dict格式,key為track id,value為外觀特征向量,value只保存最近的self.budget條。
4、Output
state為confirmed的track的location及其track id。源碼中是Track對象是不包含類別的,因此輸出的track也是沒有類別的,在deepsort看來不同的track就是不同的目標對象,不區(qū)分類別。
需要注意:
輸出的是confirmed的track,track轉換為confirmed條件是需要連續(xù)_n_init使用iou匹配策略匹配上detection。
卡爾曼濾波過程理論結合代碼
KF精髓在于Kalman gain. Kalman gain本質上是weighted average 的 weight。你越相信你的prediction,uncertainty越小,observation的weight就越??;反之,你越不相信你的prediction,uncertainty越大,observation的weight越大。舉個例子:在一個陌生地方開車,你越不自信的時候,越信賴你的觀察(路標、問路等等)你在熟悉的地方開車時越信賴自己的直覺。而觀測噪聲和測量噪聲分別是對模型和測量不確定性的定量描述。
1、流程

- predict,通過運動學方程
x=vt,由Xt預測得到Xt+1的預測狀態(tài)mean和convariance - 通過狀態(tài)轉移方程,將預測狀態(tài)轉換到觀測狀態(tài)。得到觀測狀態(tài)的
mean和convariance。對于mean來說,在deepsort中其實直接有location了,因此就是簡單的單位矩陣。但是在其他場景中需要將多個復雜的傳感器的值轉換為我們的需要的測量值。 - 更新卡爾曼增益
gain - 測量狀態(tài)和預測的測量狀態(tài)之差,稱為測量過程的革新或者殘余,結合卡爾曼增益求到新的
mean - 利用卡爾曼增益,更新
covariance
2、Predict過程
返回的mean, covariance分別對應流程圖中Prediction中的和
def predict(self, mean, covariance):
"""Run Kalman filter prediction step.
Parameters
----------
mean : ndarray
The 8 dimensional mean vector of the object state at the previous
time step.
covariance : ndarray
The 8x8 dimensional covariance matrix of the object state at the
previous time step.
Returns
-------
(ndarray, ndarray)
Returns the mean vector and covariance matrix of the predicted
state. Unobserved velocities are initialized to 0 mean.
"""
std_pos = [
self._std_weight_position * mean[3],
self._std_weight_position * mean[3],
1e-2,
self._std_weight_position * mean[3]]
std_vel = [
self._std_weight_velocity * mean[3],
self._std_weight_velocity * mean[3],
1e-5,
self._std_weight_velocity * mean[3]]
motion_cov = np.diag(np.square(np.r_[std_pos, std_vel]))
# 求到流程圖中的Xk+1
mean = np.dot(self._motion_mat, mean)
# 求到流程圖中的Pk+1
covariance = np.linalg.multi_dot((
self._motion_mat, covariance, self._motion_mat.T)) + motion_cov
return mean, covariance
3、Update過程
(1)project函數(shù)
project函數(shù)完成了狀態(tài)轉移的過程,在流程圖Correction的中就是狀態(tài)轉移方程。返回的
mean, covariance + innovation_cov分別是和
def project(self, mean, covariance):
"""Project state distribution to measurement space.
Parameters
----------
mean : ndarray
The state's mean vector (8 dimensional array).
covariance : ndarray
The state's covariance matrix (8x8 dimensional).
Returns
-------
(ndarray, ndarray)
Returns the projected mean and covariance matrix of the given state
estimate.
"""
std = [
self._std_weight_position * mean[3],
self._std_weight_position * mean[3],
1e-1,
self._std_weight_position * mean[3]]
innovation_cov = np.diag(np.square(std))
mean = np.dot(self._update_mat, mean)
covariance = np.linalg.multi_dot((
self._update_mat, covariance, self._update_mat.T))
return mean, covariance + innovation_cov
(2)update函數(shù)
這里提一下cholesky分解,在求逆矩陣很復雜時可以使用此方法簡化,在計算卡爾曼增益和馬氏距離時都有應用。由于本文中提到的協(xié)方差矩陣都是實對稱正定矩陣,所以可以應用cholesky分解成下三角矩陣,然后轉化為求非齊次線性方程組的問題:
計算馬氏距離時:
# 求LT
cholesky_factor = np.linalg.cholesky(covariance)
# x-y
d = measurements - mean
# 解非齊次線性方程組求Z
z = scipy.linalg.solve_triangular(
cholesky_factor, d.T, lower=True, check_finite=False,
overwrite_b=True)
squared_maha = np.sum(z * z, axis=0)
計算卡爾曼增益時:
- cholesky分解成下三角矩陣
chol_factor, lower = scipy.linalg.cho_factor(projected_cov, lower=True, check_finite=False) - 求解非齊次線性方程組:
,
是卡爾曼增益的轉置矩陣。
kalman_gain = scipy.linalg.cho_solve( (chol_factor, lower), np.dot(covariance, self._update_mat.T).T, check_finite=False).T
def update(self, mean, covariance, measurement):
"""Run Kalman filter correction step.
Parameters
----------
mean : ndarray
The predicted state's mean vector (8 dimensional).
covariance : ndarray
The state's covariance matrix (8x8 dimensional).
measurement : ndarray
The 4 dimensional measurement vector (x, y, a, h), where (x, y)
is the center position, a the aspect ratio, and h the height of the
bounding box.
Returns
-------
(ndarray, ndarray)
Returns the measurement-corrected state distribution.
"""
# 狀態(tài)轉移,由測量空間轉換到觀測空間
projected_mean, projected_cov = self.project(mean, covariance)
chol_factor, lower = scipy.linalg.cho_factor(
projected_cov, lower=True, check_finite=False)
kalman_gain = scipy.linalg.cho_solve(
(chol_factor, lower), np.dot(covariance, self._update_mat.T).T,
check_finite=False).T
# 測量變量和預測值之差,稱為測量過程的革新或者殘余
innovation = measurement - projected_mean
new_mean = mean + np.dot(innovation, kalman_gain.T)
new_covariance = covariance - np.linalg.multi_dot((
kalman_gain, projected_cov, kalman_gain.T))
return new_mean, new_covariance