[论文阅读笔记16]BoT-SORT论文公式推导与代码解读

38 篇文章 38 订阅
32 篇文章 8 订阅

论文地址: BoT-SORT: Robust Associations Multi-Pedestrian Tracking
和OCSORT(解读见OCSORT)一样, 都是对Kalman滤波进行的改进. OCSORT是针对观测(检测器)不可靠时Kalman预测方差变大的问题, 对轨迹做了平滑. 而BoT-SORT是针对相机运动的问题, 加入了相机运动补偿. 也就是除了利用Kalman预测目标的新位置之外, 还利用稀疏光流(也就是提取目标之外的画面中的关键点, 来获得两帧之间画面的移动, 进而补偿Kalman得到的结果)


1. Kalman滤波

Kalman滤波遵循如下的更新步骤和预测步骤:

预测步:
x ^ t ∣ t − 1 = F t x ^ t − 1 ∣ t − 1 + B t u t P t ∣ t − 1 = F t P t − 1 ∣ t − 1 F t + Q \hat{x}_{t|t-1}=F_t\hat{x}_{t-1|t-1}+B_tu_t \\ P_{t|t-1}=F_tP_{t-1|t-1}F_t+Q x^tt1=Ftx^t1∣t1+BtutPtt1=FtPt1∣t1Ft+Q

更新步:
x ^ t ∣ t = x ^ t ∣ t − 1 + K t ( z t − H t x ^ t ∣ t − 1 ) K t = P t ∣ t − 1 H t T H t T P t ∣ t − 1 H t + R t P t ∣ t = P t ∣ t − 1 − K t H t P t ∣ t − 1 \hat{x}_{t|t}=\hat{x}_{t|t-1}+K_t(z_t-H_t\hat{x}_{t|t-1})\\ K_t=\frac{P_{t|t-1}H_t^T}{H_t^TP_{t|t-1}H_t+R_t}\\ P_{t|t}=P_{t|t-1}-K_tH_tP_{t|t-1} x^tt=x^tt1+Kt(ztHtx^tt1)Kt=HtTPtt1Ht+RtPtt1HtTPtt=Ptt1KtHtPtt1

其中 Q Q Q, R R R分别为输入(观测)和状态的噪声的协方差矩阵.

2. 状态变量的选择

在SORT中, 状态变量为[中心点, 面积, 高宽比, 速度, 面积变化率], 即 x = [ x c , y c , a , s , x c ˙ , y c ˙ , s ˙ ] x=[x_c, y_c, a ,s, \dot{x_c}, \dot{y_c}, \dot{s}] x=[xc,yc,a,s,xc˙,yc˙,s˙], 从DeepSORT以后, 状态变量基本采用的是 x = [ x c , y c , a , h , x c ˙ , y c ˙ , a ˙ , h ˙ ] x=[x_c, y_c, a, h, \dot{x_c}, \dot{y_c}, \dot{a}, \dot{h}] x=[xc,yc,a,h,xc˙,yc˙,a˙,h˙].

在BoT-SORT中, 作者简单地将状态变量改成了xywh及其导数, 即: x = [ x c , y c , w , h , x c ˙ , y c ˙ , w ˙ , h ˙ ] x=[x_c, y_c, w, h, \dot{x_c}, \dot{y_c}, \dot{w}, \dot{h}] x=[xc,yc,w,h,xc˙,yc˙,w˙,h˙], 类似地, 观测器的输入变量为 z = [ z x c , z y c , z w , z h ] z=[z_{xc}, z_{yc}, z_w, z_h] z=[zxc,zyc,zw,zh].

论文(1)(2)式

论文(3)(4)式, 关于噪声协方差矩阵的定义暂时没看懂, 看懂后补充

从消融实验的结果看, 按照这种方式更改状态变量MOTA, IDF1和HOTA指标大约有0.1 0.2左右的提升

3. 相机运动补偿

这是BoT-SORT提出的核心思想.

由于相机运动, 可能会造成匹配的不准确. 然而我们缺乏相机运动的先验信息(例如导航等), 因此, 相邻两帧之间的图像配准(image registration)可以近似相机运动在图像二维平面上的投影.

这里可以理解为, 相机运动等同于坐标系的变换, 因此目标的位置, 运动方向等我们需要重新投影到新坐标系中, 因此我们需要求解坐标系的变换矩阵. 如下图所示:

在这里插入图片描述
因此, 利用 RANSAC算法得到平面坐标变化的仿射矩阵, 描述2维平面旋转变化矩阵为 M ∈ R 2 × 2 M\in\mathbb{R}^{2\times 2} MR2×2, 平移变化向量为 T ∈ R 2 T\in\mathbb{R}^{2} TR2, 定义仿射矩阵
A = [ M T ] ∈ R 2 × 3 A = [M \quad T]\in\mathbb{R}^{2\times 3} A=[MT]R2×3

论文(5)式

假定Kalman预测后的结果为 x ^ t ∣ t − 1 = [ x c , y c , w , h , x c ˙ , y c ˙ , w ˙ , h ˙ ] T \hat{x}_{t|t-1}=[x_c, y_c, w, h, \dot{x_c}, \dot{y_c}, \dot{w}, \dot{h}]^T x^tt1=[xc,yc,w,h,xc˙,yc˙,w˙,h˙]T, 我们需要对中心点和高宽都应用仿射变换, 为了用一次矩阵乘法实现, 构造:

M k ∣ k − 1 ′ = d i a g { M , M , M , M } ∈ R 8 × 8 T k ∣ k − 1 ′ = [ T , 0 , 0 , 0 , 0 , 0 , 0 ] ∈ R 8 M'_{k|k-1}=diag\{M, M, M, M\}\in\mathbb{R}^{8\times 8}\\ T'_{k|k-1}=[T ,0, 0, 0, 0, 0, 0]\in\mathbb{R}^{8} Mkk1=diag{M,M,M,M}R8×8Tkk1=[T,0,0,0,0,0,0]R8

因此:

x ^ t ∣ t − 1 ′ = M k ∣ k − 1 ′ x ^ t ∣ t − 1 + T k ∣ k − 1 ′ \hat{x}_{t|t-1}'=M'_{k|k-1}\hat{x}_{t|t-1}+T'_{k|k-1} x^tt1=Mkk1x^tt1+Tkk1

x ^ t ∣ t − 1 \hat{x}_{t|t-1} x^tt1的协方差矩阵变为(随机变量应用线性变换, 方差为系数平方关系):
P t ∣ t − 1 ′ = M k ∣ k − 1 ′ P t ∣ t − 1 M k ∣ k − 1 ′ T P_{t|t-1}'=M'_{k|k-1}P_{t|t-1}M^{'T}_{k|k-1} Ptt1=Mkk1Ptt1Mkk1T

论文(5)~(8)式

随后利用修正后的 x ^ t ∣ t − 1 ′ \hat{x}_{t|t-1}' x^tt1 P t ∣ t − 1 ′ P_{t|t-1}' Ptt1再进行更新步.

论文(9)式

在实现中, 我们将画面中的目标扣去, 在其余的部分检测关键点, 来计算仿射矩阵:(tracker\gmc.py line120)

		# find the keypoints
        mask = np.zeros_like(frame)
        # mask[int(0.05 * height): int(0.95 * height), int(0.05 * width): int(0.95 * width)] = 255
        mask[int(0.02 * height): int(0.98 * height), int(0.02 * width): int(0.98 * width)] = 255
        if detections is not None:
            for det in detections:
                tlbr = (det[:4] / self.downscale).astype(np.int_)
                mask[tlbr[1]:tlbr[3], tlbr[0]:tlbr[2]] = 0

        keypoints = self.detector.detect(frame, mask)

        # compute the descriptors
        keypoints, descriptors = self.extractor.compute(frame, keypoints)

随后将相邻两帧的关键点进行匹配

		# Match descriptors.
        knnMatches = self.matcher.knnMatch(self.prevDescriptors, descriptors, 2)

如果关键点的个数足够, 就计算仿射矩阵

		 # Find rigid matrix
        if (np.size(prevPoints, 0) > 4) and (np.size(prevPoints, 0) == np.size(prevPoints, 0)):
            H, inliesrs = cv2.estimateAffinePartial2D(prevPoints, currPoints, cv2.RANSAC)

以上部分依赖于cv2库. 得到仿射矩阵后, 修正Kalman的结果(tracker\bot_sort.py line68):

def multi_gmc(stracks, H=np.eye(2, 3)):
        if len(stracks) > 0:
            multi_mean = np.asarray([st.mean.copy() for st in stracks])
            multi_covariance = np.asarray([st.covariance for st in stracks])

            R = H[:2, :2]
            R8x8 = np.kron(np.eye(4, dtype=float), R)
            t = H[:2, 2]

            for i, (mean, cov) in enumerate(zip(multi_mean, multi_covariance)):
                mean = R8x8.dot(mean)
                mean[:2] += t
                cov = R8x8.dot(cov).dot(R8x8.transpose())

                stracks[i].mean = mean
                stracks[i].covariance = cov

4. 匹配策略

在匹配过程中, 利用指数滑动平均来平衡过去和当前的外观特征, 随后将运动特征和外观特征线性组合作为cost matrix, 如下两式所示:
在这里插入图片描述

在这里插入图片描述

对于外观和IoU都相似的目标, 给予更小的cost, 否则设为1, 并以此更新C中的元素:

在这里插入图片描述
最终结果如下表所示:

在这里插入图片描述

  • 9
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
以下是一个使用YOLOv4和SORT(Simple Online and Realtime Tracking)算法进行目标检测和跟踪的示例代码,您可以参考: ```python import cv2 import numpy as np from sort import * # 加载YOLOv4模型 net = cv2.dnn.readNet("yolov4.weights", "yolov4.cfg") # 获取模型输出层的名称 layer_names = net.getLayerNames() output_layers = [layer_names[i[0] - 1] for i in net.getUnconnectedOutLayers()] # 加载类别标签 classes = [] with open("coco.names", "r") as f: classes = [line.strip() for line in f.readlines()] # 初始化跟踪器 tracker = Sort() # 打开视频文件 video = cv2.VideoCapture("test.mp4") while True: ret, frame = video.read() if not ret: break height, width, channels = frame.shape # 图像预处理 blob = cv2.dnn.blobFromImage(frame, 0.00392, (416, 416), (0, 0, 0), True, crop=False) # 将图像输入到网络中进行目标检测 net.setInput(blob) outs = net.forward(output_layers) class_ids = [] confidences = [] boxes = [] # 解析模型输出 for out in outs: for detection in out: scores = detection[5:] class_id = np.argmax(scores) confidence = scores[class_id] if confidence > 0.5: center_x = int(detection[0] * width) center_y = int(detection[1] * height) w = int(detection[2] * width) h = int(detection[3] * height) x = int(center_x - w / 2) y = int(center_y - h / 2) boxes.append([x, y, w, h]) confidences.append(float(confidence)) class_ids.append(class_id) # 应用非极大值抑制获取最终的检测结果 indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4) # 更新跟踪器 detections = [] for i in range(len(boxes)): if i in indexes: x, y, w, h = boxes[i] label = str(classes[class_ids[i]]) detections.append([x, y, x+w, y+h]) trackers = tracker.update(np.array(detections)) for d in trackers: x1, y1, x2, y2, track_id = d.astype(np.int32) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, str(track_id), (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2) cv2.imshow("Tracking", frame) if cv2.waitKey(1) == ord('q'): break video.release() cv2.destroyAllWindows() ``` 请注意,这只是一个示例代码,您需要根据您的实际环境和需求进行适当的修改和调整。此外,您需要确保已经下载了YOLOv4的权重文件(yolov4.weights)、配置文件(yolov4.cfg)和类别标签文件(coco.names)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值