SORT-1 项目配置运行-WINDOWS
SORT-2 卡尔曼滤波推导和示例
SORT-3 匈牙利算法和SORT类
SORT-4 SORT项目代码解析
本项目地址
if __name__ == '__main__'# 设置交互模式、参数、文件路径|# 创建 SORT 对象mot_tracker = Sort(max_age=, min_hits=,iou_threshold) |# "循环"读取每一个文件(每一帧)for frame in range(...):# 参数准备frame += 1 # 帧数+1dets[] = .. # 存放 检测框total_frames += 1|# 获取检测框dets[] = ...|# 用检测框 dets 做一次更新; 返回满足条件可显示的trackers([x1,y1,x2,y2,ID])trackers = mot_tracker.update(dets)|# 作画for d in trakcers:fig.canvas.flush_events()...
class Sort(object):def __init__(self, max_age=1, min_hits=3, iou_threshold=0.3):# Sets key parameters for SORTself.max_age = max_age # tracker 的最大寿命self.min_hits = min_hits # 最小匹配次数self.iou_threshold = iou_threshold # 匹配阈值self.trackers = [] # 存放跟踪器类 KalmanBoxTracker 对象self.frame_count = 0def update(self, dets=np.empty((0, 5))):# 使用 检测框 做一次更新; dets:[x1,y1,x2,y2,ID]# 0.参数准备trks = np.zeros(len(self,trackers),5) # to_del = [] # 待删除的 预测框ret = [] # 可显示的 tracker|# 1.现有的跟踪器全部做一次更新预测,获得预测框for t,trk in enumerate(trks):pos = self.trackers[t].predict()[0] # 已有跟踪器上做一次预测trk[:]=[pos[0], pos[1], pos[2], pos[3], 0] # 获得 "预测框" 的坐标|# 去除非法的预测框trks = np.ma.compress_rows(np.ma.masked_invalid(trks))|# 删除 to_del 中待删除的 trackerfor t in reversed(to_del):self.trackers.pop(t)|# 2.预测框和 检测框 做一次匹配matched, unmatched_dets, unmatched_trks = \associate_detections_to_trackers(dets,trks, self.iou_threshold)|# 3.根据匹配结果 分别更新三类 trakcer# 3.1 更新匹配成功的跟踪器for m in matched:self.trackers[m[1]].update(dets[m[0], :])# 3.2 为未匹配到的检测框创建一个新的跟踪器for i in unmatched_dets:trk = KalmanBoxTracker(dets[i,:])self.trackers.append(trk)|# 3.3 根据跟踪器的存在时间、匹配次数等决定是否加入显示列表
卡尔曼滤波器系统如下图所示。
输入:
状态量初始值 xk−1{x_{k-1}}xk−1和状态量协方差初始值 Pk−1{P_{k-1}}Pk−1
卡尔曼滤波器中的参数设置: 状态转移矩阵 FFF,映射矩阵(或称测量矩阵)HHH等
迭代输入每一步的 观测值 ZkZ_kZk
输出:
每一步的状态预测 xkx_kxk
在SORT项目中,观测量 来自 YOLO 的检测结果 bbox([x1,y1,x2,y2])bbox([x1,y1,x2,y2])bbox([x1,y1,x2,y2]) 的转化形式 Z([u,v,s,r])Z([u,v,s,r])Z([u,v,s,r])。其中 [u,v][u,v][u,v] 是中心点的坐标,sss 是面积,rrr 是面积宽高比。这样四个量将是独立的变量,减弱了 bbox 四个变量之间的相关性。
由此可以定义状态量 [x,y,s,r,x˙,y˙,s˙]T[x,y,s,r,\dot x,\dot y,\dot s]^T[x,y,s,r,x˙,y˙,s˙]T,其中 x˙,y˙,s˙\dot x,\dot y,\dot sx˙,y˙,s˙ 分别是 x,y,sx,y,sx,y,s 的一阶微分 dx,dy,dsdx,dy,dsdx,dy,ds。
在线性模型中,可将其视为 距离-速度 模型,即 xk=xk−1+dxdtx_k=x_{k-1}+dxdtxk=xk−1+dxdt,由此可得状态转移矩阵 FFF:
F=[1,0,0,0,1,0,00,1,0,0,0,1,00,0,1,0,0,0,10,0,0,1,0,0,00,0,0,0,1,0,00,0,0,0,0,1,00,0,0,0,0,0,1]F=\begin{bmatrix}1,0,0,0,1,0,0\\0,1,0,0,0,1,0\\0,0,1,0,0,0,1\\0,0,0,1,0,0,0\\0,0,0,0,1,0,0\\0,0,0,0,0,1,0\\0,0,0,0,0,0,1 \end{bmatrix}F=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡1,0,0,0,1,0,00,1,0,0,0,1,00,0,1,0,0,0,10,0,0,1,0,0,00,0,0,0,1,0,00,0,0,0,0,1,00,0,0,0,0,0,1⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤
xk=F⋅xk−1=[1,0,0,0,1,0,00,1,0,0,0,1,00,0,1,0,0,0,10,0,0,1,0,0,00,0,0,0,1,0,00,0,0,0,0,1,00,0,0,0,0,0,1][xysrx˙y˙s˙]x_k=F\cdot x_{k-1}=\begin{bmatrix}1,0,0,0,1,0,0\\0,1,0,0,0,1,0\\0,0,1,0,0,0,1\\0,0,0,1,0,0,0\\0,0,0,0,1,0,0\\0,0,0,0,0,1,0\\0,0,0,0,0,0,1 \end{bmatrix}\begin{bmatrix} x\\y\\s\\r\\\dot x\\\dot y\\\dot s\end{bmatrix}xk=F⋅xk−1=⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡1,0,0,0,1,0,00,1,0,0,0,1,00,0,1,0,0,0,10,0,0,1,0,0,00,0,0,0,1,0,00,0,0,0,0,1,00,0,0,0,0,0,1⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤⎣⎢⎢⎢⎢⎢⎢⎢⎢⎡xysrx˙y˙s˙⎦⎥⎥⎥⎥⎥⎥⎥⎥⎤
即可得到 xk=xk−1+x˙x_k=x_{k-1}+\dot xxk=xk−1+x˙ (y,sy,sy,s同理),x˙=x˙\dot x=\dot xx˙=x˙ (r,y˙r,\dot yr,y˙同理)。这就是卡尔曼滤波器是线性滤波器原因!
class KalmanBoxTracker(object):count = 0 # 跟踪器的存在数量,staticdef __init__(self,bbox):# 卡尔曼滤波器的主要参数设置self.kf = KalmanFilter(dim_x=7, dim_z=4) # 定义状态空间和观测空间维度,解释见下self.kf.F = np.array([[1,0,0,0,1,0,0],[0,1,0,0,0,1,0],[0,0,1,0,0,0,1], [0,0,0,1,0,0,0], [0,0,0,0,1,0,0],[0,0,0,0,0,1,0],[0,0,0,0,0,0,1]])self.kf.H = np.array([[1,0,0,0,0,0,0],[0,1,0,0,0,0,0],[0,0,1,0,0,0,0], [0,0,0,1,0,0,0]])self.kf.R[2:,2:] *= 10.self.kf.P[4:,4:] *= 1000. self.kf.P *= 10.self.kf.Q[-1,-1] *= 0.01self.kf.Q[4:,4:] *= 0.01self.kf.x[:4] = convert_bbox_to_z(bbox) # [u,v,s,r,u',v',s']self.time_since_update = 0self.id = KalmanBoxTracker.countKalmanBoxTracker.count += 1self.history = []self.hits = 0self.hit_streak = 0self.age = 0
def update(self,bbox):# 用检测框更新 tracker(kalman filter update)self.time_since_update = 0 # 自第一次 update(创建) 的匹配次数self.history = [] # 保存历次的self.hits += 1self.hit_streak += 1 # 与检测框成功匹配的次数self.kf.update(convert_bbox_to_z(bbox)) # 调用库函数的 updatedef predict(self):if((self.kf.x[6]+self.kf.x[2])<=0):self.kf.x[6] *= 0.0self.kf.predict()self.age += 1if(self.time_since_update>0):self.hit_streak = 0self.time_since_update += 1self.history.append(convert_x_to_bbox(self.kf.x))return self.history[-1] # 返回最后(新)一个 bbox
def associate_detections_to_trackers(detections,trackers,iou_threshold = 0.3):