yolov5+deepsort跟踪学习笔记

前段时间因为项目需要,需要使用yolo+deepsort来进行目标跟踪,此博客作为学习笔记与大家共勉,欢迎大家批评指正。接下来,将从整体流程以及各个小模块来进行介绍。

整体流程图

本文主要讲解deepsort流程,下图为yolo+deepsort主要模块的流程图:
yolov5+deepsort跟踪学习笔记_第1张图片

yolo预测的位置信息作为观测值输入到deepsort后,卡尔曼滤波先判断是否存在track,如果存在对其位置信息进行先验概率预测,得到先验预测 x t − 1 − x^{-}_{t-1} xt1,先验预测 x t − 1 − x^{-}_{t-1} xt1在匹配模块先后进行级联匹配和IOU匹配,最终得到匹配成功列表,包含先验预测track、观测值detection,以及没有匹配到的track和detection,在卡尔曼更新模块,对匹配成功的元素进行后验预测,得到最终的修正坐标,更新卡尔曼增益、协方差矩阵、feature set等参数,完成一帧检测,一直循环上述操作直至处理完全部视频。

卡尔曼滤波预测

对t-1时刻的track进行先验预测,使用卡尔曼滤波公式的公式1与公式2。公式如下:
x t − = F x t − 1 + B u t − 1 x^{-}_{t}=Fx_{t-1}+Bu_{t-1} xt=Fxt1+But1 公式1
P t − = F P t − 1 F T + Q P^{-}_{t}=FP_{t-1}F^{T}+Q Pt=FPt1FT+Q 公式2
其中公式1中的 u t − 1 u_{t-1} ut1 为表示可选的控制输入 B B B表示控制输入到当前状态的转换矩阵, B u t − 1 Bu_{t-1} But1 一般在实际使用中忽略,因此在实际代码公式1形如 x t − = F x t − 1 x^{-}_{t}=Fx_{t-1} xt=Fxt1 x t − 1 x_{t-1} xt1为t-1时刻track的状态信息矩阵, F F F为t-1时刻到t时刻的状态的转移矩阵, x t − x^{-}_{t} xt为先验预测。其中 x t − 1 x_{t-1} xt1为一个八维的长向量:[x,y,w,h,vx,vy,vw,vh],表示位置信息以及相应的速度信息, F F F 8 ∗ 8 8*8 88数值为1的对角阵的基础上进行

        for i in range(ndim):
            self._motion_mat[i, ndim + i] = dt

的矩阵(这里其实不是很明白为啥要进行这个操作)。

公式2中 P t − 1 P_{t-1} Pt1 P t − P^{-}_{t} Pt分别为t-1时刻 8 ∗ 8 8*8 88的协方差矩阵和其t时刻的先验预测。其中当track被初始化时,其协方差矩阵和均值矩阵由目标框的高度来生成和目标框坐标长宽信息生成。 Q Q Q为卡尔曼滤波器的运动估计误差,代表不确定程度。

此为卡尔曼预测代码
```python
    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]))

        mean = np.dot(self._motion_mat, mean)
        covariance = np.linalg.multi_dot((
            self._motion_mat, covariance, self._motion_mat.T)) + motion_cov

        return mean, covariance

track、detection匹配

先验预测得到后,需要进行track和detection匹配,deepsort使用了级联匹配+IOU匹配串联的策略,track根据状态分为Confirmed、Tentative以及Deleted类型。级联匹配只对Confirmed的track执行,根据其距离上一次匹配的时间距离长短进行分批次匹配,用来匹配的代价矩阵由特征的余弦相似度距离与马氏距离来构建,使用匈牙利算法对代价矩阵进行计算得到matched和unmatched。

级联匹配代码
def matching_cascade(
        distance_metric, max_distance, cascade_depth, tracks, detections,
        track_indices=None, detection_indices=None):
   
    if track_indices is None:
        track_indices = list(range(len(tracks)))
    if detection_indices is None:
        detection_indices = list(range(len(detections)))

    unmatched_detections = detection_indices
    matches = []
    for level in range(cascade_depth):
        if len(unmatched_detections) == 0:  # No detections left
            break

        track_indices_l = [
            k for k in track_indices
            if tracks[k].time_since_update == 1 + level
        ]
        if len(track_indices_l) == 0:  # Nothing to match at this level
            continue

        matches_l, _, unmatched_detections = \
            min_cost_matching(
                distance_metric, max_distance, tracks, detections,
                track_indices_l, unmatched_detections)
        matches += matches_l
    unmatched_tracks = list(set(track_indices) - set(k for k, _ in matches))
    return matches, unmatched_tracks, unmatched_detections
  • 匈牙利算法应用:
    对于len(track),len(det)的代价矩阵,使用row_indices, col_indices = linear_assignment(cost_matrix)得到相应索引后,根据其代价矩阵是否大于阈值来确定是否匹配成功。

Confirmed的track匹配完成后,将匹配失败的与Tentative的track组合成新的集进行IOU匹配。IOU匹配直接将所有track和detection由iou作为元素构建成IOU代价矩阵,使用匈牙利算法来进行匹配,匹配方法与级联匹配相同。

代价矩阵构建

上一小节中匹配依靠的标注就是代价矩阵,因此代价矩阵的构建是匹配过程中非常重要的一步。首先我们先来介绍级联匹配的代价矩阵,先看代码:

  • 级联匹配代价矩阵
        def gated_metric(tracks, dets, track_indices, detection_indices):
            features = np.array([dets[i].feature for i in detection_indices])
            targets = np.array([tracks[i].track_id for i in track_indices])
            cost_matrix = self.metric.distance(features, targets)# 通过最近邻计算出代价矩阵 cosine distance
            cost_matrix = linear_assignment.gate_cost_matrix(#2. 计算马氏距离,得到新的状态矩阵
                self.kf, cost_matrix, tracks, dets, track_indices,
                detection_indices)

            return cost_matrix

首先根据每个track以及detection对应的feature来计算余弦距离(余弦距离=1-余弦相似度),其中代价矩阵中的每一个元素为余弦距离。在得到第一个代价矩阵后,我们再通过马氏距离进行调整,如果代价矩阵中某一个元素的马氏距离大于阈值,则修改数值。修正完成后即为级联匹配最终的代价矩阵。

余弦距离计算
distances = _cosine_distance(x, y)
---------------------------------------
def _cosine_distance(a, b, data_is_normalized=False):

    if not data_is_normalized:
        a = np.asarray(a) / np.linalg.norm(a, axis=1, keepdims=True)
        #  np.linalg.norm 操作是求向量的范式,默认是L2范式,等同于求向量的欧式距离。
        b = np.asarray(b) / np.linalg.norm(b, axis=1, keepdims=True)
    return 1. - np.dot(a, b.T)
    

余弦相似度计算公式:
yolov5+deepsort跟踪学习笔记_第2张图片

马氏距离计算以及修正代价矩阵
def gate_cost_matrix(
        kf, cost_matrix, tracks, detections, track_indices, detection_indices,
        gated_cost=INFTY_COST, only_position=False):

    gating_dim = 2 if only_position else 4
    gating_threshold = kalman_filter.chi2inv95[gating_dim]
    measurements = np.asarray(
        [detections[i].to_xyah() for i in detection_indices])
    for row, track_idx in enumerate(track_indices):
        track = tracks[track_idx]
        gating_distance = kf.gating_distance(
            track.mean, track.covariance, measurements, only_position)
        cost_matrix[row, gating_distance > gating_threshold] = gated_cost
    return cost_matrix
  • IOU匹配代价矩阵
    IOU代价矩阵的构建比较简单,通过计算track和detection的IOU来得到代价矩阵中每个元素的数值。
IOU代价矩阵计算
iou_cost(tracks, detections, track_indices=None,
             detection_indices=None):

    if track_indices is None:
        track_indices = np.arange(len(tracks))
    if detection_indices is None:
        detection_indices = np.arange(len(detections))

    cost_matrix = np.zeros((len(track_indices), len(detection_indices)))
    for row, track_idx in enumerate(track_indices):
        if tracks[track_idx].time_since_update > 1:
            cost_matrix[row, :] = linear_assignment.INFTY_COST
            continue

        bbox = tracks[track_idx].to_tlwh()
        candidates = np.asarray(
            [detections[i].tlwh for i in detection_indices])
        cost_matrix[row, :] = 1. - iou(bbox, candidates)#计算1-IOU 得到代价矩阵
    return cost_matrix

卡尔曼滤波更新

经过匹配之后得到了matched、unmatched_track和unmatched_det。依次对匹配成功的track进行修正、对匹配事变的tack进行状态更新、对未匹配的detection将其转为track、对匹配成功的track更新特征集。
卡尔曼更新用到公式3、公式4、公司5:
K t = P t − C T C P t − C T + R K_{t}={\frac{P_{t}^{-}C^{T}}{CP_{t}^{-}C^{T}+R}} Kt=CPtCT+RPtCT 公式3
x t + = x t − + K t ( y k − C x t − ) x_{t}^{+}=x_{t}^{-}+K_{t}(y_{k}-Cx_{t}^{-}) xt+=xt+Kt(ykCxt)公式4
P t + = ( 1 − K t C ) P t − P_{t}^{+}=(1-K_{t}C)P_{t}^{-} Pt+=(1KtC)Pt公式5
根据匹配好的track和detection进行坐标修订,依次计算卡尔曼增益(kalman_gain)、 C x t − Cx^{-}_{t} Cxt(projected_mean),以及修正的结果(new_mean)以及后验协方差矩阵(new_covariance)。代码如下:

        self.mean, self.covariance = kf.update(
            self.mean,self.covariance,detection.to_xyah())
    def update(self, mean, covariance, measurement):
       
        projected_mean, projected_cov = self.project(mean, covariance)
        #scipy.linalg.cho_factor 返回上三角 或者下三角矩阵  其他位置元素 随机
        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

卡尔曼更新完成后 deepsort的核心操作即全部完成,后续操作为更新每个track的状态、删除死亡track、更新confirmed track的feature set,完成所有更新进入下一帧检测追踪。

你可能感兴趣的:(笔记,目标跟踪,深度学习,计算机视觉,目标跟踪,人工智能,学习)