1. はじめに
1.1 以前のカルマン法の問題点:
- 1. 長期的な動きの線形推定は非常に不正確になる可能性があります。
- 2. カルマン フィルター パラメーターを更新するために利用できる測定値がない場合、標準的な方法では、事後更新について先験的な状態推定を信頼することになります。これにより、時間の経過とともに誤差が蓄積されます。
1.2 仮定に基づく
追跡ターゲットは一定の時間間隔内で一定の速度を持つと仮定され、これを線形運動の仮定と呼びます。
1.3 SORT の制限
(1) 状態推定ノイズに対する感度: オブジェクトの動きを線形に近似するには高いフレーム レートが重要ですが、状態推定ノイズに対するモデルの感度も増幅します。(騒音と物体の動きは同じ大きさです)
(2) 時間の経過に伴う誤差の蓄積: KF の更新フェーズで利用可能な観測値がない場合、KF の状態推定によるノイズは時間の経過とともに蓄積されます。
(3) 推定中心: SORT は、観察ではなく状態推定を通じてオブジェクトの軌道を延長するように設計されています。
1.4 本作の革新点
(1)オブジェクトの状態観察を使用して、軌道喪失時の累積誤差を削減するモジュールが設計されています。
- 従来の予測および更新ステージに加えて、蓄積されたエラーを修正するために「再更新」ステージが追加されます。再更新は、一定期間の追跡が行われなかった後に観測に関連付けられて追跡が再アクティブ化されるとトリガーされます。再更新では、履歴タイム ステップのダミー観測値を使用して、エラーの蓄積を防ぎます。
- 仮想観測は、追跡を解除する前の最後の観測と、軌道を再アクティブ化する最新の観測によって固定された軌道から生成されます。私たちはこれを観測中心の再更新 (ORU) と名付けています。
(2)観測中心(OCM)の運動量を考慮する。
- 関連するコスト マトリクスに追跡の一貫性を組み込む
2.OCソート
2.1 観測中心の再更新 (ORU)
追跡されなくなる前に確認された最後の観測値は として記録され、再関連付けをトリガーした観測値はとして記録されます。
予測 - 再更新サイクルは、 上記の軌跡に沿って実行されます。再更新の手順は次のとおりです。
2.2 観測中心の勢い (OCM)
推定値の代わりに観測値を使用することで、推定値の誤差増幅の問題が回避されます。
関連するコスト行列には、状態観測値の一貫性項が導入され、次のように表現されます。
- 負のペアごとの IoU (和集合に対する交差) を計算します。
- ①既存の軌道上の 2 つの観測値を接続する( ) と、② 軌道上の過去の観測値と新しい観測値を接続する(方向 )の間の整合性を計算します。
-
Cv にはすべてのペアが含まれます。私たちの実装では、動きの方向をラジアンで計算します。つまり、(u1, v1) と (u2, v2) は 2 つの異なるタイム ステップでの観測値です。
線形運動モデルでは、方向推定ノイズの大きさは 2 つの観測点間の時間差、つまり Δt に負の相関関係があります。これは、Δt を増加させると、θ の低ノイズ推定値が達成できることを示しています。ただし、線形運動の仮定は通常、Δt が十分に小さい場合にのみ当てはまります。したがって、Δt の選択にはトレードオフが必要です。
2.3 ヒューリスティックな観察中心のリカバリ (OCR)
OCR は、最後に観察された不一致の軌跡を不一致の観測と関連付けるための 2 回目の試みを開始します。
3. コード
3.1 全体的なコード構造
全体的なコード アーキテクチャはバイトトラックに似ています
3.2 OCM
動きの方向は相関関係としてコスト マトリックスに導入されます。
過去の運動方向と現在観測されている運動方向の間の円弧の差を表します。
3.2.1 アソシエーション関数アソシエート
3.2.1.1 受信パラメータの意味
#dets:高置信度检测结果
remain_inds = scores > self.det_thresh
dets = dets[remain_inds]
#trks:之前的跟踪轨迹
#iou_threshold:iou匹配阈值
#velocities:之前所有轨迹的跟踪结果
velocities = np.array([trk.velocity if trk.velocity is not None else np.array((0, 0)) for trk in self.trackers])
#k_observations:最新的观测
#k_previous_obs用于返回最新age最大的观测数据
k_observations = np.array([k_previous_obs(trk.observations, trk.age, self.delta_t) for trk in self.trackers])
#inertia:权重
matched, unmatched_dets, unmatched_trks = associate(
dets, trks, self.iou_threshold, velocities, k_observations, self.inertia)
3.2.1.2 アソシエート機能
(コードは少し短くなります)
(1)角度差分コスト
Y, X = speed_direction_batch(detections, previous_obs)
def speed_direction_batch(dets, tracks):
tracks = tracks[..., np.newaxis]
CX1, CY1 = (dets[:,0] + dets[:,2])/2.0, (dets[:,1]+dets[:,3])/2.0
CX2, CY2 = (tracks[:,0] + tracks[:,2]) /2.0, (tracks[:,1]+tracks[:,3])/2.0
dx = CX1 - CX2
dy = CY1 - CY2
norm = np.sqrt(dx**2 + dy**2) + 1e-6
dx = dx / norm
dy = dy / norm
return dy, dx # size: num_track x num_det
観測データと軌跡データからx、y方向の画素位置の変化を取得し、出力データを正規化します。
inertia_Y, inertia_X = velocities[:,0], velocities[:,1]
inertia_Y = np.repeat(inertia_Y[:, np.newaxis], Y.shape[1], axis=1)
inertia_X = np.repeat(inertia_X[:, np.newaxis], X.shape[1], axis=1)
X、Y方向の速度変化を取得します
diff_angle_cos = inertia_X * X + inertia_Y * Y
、渡される inertia_X、X、inertia_Y、Y はすべて正規化されたデータです
(2)iou_matrix
iou_matrix コスト行列の計算は新しいものではありません。
def iou_batch(bboxes1, bboxes2):
"""
From SORT: Computes IOU between two bboxes in the form [x1,y1,x2,y2]
"""
bboxes2 = np.expand_dims(bboxes2, 0)
bboxes1 = np.expand_dims(bboxes1, 1)
xx1 = np.maximum(bboxes1[..., 0], bboxes2[..., 0])
yy1 = np.maximum(bboxes1[..., 1], bboxes2[..., 1])
xx2 = np.minimum(bboxes1[..., 2], bboxes2[..., 2])
yy2 = np.minimum(bboxes1[..., 3], bboxes2[..., 3])
w = np.maximum(0., xx2 - xx1)
h = np.maximum(0., yy2 - yy1)
wh = w * h
o = wh / ((bboxes1[..., 2] - bboxes1[..., 0]) * (bboxes1[..., 3] - bboxes1[..., 1])
+ (bboxes2[..., 2] - bboxes2[..., 0]) * (bboxes2[..., 3] - bboxes2[..., 1]) - wh)
return(o)
(3) マッチングと対応するデータ配信
3.3 ゴールド
3.3.1 時間の計算
OCsort では、実際に時刻は渡されませんが、データのシリアル番号に基づいて時刻が計算されます。
indices = np.where(np.array(occur)==0)[0]
index1 = indices[-2]
index2 = indices[-1]
time_gap = index2 - index1
3.3.2 仮想パスの生成
3.3.2.1 タイムステップごとの変化
追跡を解除する前に確認された最後の観測と、再関連付けをトリガーした観測との間に経過したタイム ステップを取得することにより、各タイム ステップでの変化を計算します。
time_gap = index2 - index1
dx = (x2-x1)/time_gap
dy = (y2-y1)/time_gap
dw = (w2-w1)/time_gap
dh = (h2-h1)/time_gap
3.3.2.2 等速運動による仮想軌道の生成
for i in range(index2 - index1):
x = x1 + (i+1) * dx
y = y1 + (i+1) * dy
w = w1 + (i+1) * dw
h = h1 + (i+1) * dh
s = w * h
r = w / float(h)
new_box = np.array([x, y, s, r]).reshape((4, 1))
self.update(new_box)
if not i == (index2-index1-1):
self.predict()
3.3.2 ORUトリガー
(1) 現在のカルマンパラメータを保存するための観測データが存在しない
if z is None:
if self.observed:
"""
Got no observation so freeze the current parameters for future
potential online smoothing.
"""
self.freeze()
self.observed = False
self.z = np.array([[None]*self.dim_z]).T
self.x_post = self.x.copy()
self.P_post = self.P.copy()
self.y = zeros((self.dim_z, 1))
return
(2) リトラック
if not self.observed:
"""
Get observation, use online smoothing to re-update parameters
"""
self.unfreeze()