精华内容
下载资源
问答
  • 针对多站纯方位目标跟踪与定位问题,给出了一种基于一致性的分布式无迹卡尔曼滤波算法(CDUKF,Consensus-based Dis-tributed Unscented Kalman Filter)。各观测站利用本站和邻接站点信息获得目标状态的局部估计,其次...
  • 多目标跟踪算法

    2019-04-16 15:00:22
    多目标跟踪任务的代码,内含演示代码以及视频.演示代码为main.py
  • camshift 多目标跟踪算法,使用鼠标选中摄像头视频流中的个目标,即可进行对目标进行跟踪。后续可使用kalman及例子滤波对目标跟踪进行优化,kalman及例子滤波程序参见我的其他资源。
  • SORT 多目标跟踪算法笔记

    万次阅读 多人点赞 2019-04-21 15:20:46
    sort 是一种简单的在线实时多目标跟踪算法。文章要点为: 以 IoU 作为前后帧间目标关系度量指标; 利用卡尔曼滤波器预测当前位置; 通过匈牙利算法关联检测框到目标; 应用试探期甄别虚检; 使用 Faster R-...

    SORT 是一种简单的在线实时多目标跟踪算法。文章要点为:

    • 以 IoU 作为前后帧间目标关系度量指标;
    • 利用卡尔曼滤波器预测当前位置;
    • 通过匈牙利算法关联检测框到目标;
    • 应用试探期甄别虚检;
    • 使用 Faster R-CNN,证明检测好跟踪可以很简单。

    技术方案

    SORT 算法以检测作为关键组件,传播目标状态到未来帧中,将当前检测与现有目标相关联,并管理跟踪目标的生命周期。

    检测

    跟踪框架使用 Faster R-CNN 并应用其在 PASCAL VOC 挑战中的默认参数,只输出概率大于50%的行人检测结果而忽略其他类。
    文章在实验中替换 MDP 和所提方法的检测,发现检测质量对跟踪性能有显著影响。在这里插入图片描述

    估计模型

    目标模型,即用于将目标身份传播到下一帧的表示和运动模型。SORT 算法用一个独立于其他物体和相机运动的线性等速模型来近似每个物体的帧间位移。每个目标的状态建模为:

    x = [ u , v , s , r , u ˙ , v ˙ , s ˙ ] T , \mathbf{x} = [u,v,s,r,\dot{u},\dot{v},\dot{s}]^T, x=[u,v,s,r,u˙,v˙,s˙]T,

    其中 u u u v v v 分别代表目标中心的水平和垂直像素位置,而 s s s r r r 分别代表目标边界框的比例(面积)和纵横比。注意,纵横比被认为是常数。关联检测到目标后,用检测到的边界框更新目标状态,其中速度分量通过卡尔曼滤波器框架进行优化求解。如果没有与目标相关的检测,则使用线性速度模型简单地预测其状态而不进行校正。

    数据关联

    在将检测分配给现有目标时:

    • 预测每个目标在当前帧中的新位置,估计其边界框形状;
    • 由每个检测与现有目标的所有预测边界框之间的交并比(IoU)计算分配成本矩阵;
    • 使用匈牙利算法对分配进行优化求解;
    • 拒绝检测与目标重叠小于 I O U m i n IOU_{min} IOUmin 的分配。

    文章发现边界框的 IoU 距离隐式处理由目标经过引起的短时遮挡。具体地说,当遮挡物盖过目标时,只检测到遮挡物。尽管隐藏目标离检测框中心更近,但 IoU 距离更倾向于具有相似比例的检测。这使得可以在不影响覆盖目标的情况下,通过检测对遮挡目标进行校正。

    创建和删除轨迹标识

    当目标进入和离开图像时,需要相应地创建或销毁唯一标识。对于创建跟踪程序,文中认为任何重叠小于 I o U m i n IoU_{min} IoUmin 的检测都表示存在未跟踪的目标。使用速度设置为零的边界框信息初始化跟踪器。由于此时无法观测到速度,因此速度分量的协方差用较大的值初始化,反映出这种不确定性。此外,新的跟踪器将经历一个试用期,其中目标需要与检测相关联以积累足够的证据以防止误报的跟踪。

    如果 T L o s t T_{Lost} TLost 帧未检测到,则终止轨迹。这可以防止跟踪器数量的无限增长以及由于无检测校正下预测时间过长而导致的定位错误。在所有实验中, T L o s t T_{Lost} TLost 设为1有以下原因:

    • 首先,等速模型对真实动力学的预测能力较差;
    • 其次,我们主要关注逐帧跟踪,目标重识别超出本工作范畴;
    • 此外,早期删除丢失的目标有助于提高效率。如果目标重新出现,跟踪将在新标识下隐式恢复。

    sort.py

    算法和程序都比较简单。程序依赖 scikit-learn 所提供的 linear_assignment 实现匈牙利匹配。KalmanFilterFilterPy 提供。

    matplotlib.pyplot.ion() 打开交互模式。

      # all train
      sequences = ['PETS09-S2L1','TUD-Campus','TUD-Stadtmitte','ETH-Bahnhof','ETH-Sunnyday','ETH-Pedcross2','KITTI-13','KITTI-17','ADL-Rundle-6','ADL-Rundle-8','Venice-2']
      args = parse_args()
      display = args.display
      phase = 'train'
      total_time = 0.0
      total_frames = 0
      colours = np.random.rand(32,3) #used only for display
      if(display):
        if not os.path.exists('mot_benchmark'):
          print('\n\tERROR: mot_benchmark link not found!\n\n    Create a symbolic link to the MOT benchmark\n    (https://motchallenge.net/data/2D_MOT_2015/#download). E.g.:\n\n    $ ln -s /path/to/MOT2015_challenge/2DMOT2015 mot_benchmark\n\n')
          exit()
        plt.ion()
        fig = plt.figure() 
      
      if not os.path.exists('output'):
        os.makedirs('output')
    

    对于每个序列,创建一个 SORT 跟踪器实例。
    加载序列的检测数据。检测框格式为[x1,y1,w,h]

      for seq in sequences:
        mot_tracker = Sort() #create instance of the SORT tracker
        seq_dets = np.loadtxt('data/%s/det.txt'%(seq),delimiter=',') #load detections
        with open('output/%s.txt'%(seq),'w') as out_file:
          print("Processing %s."%(seq))
          for frame in range(int(seq_dets[:,0].max())):
            frame += 1 #detection and frame numbers begin at 1
            dets = seq_dets[seq_dets[:,0]==frame,2:7]
            dets[:,2:4] += dets[:,0:2] #convert to [x1,y1,w,h] to [x1,y1,x2,y2]
            total_frames += 1
    

    skimage.io.imread 从文件加载图像。

            if(display):
              ax1 = fig.add_subplot(111, aspect='equal')
              fn = 'mot_benchmark/%s/%s/img1/%06d.jpg'%(phase,seq,frame)
              im =io.imread(fn)
              ax1.imshow(im)
              plt.title(seq+' Tracked Targets')
    

    update 由检测框更新轨迹。trackers命名有问题。

            start_time = time.time()
            trackers = mot_tracker.update(dets)
            cycle_time = time.time() - start_time
            total_time += cycle_time
    

    matplotlib.axes.Axes.add_patch 将补丁p添加到轴补丁列表中;剪辑框将设置为 Axes 剪切框。 如果未设置变换,则将其设置为 transData。返回补丁。
    matplotlib.axes.Axes.set_adjustable 定义 Axes 将更改哪个参数以实现给定面。

            for d in trackers:
              print('%d,%d,%.2f,%.2f,%.2f,%.2f,1,-1,-1,-1'%(frame,d[4],d[0],d[1],d[2]-d[0],d[3]-d[1]),file=out_file)
              if(display):
                d = d.astype(np.int32)
                ax1.add_patch(patches.Rectangle((d[0],d[1]),d[2]-d[0],d[3]-d[1],fill=False,lw=3,ec=colours[d[4]%32,:]))
                ax1.set_adjustable('box-forced')
    
            if(display):
              fig.canvas.flush_events()
              plt.draw()
              ax1.cla()
    
      print("Total Tracking took: %.3f for %d frames or %.1f FPS"%(total_time,total_frames,total_frames/total_time))
      if(display):
        print("Note: to get real runtime results run without the option: --display")
    

    Sort

    Sort 是一个多目标跟踪器,管理多个 KalmanBoxTracker 对象。

      def __init__(self,max_age=1,min_hits=3):
        """
        Sets key parameters for SORT
        """
        self.max_age = max_age
        self.min_hits = min_hits
        self.trackers = []
        self.frame_count = 0
    

    update

    参数dets:格式为[[x1,y1,x2,y2,score],[x1,y1,x2,y2,score],...]的 numpy 检测数组。
    要求:即使空检测,也必须为每个帧调用此方法一次。返回一个类似的数组,其中最后一列是对象 ID。

    注意:返回的对象数可能与提供的检测数不同。

    update 的输入参数dets为 numpy.array,然而 KalmanBoxTracker 要求的输入为列表。

    Created with Raphaël 2.2.0 update dets KalmanBoxTracker.predict associate_detections_to_trackers KalmanBoxTracker.update KalmanBoxTracker tracks End

    从现有跟踪器获取预测位置。
    predict 推进状态向量并返回预测的边界框估计。

    在当前帧逐个预测轨迹位置,记录状态异常的跟踪器索引。trks存储跟踪器的预测,不幸与下面的跟踪器重名。

        self.frame_count += 1
        #get predicted locations from existing trackers.
        trks = np.zeros((len(self.trackers),5))
        to_del = []
        ret = []
        for t,trk in enumerate(trks):
          pos = self.trackers[t].predict()[0]
          trk[:] = [pos[0], pos[1], pos[2], pos[3], 0]
          if(np.any(np.isnan(pos))):
            to_del.append(t)
    

    numpy.ma.masked_invalid 屏蔽出现无效值的数组(NaN 或 inf)。
    numpy.ma.compress_rows 压缩包含掩码值的2-D 数组的整行。这相当于np.ma.compress_rowcols(a, 0),有关详细信息,请参阅 extras.compress_rowcols
    reversed 返回反向 iterator. seq 必须是具有 __reversed__() 方法的对象,或者支持序列协议(__len__() 方法和 __getitem__() 方法,整数参数从0开始)。

    逆向删除异常的跟踪器,防止破坏索引。压缩能够保证在数组中的位置不变。
    associate_detections_to_trackers 将检测分配给跟踪对象(均以边界框表示)。返回3个列表:matchesunmatched_detectionsunmatched_trackers

        trks = np.ma.compress_rows(np.ma.masked_invalid(trks))
        for t in reversed(to_del):
          self.trackers.pop(t)
        matched, unmatched_dets, unmatched_trks = associate_detections_to_trackers(dets,trks)
    

    使用分配的检测更新匹配的跟踪器。为什么不通过matched存储的索引选择跟踪器?
    update 使用观测边界框更新状态向量。

        #update matched trackers with assigned detections
        for t,trk in enumerate(self.trackers):
          if(t not in unmatched_trks):
            d = matched[np.where(matched[:,1]==t)[0],0]
            trk.update(dets[d,:][0])
    

    由未匹配的检测创建和初始化新的跟踪器。

        #create and initialise new trackers for unmatched detections
        for i in unmatched_dets:
            trk = KalmanBoxTracker(dets[i,:]) 
            self.trackers.append(trk)
    

    get_state 返回当前边界框估计值。
    ret格式为[[x1,y1,x2,y2,score],[x1,y1,x2,y2,score],...]

    自后向前遍历,仅返回在当前帧出现且命中周期大于self.min_hits(除非跟踪刚开始)的跟踪结果;如果未命中时间大于self.max_age则删除跟踪器。
    hit_streak忽略目标初始的若干帧。

        i = len(self.trackers)
        for trk in reversed(self.trackers):
            d = trk.get_state()[0]
            if((trk.time_since_update < 1) and (trk.hit_streak >= self.min_hits or self.frame_count <= self.min_hits)):
              ret.append(np.concatenate((d,[trk.id+1])).reshape(1,-1)) # +1 as MOT benchmark requires positive
            i -= 1
            #remove dead tracklet
            if(trk.time_since_update > self.max_age):
              self.trackers.pop(i)
    
        if(len(ret)>0):
          return np.concatenate(ret)
        return np.empty((0,5))
    

    associate_detections_to_trackers

    这里命名不准确,应该是将检测框关联到跟踪目标(objects)或者轨迹(tracks),而不是跟踪器(trackers)。
    跟踪器数量为0则直接构造结果。

      if(len(trackers)==0):
        return np.empty((0,2),dtype=int), np.arange(len(detections)), np.empty((0,5),dtype=int)
      iou_matrix = np.zeros((len(detections),len(trackers)),dtype=np.float32)
    

    iou 不支持数组计算。
    逐个计算两两间的交并比,调用 linear_assignment 进行匹配。

      for d,det in enumerate(detections):
        for t,trk in enumerate(trackers):
          iou_matrix[d,t] = iou(det,trk)
      matched_indices = linear_assignment(-iou_matrix)
    

    记录未匹配的检测框及轨迹。

      unmatched_detections = []
      for d,det in enumerate(detections):
        if(d not in matched_indices[:,0]):
          unmatched_detections.append(d)
      unmatched_trackers = []
      for t,trk in enumerate(trackers):
        if(t not in matched_indices[:,1]):
          unmatched_trackers.append(t)
    

    过滤掉 IoU 低的匹配。

      #filter out matched with low IOU
      matches = []
      for m in matched_indices:
        if(iou_matrix[m[0],m[1]]<iou_threshold):
          unmatched_detections.append(m[0])
          unmatched_trackers.append(m[1])
        else:
          matches.append(m.reshape(1,2))
    

    初始化用列表,返回值用 Numpy.array。

      if(len(matches)==0):
        matches = np.empty((0,2),dtype=int)
      else:
        matches = np.concatenate(matches,axis=0)
    
      return matches, np.array(unmatched_detections), np.array(unmatched_trackers)
    

    KalmanBoxTracker

    此类表示观测目标框所对应跟踪对象的内部状态。
    定义等速模型。
    内部使用 KalmanFilter,7个状态变量,4个观测输入。
    F是状态变换模型,H是观测函数,R为测量噪声矩阵,P为协方差矩阵,Q为过程噪声矩阵。
    状态转移矩阵A根据运动学公式确定
    x = [ u , v , s , r , u ˙ , v ˙ , s ˙ ] T , \mathbf{x} = [u,v,s,r,\dot{u},\dot{v},\dot{s}]^T, x=[u,v,s,r,u˙,v˙,s˙]T,
    F = [ 1 0 0 0 Δ u 0 0 0 1 0 0 0 Δ v 0 0 0 1 0 0 0 Δ s 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 ] F=\begin{bmatrix} 1 &amp; 0 &amp; 0 &amp; 0 &amp; \Delta u &amp; 0 &amp; 0 \\ 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; \Delta v &amp; 0 \\ 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; \Delta s \\ 0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 \\ 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 1 \end{bmatrix} F=1000000010000000100000001000Δu0001000Δv0001000Δs0001

    H = [ 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 ] H=\begin{bmatrix} 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 \end{bmatrix} H=1000010000100001000000000000

      count = 0
      def __init__(self,bbox):
        """
        Initialises a tracker using initial bounding box.
        """
        #define constant velocity model
        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. #give high uncertainty to the unobservable initial velocities
        self.kf.P *= 10.
        self.kf.Q[-1,-1] *= 0.01
        self.kf.Q[4:,4:] *= 0.01
    
        self.kf.x[:4] = convert_bbox_to_z(bbox)
        self.time_since_update = 0
        self.id = KalmanBoxTracker.count
        KalmanBoxTracker.count += 1
        self.history = []
        self.hits = 0
        self.hit_streak = 0
        self.age = 0
    

    update

    使用观察到的目标框更新状态向量。filterpy.kalman.KalmanFilter.update 会根据观测修改内部状态估计self.kf.x
    重置self.time_since_update,清空self.history

        self.time_since_update = 0
        self.history = []
        self.hits += 1
        self.hit_streak += 1
        self.kf.update(convert_bbox_to_z(bbox))
    

    predict

    推进状态向量并返回预测的边界框估计。
    将预测结果追加到self.history。由于 get_state 直接访问 self.kf.x,所以self.history没有用到。

        if((self.kf.x[6]+self.kf.x[2])<=0):
          self.kf.x[6] *= 0.0
        self.kf.predict()
        self.age += 1
        if(self.time_since_update>0):
          self.hit_streak = 0
        self.time_since_update += 1
        self.history.append(convert_x_to_bbox(self.kf.x))
        return self.history[-1]
    

    get_state

    get_state
    convert_x_to_bbox

    返回当前边界框估计值。

        return convert_x_to_bbox(self.kf.x)
    

    iou

    @numba.jit 即时编译修饰函数以生成高效的机器代码。所有参数都是可选的。

    @jit
    def iou(bb_test,bb_gt):
      """
      Computes IUO between two bboxes in the form [x1,y1,x2,y2]
      """
      xx1 = np.maximum(bb_test[0], bb_gt[0])
      yy1 = np.maximum(bb_test[1], bb_gt[1])
      xx2 = np.minimum(bb_test[2], bb_gt[2])
      yy2 = np.minimum(bb_test[3], bb_gt[3])
      w = np.maximum(0., xx2 - xx1)
      h = np.maximum(0., yy2 - yy1)
      wh = w * h
      o = wh / ((bb_test[2]-bb_test[0])*(bb_test[3]-bb_test[1])
        + (bb_gt[2]-bb_gt[0])*(bb_gt[3]-bb_gt[1]) - wh)
      return(o)
    
    

    convert_bbox_to_z

    [x1,y1,x2,y2]形式的检测框转为滤波器的状态表示形式[x,y,s,r]。其中xy是框的中心,s是比例/区域,r是宽高比。

      w = bbox[2]-bbox[0]
      h = bbox[3]-bbox[1]
      x = bbox[0]+w/2.
      y = bbox[1]+h/2.
      s = w*h    #scale is just area
      r = w/float(h)
      return np.array([x,y,s,r]).reshape((4,1))
    

    convert_x_to_bbox

    [cx,cy,s,r]的目标框表示转为[x_min,y_min,x_max,y_max]的形式。

      w = np.sqrt(x[2]*x[3])
      h = x[2]/w
      if(score==None):
        return np.array([x[0]-w/2.,x[1]-h/2.,x[0]+w/2.,x[1]+h/2.]).reshape((1,4))
      else:
        return np.array([x[0]-w/2.,x[1]-h/2.,x[0]+w/2.,x[1]+h/2.,score]).reshape((1,5))
    

    改进思路

    Sort 算法受限于在线的定位,直接忽略了所有目标的考察期输出。这未免有些因噎废食。对于目标的甄别期较短,可以考虑延时判断后再行输出。

    参考资料:

    展开全文
  • 对目标进行跟踪的算法卡尔曼滤波算法与交互式模型机动目标跟踪算法的性能比较。
  • 摘要:模型目标跟踪算法由于其独特的处理未知结构和可变参数的优点,已成为当前目标跟踪研究领域的一个重要方向。然而当今的模型目标跟踪方法大都停留在理论层面,因此在实际应用层面上研究并设计模型目标跟踪...
  • 多目标跟踪算法研究

    2021-04-13 20:20:10
    多目标跟踪算法研究
  • 多目标跟踪算法解读

    千次阅读 2020-09-28 14:29:43
    一、多目标跟踪背景介绍 1.问题定义 2.难点 3.应用场景 二、相关方法 1.Model free 方法 2. Tracking by detection 方法 1)离线方法 2)在线方法 三、基准 多目标跟踪背景介绍 问题定义 多目标跟踪是将视频...
    目录
    一、多目标跟踪背景介绍
            1.问题定义
            2.难点
            3.应用场景
    二、相关方法
            1.Model free 方法
            2. Tracking by detection 方法
                 1)离线方法
                  2)在线方法
    三、基准
    

    多目标跟踪背景介绍

    问题定义

    多目标跟踪是将视频中每一帧的物体都赋予一个id,并且得到每个id的行为轨迹,如图1所示。
    从代码的层次考虑,其输入为视频序列,输出为物体的行为轨迹和id。从是否有模型上考虑,可以分为model free的方法和tracking by detection的方法,其中后者又可以分为离线学习的方法和在线学习的方法。
    有多目标跟踪算法就有单目标跟踪方法,不同之处就在于匹配问题,举例说明,如果在前一帧检测到两个物体a,b,在后一帧检测到两个物体c,d,怎么知道前一帧的a在后一帧所对应的是c或者d的哪一个物体呢?匹配问题在单目标跟踪领域就不存在,因为前后两帧只存在一个目标。
    在这里插入图片描述

    难点

    多目标跟踪的难点在于ID switch, 如图2所示,当骑自行车的人与步行的人相遇的时候,步行的人发生了遮挡,这是对于电脑来说,认为是此id的跟踪已经结束,过了一段时间,步行的人重新出现在了视野,但是电脑会认为是一个新的物体出现,因此赋予一个新的id,这种情况下,就发生了id交换,同理当物体被其他物体例如电线杆遮挡时也是一样。因此针对这种id交换的情况,学术界提出了很多的解决方案,这个我们后面再看。
    在这里插入图片描述

    应用场景

    多目标跟踪是一种通用的算法,几乎应用在视觉领域的各个方面,例如安防领域、自动驾驶领域以及医疗领域。如图3,4,5所示,在安防领域,我们通常需要跟踪来得到在特定区域的人数统计。在自动驾驶领域,我们通常需要跟踪来预估行人或者车辆的轨迹。在医疗领域,我们通常需要跟踪来得到细胞的运动情况。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    相关方法

    多目标的跟踪方法在这里我们分为两类,model-free的方法和tracking by detection的方法。

    model-free的方法

    model-free的方法核心思想是在第一帧给定初始的检测框,在后续帧中跟踪这些框,这种方法是早期的算法,缺点是如果有些目标在后续帧中才出现,这些目标并不会被跟踪。如图6所示。
    在这里插入图片描述

    tracking by detection的方法

    离线学习

    离线学习的方法缺点在于无法实时处理视频,通过比较相邻帧之间的相似度生成短的轨迹,之后再将短的轨迹合成长的轨迹。如图7所示。
    在这里插入图片描述

    在线学习

    Learning to Track: Online Multi-Object Tracking by Decision Making (2015,ICCV)
    该论文第一次将强化学习加入到跟踪这个领域,将目标的跟踪周期建模成马尔可夫过程进行优化。如图8所示。当物体被检测到之后,被认为是active的状态。在active的状态下如果下一帧也检测到了,状态由active转换为tracked,如果没有检测到,状态转换为inactive状态。在tracked状态下如果如果继续检测到该目标,则持续为tracked状态,如果没检测到,则状态转换为lost。在lost状态下如果被检测到了,则状态恢复为tracked,如果持续没检测到,则还保持lost状态,当lost的时间超过了一定的阈值后,状态切换为inactive,inactive状态下的目标会保持inactive直到视频结束。
    在这里插入图片描述
    Online Multi-Object Tracking via Structural Constraint Event Aggregation (2016,CVPR)
    该论文将目标跟踪中的匹配问题加入了结构约束,举个例子,前一帧如果检测到的目标连结起来呈现的是三角形,那么通过速度预测每个目标的位置,从而调整三角形的形态,并且认为后面的帧就是呈现这样的结构。如图9所示
    在这里插入图片描述
    Tracking The Untrackable: Learning to Track Multiple Cues with Long-Term Dependencies (2017, ICCV)
    该论文首次将深度学习加入到跟踪中,通过神经网络提取三种特征,外表特征、运动特征、交互特征,将三种特征连接到一起,再经过全连接层,得到相似度分数。相邻帧得到相似分数后,利用匈牙利匹配得到最大匹配。匈牙利匹配的概念见《带你入门多目标跟踪(三)匈牙利算法&KM算法》
    在这里插入图片描述
    Simple Online and Real-time Tracking with a D eep Association Metric
    这个系列有两个算法,sort和deep sort,这里先介绍sort。sort的思路很简单,首先通过检测器例如faster rcnn把每一帧的物体检测出来,之后通过卡尔曼滤波器预测物体在下一帧的位置,将预测的位置与下一帧实际检测到的位置做IOU的计算,得到相邻两帧物体的相似度,最后利用匈牙利匹配得到相邻帧的对应id。由于这里只是计算框的重叠面积,因此如果两个物体发生遮挡,会出现id交换的情况,所以作者为了降低id交换,提出了deep sort算法。deep sort之所以可以把id交换降低45%,是因为它将面积匹配修改为了特征匹配。在匹配之前作者利用resnet50先将特征里取出来,由于一般情况下很少会遇到外观特征完全相同的两个物体,因此这种改进是有效的。deep dort由于很简单,因此没有在顶会上发表成功,但是由于其可以保证在高速度的情况下的高准确度,所以广泛应用在了工程实践的过程中。相关的demo
    在这里插入图片描述
    Chained-Tracker: Chaining Paired Attentive Regression Results for End-to-End Joint Multiple-Object Detection and Tracking
    论文有两个贡献1)提出了一种链式跟踪算法,业内首创将相邻两帧输入网络,可以将目标检测、特征提取、数据关联三个模块集成到单个网络中,实现端到端联合检测跟踪。2)相邻帧检测框对回归。论文将数据关联问题转换为相邻帧中检测框对的回归问题。这两个贡献主要是针对现存的两个问题:1)算法不能端到端的优化。2)过度依赖于检测的质量

    在这里插入图片描述

    基准

    目前在MOT7数据集上所能达到的效果如下图所示。MOTbenchmark
    在这里插入图片描述
    参考:高旭 《多目标跟踪》

    展开全文
  • 基于staple目标跟踪算法,写的多目标跟踪算法,C++代码,工程中已使用,放心使用。
  • 基于camshift跟踪算法,使用鼠标框选目标进行跟踪
  • 目标跟踪算法多目标跟踪算法多目标跟踪算法多目标跟踪算法多目标跟踪算法
  • 利用混合高斯模型对固定背景的视频进行运动目标检测,根据KCF算法对检测到的目标进行跟踪,15帧后重新检测
  • 相对于多目标跟踪算法,视觉单目标跟踪算法研究的更为广泛,当前解决的相对更好。典型的如Mean shift算法,用卡尔曼滤波、粒子滤波进行状态预测,TLD等基于在线学习的跟踪,KCF等基于相关性滤波的算法等。
  • 光流多目标跟踪算法

    2015-11-05 10:13:49
    光流法对多目标实现稳定跟踪,对光流法实现速度优化,同时可对个运动目标进行稳定跟踪
  • 基于深度学习的目标跟踪算法综述论文,参考文献100篇,权威发布! 基于深度学习的目标跟踪算法综述论文,参考文献100篇,权威发布!
  • 介绍了交互模型算法的起源和发展, 归纳和总结了近年来模型算法的改进方向,介绍了模型算法应用的新领域,简述了传感器目标跟踪的研究成 果;最后加以总结,展望模型算法的研究方向。
  • 多目标跟踪为核心的算法,具有较好的鲁棒性
  • 摘要:模型目标跟踪算法由于其独特的处理未知结构和可变参数的优点,已成为当前目标跟踪研究领域的一个重要方向。然而当今的模型目标跟踪方法大都停留在理论层面,因此在实际应用层面上研究并设计模型目标跟踪...
  • 一种鲁棒快速的单目标跟踪算法matlab程序,
  • DeepSORT 多目标跟踪算法笔记

    千次阅读 多人点赞 2019-05-11 16:54:58
    SORT 是一种实用的多目标跟踪算法,然而由于现实中目标运动多变且遮挡频繁,该算法的身份转换(Identity Switches)次数较高。DeepSORT 整合外观信息使得身份转换的数量减少了45%。所提方案为: 使用马氏距离和...

    SORT 是一种实用的多目标跟踪算法,然而由于现实中目标运动多变且遮挡频繁,该算法的身份转换(Identity Switches)次数较高。DeepSORT 整合外观信息使得身份转换的数量减少了45%。所提方案为:

    • 使用马氏距离和深度特征余弦距离两种度量;
    • 采取级联匹配,优先匹配距上次出现间隔短的目标;
    • 第一级关联以余弦距离作为成本函数,但设定马氏距离和余弦距离两个阈值约束;
    • 第二级关联与 SORT 中相同,尝试关联未确认和年龄为 n = 1 n=1 n=1 的不匹配轨迹;
    • 同样采用试用期甄别目标,但大幅提高轨迹寿命 A m a x = 30 A_{\mathrm{max}}=30 Amax=30

    带有深度关联度量的 SORT 算法

    DeepSORT 属于传统的单假设跟踪方法,采用递归卡尔曼滤波和逐帧数据关联。

    轨迹处理和状态估计

    多目标跟踪问题的一个普遍场景为:摄像机未校准且没有自身运动信息可用。这也是多目标跟踪基准测试中最常见的设置(MOT16)。因此,DeepSORT

    • 将跟踪场景定义在八维状态空间—— ( u , v , γ , h , x ˙ , y ˙ , γ ˙ , h ˙ ) (u, v, \gamma, h, \dot{x}, \dot{y}, \dot{\gamma}, \dot{h}) (u,v,γ,h,x˙,y˙,γ˙,h˙),包含边界框中心位置 ( u , v ) (u, v) (u,v)、纵横比 γ \gamma γ、高度 h h h 以及它们各自在图像坐标中的速度。
    • 使用具有等速运动和线性观测模型的标准卡尔曼滤波器,并把边界坐标 ( u , v , γ , h ) (u, v, \gamma, h) (u,v,γ,h) 作为物体状态的直接观测。
    • 轨迹处理和卡尔曼滤波框架与 SORT 中的原始公式基本相同。

    每个轨迹 K K K 内部记录自上次成功关联到测量 a k a_k ak 以来的帧数。该计数器在卡尔曼滤波器预测期间递增,并且当轨迹与测量相关联时重置为0。

    • 将超过预定义最大寿命 A m a x A_{\rm max} Amax 的轨迹视为已离开场景并从轨迹集中删除。
    • 由无法与现有轨迹相关联的检测创建新的轨迹假设。这些新轨迹在前三帧中被列为暂定轨迹。在此期间,每次检测后如不能关联到则删除该轨迹。

    关联问题

    关联预测卡尔曼状态与新到达测量值的常规方法是将其看作分配问题,利用匈牙利算法求解。DeepSORT 通过结合目标框马氏距离和特征余弦距离两个度量来整合运动和外观信息。一方面,马式距离基于运动提供了有关物体可能位置的信息,这对短期预测特别有用。另一方面,余弦距离考虑外观信息,这对于在长期遮挡之后找回身份特别有用,此时运动不那么具有辨别力。

    预测卡尔曼状态和新到测量值之间的(平方)马氏距离(Mahalanobis 距离):

    d ( 1 ) ( i , j ) = ( d j − y i ) ⊤ S i − 1 ( d j − y i ) , \begin{aligned} d^{(1)}(i,j) = (d_j - y_i)^\top S^{-1}_i (d_j - y_i), \end{aligned} d(1)(i,j)=(djyi)Si1(djyi),
    其中, ( y i , S i ) (y_i, S_i) (yi,Si) 表示第 i i i 个轨迹分布到测量空间的投影, d j d_j dj 表示第 j j j 个检测边界框。

    马氏距离通过测算检测与平均轨迹位置的距离超过多少标准差来考虑状态估计的不确定性。此外,可以通过从逆 χ 2 \chi^2 χ2 分布计算 95 % 95\% 95% 置信区间的阈值,排除可能性小的关联。四维测量空间对应的马氏阈值为 t ( 1 ) = 9.4877 t^{(1)} = 9.4877 t(1)=9.4877。如果第 i i i 条轨迹和第 j j j 个检测之间的关联是可采纳的,则:
    b i , j ( 1 ) = 1 [ d ( 1 ) ( i , j ) ≤ t ( 1 ) ] \begin{aligned} b_{i,j}^{(1)} = \mathbb{1}[d^{(1)}(i, j) \leq t^{(1)}] \end{aligned} bi,j(1)=1[d(1)(i,j)t(1)]

    当运动不确定性较低时,马式距离是一个合适的关联度量。但在跟踪的图像空间问题公式中,卡尔曼滤波框架仅提供目标位置的粗略估计。尤其是,未考虑的摄像机运动会在图像平面中引入快速位移,使得在遮挡情况下跟踪时马式距离度量相当不精确。因此,DeepSORT

    • 对于每个检测边界框 d j d_j dj 计算一个外观描述符 r j r_j rj,满足 ∥ r j ∥ = 1 \lVert r_j\rVert = 1 rj=1
    • 每个轨迹保留最后 L k = 100 L_k=100 Lk=100 个关联外观描述符的原型库(gallery) R k = { r ⃗ k ( i ) } k = 1 L k \mathcal{R}_k = \{\vec{r}_k^{(i)}\}_{k=1}^{L_k} Rk={r k(i)}k=1Lk
    • 外观空间中第 i i i 个轨迹和第 j j j 个检测之间的最小余弦距离为:
      d ( 2 ) ( i , j ) = min ⁡ { 1 − r j ⊤ r k ( i ) ∥ r k ( i ) ∈ R i } \begin{aligned} d^{(2)}(i, j) = \min\{1 - r^\top_j r^{(i)}_k \| r^{(i)}_k\in \mathcal{R}_i\} \end{aligned} d(2)(i,j)=min{1rjrk(i)rk(i)Ri}
    • 引入一个二进制变量来指示根据此度量标准是否允许关联:
      b i , j ( 2 ) = 1 [ d ( 2 ) ( i , j ) ≤ t ( 2 ) ] \begin{aligned} b_{i,j}^{(2)} = \mathbb{1}[d^{(2)}(i, j) \leq t^{(2)}] \end{aligned} bi,j(2)=1[d(2)(i,j)t(2)]

    算法在一个独立训练数据集上找到该指标的合适阈值。在实践中,DeepSORT 应用一个预训练的 CNN 来计算边界框外观描述符。

    关联问题的成本函数为以上两个指标的加权和:
    c i , j = λ &ThinSpace; d ( 1 ) ( i , j ) + ( 1 − λ ) d ( 2 ) ( i , j ) \begin{aligned} c_{i,j} = \lambda \, d^{(1)}(i, j) + (1 - \lambda) d^{(2)}(i, j) \end{aligned} ci,j=λd(1)(i,j)+(1λ)d(2)(i,j)
    超参数 λ \lambda λ 控制每个度量对组合关联成本的影响。在实验中,作者发现当有大量的相机运动时,设置 λ = 0 \lambda=0 λ=0 是一个合理的选择。此时,关联成本中仅使用外观信息。然而,关联结果仍受两方面的约束。仅当关联在两个度量的选通区域内时,称其为可接受关联:

    b i , j = ∏ m = 1 2 b i , j ( m ) . \begin{aligned} b_{i,j} = \prod_{m=1}^{2} b_{i, j}^{(m)}. \end{aligned} bi,j=m=12bi,j(m).

    级联匹配

    当目标被遮挡一段较长的时间后,随后的卡尔曼滤波预测会增加与目标位置相关的不确定性。因此,概率质量在状态空间中扩散,观测概率变得不那么尖峰。直观地说,关联度量应该通过增加测量跟踪距离来解释概率质量的这种扩散。与直觉相反,当两条轨迹竞争同一检测时,马式距离倾向于更大的不确定性,因为它有效地减少了检测的标准偏差到投影轨迹平均值的距离。这不是我们所期望的,因为它可能导致轨迹碎片增加和轨迹不稳定。因此,DeepSORT
    引入级联匹配,优先考虑更常见的目标,以编码关联似然中概率扩散的概念。

    在这里插入图片描述

    在最后的匹配阶段,使用 SORT 算法中提出的 IoU 度量方法尝试关联未确认和年龄为 n = 1 n=1 n=1 的不匹配轨迹。 这有助于解决外观的突然变化,如静态场景几何体的部分遮挡,并且增加了针对错误初始化的鲁棒性。

    Deep Appearance Descriptor

    以上方法的成功应用需要提前离线训练区分度高的特征嵌入。为此,DeepSORT 采用了一个在大规模行人重新识别数据集(MARS)上训练的 CNN,其中包含1261个行人的超过110万张图像,这使得它非常适合行人跟踪中的深度度量学习。

    如下表所示,模型结构为宽残差网络(WRNS),其中有两个卷积层,后面是六个残差块。维度 128 128 128 的全局特征映射在 “Dense 10”层中计算。最终 BN 和 ℓ 2 \ell_2 2 规范化投影特征到单元超球面上从而与余弦外观度量兼容。网络参数量为2.67M,在 Nvidia GeForce GTX 1050移动 GPU 上,32个边界框的一次前向花费大约 30 &ThinSpace; ms 30\,\textrm{ms} 30ms。因此,只要有现代 GPU,该网络就非常适合在线跟踪。作者在 GitHub 仓库中提供了预先训练的模型以及可用于生成特征的脚本。

    cnn

    程序分为两部分:运行框架(application_util)和算法(deep_sort)。程序运行时由 Visualization 或者 NoVisualization 进行管理。算法主要实体为 TrackerKalmanFilterTrackNearestNeighborDistanceMetricDetectionKalmanFilter 中自己定义了马氏距离的计算,NearestNeighborDistanceMetric 能够计算特征相似度。linear_assignment.py 中定义了阈值选通和匹配函数。

    deep_sort_app.py

    main
    parse_args
    run
        args = parse_args()
        run(
            args.sequence_dir, args.detection_file, args.output_file,
            args.min_confidence, args.nms_max_overlap, args.min_detection_height,
            args.max_cosine_distance, args.nn_budget, args.display)
    

    parse_args()

    解析命令行参数。

        parser = argparse.ArgumentParser(description="Deep SORT")
        parser.add_argument(
            "--sequence_dir", help="Path to MOTChallenge sequence directory",
            default=None, required=True)
        parser.add_argument(
            "--detection_file", help="Path to custom detections.", default=None,
            required=True)
        parser.add_argument(
            "--output_file", help="Path to the tracking output file. This file will"
            " contain the tracking results on completion.",
            default="/tmp/hypotheses.txt")
        parser.add_argument(
            "--min_confidence", help="Detection confidence threshold. Disregard "
            "all detections that have a confidence lower than this value.",
            default=0.8, type=float)
        parser.add_argument(
            "--min_detection_height", help="Threshold on the detection bounding "
            "box height. Detections with height smaller than this value are "
            "disregarded", default=0, type=int)
        parser.add_argument(
            "--nms_max_overlap",  help="Non-maxima suppression threshold: Maximum "
            "detection overlap.", default=1.0, type=float)
        parser.add_argument(
            "--max_cosine_distance", help="Gating threshold for cosine distance "
            "metric (object appearance).", type=float, default=0.2)
        parser.add_argument(
            "--nn_budget", help="Maximum size of the appearance descriptors "
            "gallery. If None, no budget is enforced.", type=int, default=None)
        parser.add_argument(
            "--display", help="Show intermediate tracking results",
            default=True, type=bool_string)
        return parser.parse_args()
    

    run

    Created with Raphaël 2.2.0 run sequence, detection gather_sequence_info NearestNeighborDistanceMetric Tracker display? Visualization Visualization.run frame_callback results End NoVisualization NoVisualization.run yes no

    gather_sequence_info 收集序列信息,例如图像文件名、检测、标注(如果有的话)。
    NearestNeighborDistanceMetric 最近邻距离度量,对于每个目标,返回到目前为止已观察到的任何样本的最近距离(欧式或余弦)。
    由距离度量方法构造一个 Tracker

        seq_info = gather_sequence_info(sequence_dir, detection_file)
        metric = nn_matching.NearestNeighborDistanceMetric(
            "cosine", max_cosine_distance, nn_budget)
        tracker = Tracker(metric)
        results = []
    

    嵌套定义回调函数,过滤检测结果,预测目标并进行更新。
    create_detections 从原始检测矩阵创建给定帧索引的检测。
    non_max_suppression 抑制重叠的检测。

        def frame_callback(vis, frame_idx):
            print("Processing frame %05d" % frame_idx)
    
            # Load image and generate detections.
            detections = create_detections(
                seq_info["detections"], frame_idx, min_detection_height)
            detections = [d for d in detections if d.confidence >= min_confidence]
    
            # Run non-maxima suppression.
            boxes = np.array([d.tlwh for d in detections])
            scores = np.array([d.confidence for d in detections])
            indices = preprocessing.non_max_suppression(
                boxes, nms_max_overlap, scores)
            detections = [detections[i] for i in indices]
    

    Tracker.predict 将跟踪状态分布向前传播一步。
    Tracker.update 执行测量更新和跟踪管理。

            # Update tracker.
            tracker.predict()
            tracker.update(detections)
    

    visVisualization 或者 NoVisualization
    Visualization.set_image 设置 ImageViewer
    Visualization.draw_detections 绘制检测框。
    Visualization.draw_trackers 绘制跟踪框。

            # Update visualization.
            if display:
                image = cv2.imread(
                    seq_info["image_filenames"][frame_idx], cv2.IMREAD_COLOR)
                vis.set_image(image.copy())
                vis.draw_detections(detections)
                vis.draw_trackers(tracker.tracks)
    

    Track.is_confirmed 检查该轨迹是否确认过。
    Track.to_tlwh[x, y, width, height]边界框格式获取当前位置。

            # Store results.
            for track in tracker.tracks:
                if not track.is_confirmed() or track.time_since_update > 1:
                    continue
                bbox = track.to_tlwh()
                results.append([
                    frame_idx, track.track_id, bbox[0], bbox[1], bbox[2], bbox[3]])
    

    根据序列信息创建一个 Visualization 或者 NoVisualization 对象。由其运行跟踪器。
    update_msImageViewer 刷新显示的最小间隔(包含了跟踪处理时间)。

        # Run tracker.
        if display:
            visualizer = visualization.Visualization(seq_info, update_ms=5)
        else:
            visualizer = visualization.NoVisualization(seq_info)
        visualizer.run(frame_callback)
    
        # Store results.
        f = open(output_file, 'w')
        for row in results:
            print('%d,%d,%.2f,%.2f,%.2f,%.2f,1,-1,-1,-1' % (
                row[0], row[1], row[2], row[3], row[4], row[5]),file=f)
    

    Visualization

    显示 OpenCV 图像查看器中的跟踪输出。
    seq_info主要包含图片大小和帧起止索引。

        def __init__(self, seq_info, update_ms):
            image_shape = seq_info["image_size"][::-1]
            aspect_ratio = float(image_shape[1]) / image_shape[0]
            image_shape = 1024, int(aspect_ratio * 1024)
            self.viewer = ImageViewer(
                update_ms, image_shape, "Figure %s" % seq_info["sequence_name"])
            self.viewer.thickness = 2
            self.frame_idx = seq_info["min_frame_idx"]
            self.last_idx = seq_info["max_frame_idx"]
    

    run

    run
    ImageViewer.run
    _update_fun
            self.viewer.run(lambda: self._update_fun(frame_callback))
    

    _update_fun

    _update_fun
    frame_callback

    _update_funframe_callback 进行封装。根据帧索引判断是否终止,调用 frame_callback 进行处理。

            if self.frame_idx > self.last_idx:
                return False  # Terminate
            frame_callback(self, self.frame_idx)
            self.frame_idx += 1
            return True
    

    set_image

            self.viewer.image = image
    

    draw_groundtruth

    create_unique_color_uchar 为给定的轨迹 ID(标签)创建唯一的 RGB 颜色代码。

    draw_groundtruth
    create_unique_color_uchar
    rectangle
            self.viewer.thickness = 2
            for track_id, box in zip(track_ids, boxes):
                self.viewer.color = create_unique_color_uchar(track_id)
                self.viewer.rectangle(*box.astype(np.int), label=str(track_id))
    

    draw_detections

    绘制红色检测框。

            self.viewer.thickness = 2
            self.viewer.color = 0, 0, 255
            for i, detection in enumerate(detections):
                self.viewer.rectangle(*detection.tlwh)
    

    draw_trackers

    绘制目标轨迹,跳过未确认或者本次未检到的目标。

            self.viewer.thickness = 2
            for track in tracks:
                if not track.is_confirmed() or track.time_since_update > 0:
                    continue
                self.viewer.color = create_unique_color_uchar(track.track_id)
                self.viewer.rectangle(
                    *track.to_tlwh().astype(np.int), label=str(track.track_id))
                # self.viewer.gaussian(track.mean[:2], track.covariance[:2, :2],
                #                      label="%d" % track.track_id)
    
    

    Tracker

    参数:

    • metricNearestNeighborDistanceMetric 测量与轨迹关联的距离度量。
    • max_age:int,删除轨迹前的最大未命中数 A m a x A_{\mathrm{max}} Amax
    • n_init:int,确认轨迹前的连续检测次数。如果前n_init帧内发生未命中,则将轨迹状态设置为Deleted
    
        def __init__(self, metric, max_iou_distance=0.7, max_age=30, n_init=3):
            self.metric = metric
            self.max_iou_distance = max_iou_distance
            self.max_age = max_age
            self.n_init = n_init
    
            self.kf = kalman_filter.KalmanFilter()
            self.tracks = []
            self._next_id = 1
    

    predict

    Tracker.predict
    Track.predict
    KalmanFilter.predict

    对于每个轨迹,由一个 KalmanFilter 预测状态分布。每个轨迹记录自己的均值和方差作为滤波器输入。

            for track in self.tracks:
                track.predict(self.kf)
    

    update

    Created with Raphaël 2.2.0 update detections _match Track.update Track.mark_missed _initiate_track NearestNeighborDistanceMetric.partial_fit End

    调用 _match 进行级联匹配。

            """Perform measurement update and track management.
            Parameters
            ----------
            detections : List[deep_sort.detection.Detection]
                A list of detections at the current time step.
            """
            # Run matching cascade.
            matches, unmatched_tracks, unmatched_detections = \
                self._match(detections)
    

    根据匹配结果更新轨迹集合。

            # Update track set.
            for track_idx, detection_idx in matches:
                self.tracks[track_idx].update(
                    self.kf, detections[detection_idx])
            for track_idx in unmatched_tracks:
                self.tracks[track_idx].mark_missed()
            for detection_idx in unmatched_detections:
                self._initiate_track(detections[detection_idx])
            self.tracks = [t for t in self.tracks if not t.is_deleted()]
    

    传入特征列表及其对应 id,NearestNeighborDistanceMetric.partial_fit 构造一个活跃目标的特征字典。

            # Update distance metric.
            active_targets = [t.track_id for t in self.tracks if t.is_confirmed()]
            features, targets = [], []
            for track in self.tracks:
                if not track.is_confirmed():
                    continue
                features += track.features
                targets += [track.track_id for _ in track.features]
                track.features = []
            self.metric.partial_fit(
                np.asarray(features), np.asarray(targets), active_targets)
    
    

    _match

    _match 实现了论文2.3. Matching Cascade 的内容。

    内部嵌套定义 gated_metric 函数,由特征距离构建门矩阵。

    gated_metric
    NearestNeighborDistanceMetric.distance
    linear_assignment.gate_cost_matrix

    d ( 2 ) ( i , j ) = min ⁡ { 1 − r j ⊤ r k ( i ) ∥ r k ( i ) ∈ R i } b i , j ( 2 ) = 1 [ d ( 2 ) ( i , j ) ≤ t ( 2 ) ] \begin{aligned} d^{(2)}(i, j) &amp;= \min\{1 - r^\top_j r^{(i)}_k \| r^{(i)}_k\in \mathcal{R}_i\}\\ b_{i,j}^{(2)} &amp;= \mathbb{1}[d^{(2)}(i, j) \leq t^{(2)}] \end{aligned} d(2)(i,j)bi,j(2)=min{1rjrk(i)rk(i)Ri}=1[d(2)(i,j)t(2)]
    NearestNeighborDistanceMetric.distance 计算 d ( 2 ) ( i , j ) d^{(2)}(i, j) d(2)(i,j)

            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)
                cost_matrix = linear_assignment.gate_cost_matrix(
                    self.kf, cost_matrix, tracks, dets, track_indices,
                    detection_indices)
    
                return cost_matrix
    

    将轨迹集合拆分为已确认和未确认的,得到两个集合的索引。
    Track.is_confirmed 查询轨迹的状态。

            # Split track set into confirmed and unconfirmed tracks.
            confirmed_tracks = [
                i for i, t in enumerate(self.tracks) if t.is_confirmed()]
            unconfirmed_tracks = [
                i for i, t in enumerate(self.tracks) if not t.is_confirmed()]
    

    matching_cascade 根据特征将检测框匹配到确认的轨迹。
    传入门矩阵 B = [ b i , j ] \mathit{B} = [b_{i,j}] B=[bi,j] 而不是成本矩阵 C = [ c i , j ] \mathit{C} = [c_{i,j}] C=[ci,j]

            # Associate confirmed tracks using appearance features.
            matches_a, unmatched_tracks_a, unmatched_detections = \
                linear_assignment.matching_cascade(
                    gated_metric, self.metric.matching_threshold, self.max_age,
                    self.tracks, detections, confirmed_tracks)
    

    min_cost_matching 使用匈牙利算法解决线性分配问题。
    传入 iou_cost,尝试关联剩余的轨迹与未确认的轨迹。

            # Associate remaining tracks together with unconfirmed tracks using IOU.
            iou_track_candidates = unconfirmed_tracks + [
                k for k in unmatched_tracks_a if
                self.tracks[k].time_since_update == 1]
            unmatched_tracks_a = [
                k for k in unmatched_tracks_a if
                self.tracks[k].time_since_update != 1]
            matches_b, unmatched_tracks_b, unmatched_detections = \
                linear_assignment.min_cost_matching(
                    iou_matching.iou_cost, self.max_iou_distance, self.tracks,
                    detections, iou_track_candidates, unmatched_detections)
    
            matches = matches_a + matches_b
            unmatched_tracks = list(set(unmatched_tracks_a + unmatched_tracks_b))
            return matches, unmatched_tracks, unmatched_detections
    

    _initiate_track

    _initiate_track
    KalmanFilter.initiate
    Track

    KalmanFilter.initiate 由检测目标构建均值向量与协方差矩阵。

            mean, covariance = self.kf.initiate(detection.to_xyah())
            self.tracks.append(Track(
                mean, covariance, self._next_id, self.n_init, self.max_age,
                detection.feature))
            self._next_id += 1
    

    min_cost_matching

    min_cost_matching
    distance_metric
    linear_assignment

    解决线性分配问题。
    参数:

    • distance_metricCallable[List[Track], List[Detection], List[int], List[int]) -> ndarray。距离度量给出了轨迹和检测的列表以及 N 个轨迹索引和 M 个检测索引的列表。度量应该返回 NxM 维度成本矩阵,其中元素(i,j)是给定轨迹索引中的第 i 个轨迹与给定的检测索引中的第 j 个检测之间的关联成本。
    • max_distance:门控阈值,float。忽略成本大于此值的关联。
    • tracks:列表[track.Track],当前时间步骤的预测轨迹列表。
    • detections:列表[detection.Detection]当前时间步骤的检测列表。
    • track_indices:int 型列表。将cost_matrix中的行映射到轨迹的轨迹索引列表track(见上面的描述)。
    • detection_indices:int 型列表。 将cost_matrix中的列映射到的检测索引列表detections 中的检测(见上面的描述)。

    返回值:
    (List[(int, int)], List[int], List[int])
    返回包含以下三个条目的元组:

    • 匹配的跟踪和检测索引列表。
    • 不匹配的轨迹索引列表。
    • 未匹配的检测索引列表。
        if track_indices is None:
            track_indices = np.arange(len(tracks))
        if detection_indices is None:
            detection_indices = np.arange(len(detections))
    
        if len(detection_indices) == 0 or len(track_indices) == 0:
            return [], track_indices, detection_indices  # Nothing to match.
    

    由距离度量指标计算成本矩阵。设置超过阈值max_distance的成本为固定值,消除差异。
    linear_assignment 关联检测框。

        cost_matrix = distance_metric(
            tracks, detections, track_indices, detection_indices)
        cost_matrix[cost_matrix > max_distance] = max_distance + 1e-5
        indices = linear_assignment(cost_matrix)
    
        matches, unmatched_tracks, unmatched_detections = [], [], []
        for col, detection_idx in enumerate(detection_indices):
            if col not in indices[:, 1]:
                unmatched_detections.append(detection_idx)
        for row, track_idx in enumerate(track_indices):
            if row not in indices[:, 0]:
                unmatched_tracks.append(track_idx)
        for row, col in indices:
            track_idx = track_indices[row]
            detection_idx = detection_indices[col]
            if cost_matrix[row, col] > max_distance:
                unmatched_tracks.append(track_idx)
                unmatched_detections.append(detection_idx)
            else:
                matches.append((track_idx, detection_idx))
        return matches, unmatched_tracks, unmatched_detections
    

    matching_cascade

        if track_indices is None:
            track_indices = list(range(len(tracks)))
        if detection_indices is None:
            detection_indices = list(range(len(detections)))
    

    初始化匹配集matches M ← ∅ M \gets \emptyset M
    未匹配检测集unmatched_detections U ← D U \gets D UD

        unmatched_detections = detection_indices
        matches = []
    

    f o r &ThinSpace; n ∈ { 1 , … , A m a x } &ThinSpace; d o Select tracks by age  T n ← { i ∈ T ∣ a i = n } \begin{aligned} \mathbf{for}&amp; \, n\in\{1,\dots,A_{\rm max}\}\, \mathbf{do}\\ &amp;\text{Select tracks by age } T_n \gets \{i \in T \mid a_i = n\} \end{aligned} forn{1,,Amax}doSelect tracks by age Tn{iTai=n}

        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
    

    [ x i , j ] ← min_cost_matching ( C , T n , U ) M ← M ∪ { ( i , j ) ∣ b i , j ⋅ x i , j &gt; 0 } U ← U ∖ { j ∣ ∑ i b i , j ⋅ x i , j &gt; 0 } [x_{i,j}] \gets \text{min\_cost\_matching}(\mathit{C}, \mathcal{T}_n, \mathcal{U})\\ \mathcal{M} \gets \mathcal{M} \cup \{(i, j) \mid b_{i,j}\cdot x_{i,j} &gt; 0 \}\\ \mathcal{U} \gets \mathcal{U} \setminus \{j \mid \sum_i b_{i,j}\cdot x_{i,j} &gt; 0\} [xi,j]min_cost_matching(C,Tn,U)MM{(i,j)bi,jxi,j>0}UU{jibi,jxi,j>0}
    min_cost_matching 输出的匹配直接满足 b i , j &gt; 0 b_{i,j}&gt;0 bi,j>0

            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
    

    gate_cost_matrix

    基于卡尔曼滤波获得的状态分布,使成本矩阵中的不可行条目无效。
    参数:

    • kf:卡尔曼滤波器。
    • cost_matrix:ndarray NxM 维度成本矩阵,其中 N 是轨迹索引的数量,M 是检测索引的数量,使得条目(i, j)是track[track_indices [i]]detections[detection_indices[j]]关联成本。
    • tracks:列表 [track.Track],当前时间点的预测轨迹列表。
    • detections:列表 [detection.Detection],当前时间步骤的检测列表。
    • track_indices:List [int] 将cost_matrix中的行映射到轨道的轨道索引列表track(见上面的描述)。
    • detection_indices:List [int] 将“cost_matrix”中的列映射到“检测”中的检测的检测索引列表(参见上面的描述)。
    • gated_cost:可选[float] 与不可行关联对应的成本矩阵中的条目设置为此值。默认为非常大的值。
    • only_position:可选[bool] 如果为True,则在门控期间仅考虑状态分布的x,y位置。默认为False。

    返回值:

    • ndarray,返回修改后的成本矩阵。

    chi2inv95 具有N个自由度的卡方分布的0.95分位数的表(包含N=1, ..., 9的值)。 取自 MATLAB/Octave 的 chi2inv 函数并用作 Mahalanobis 门控阈值。
    KalmanFilter.gating_distance 计算状态分布和测量之间的选通距离。

        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
    

    NearestNeighborDistanceMetric

    最近邻距离度量。对于每个目标,返回到目前为止已观察到的所有样本的最近距离。
    参数:

    • metric:str 类型,“euclidean"或者是"cosine”。

    • matching_threshold:float 型,匹配阈值。将距离较大的样本视为无效匹配。

    • budget:int 型(可选),如果不是 None,则最多每个类抽样到为此数字。达到预算时删除最旧的样本。
      属性:

    • samples:Dict [int - > List [ndarray]],从目标身份映射到目前已观察到的样本列表的字典。

     
        def __init__(self, metric, matching_threshold, budget=None):
    
    
            if metric == "euclidean":
                self._metric = _nn_euclidean_distance
            elif metric == "cosine":
                self._metric = _nn_cosine_distance
            else:
                raise ValueError(
                    "Invalid metric; must be either 'euclidean' or 'cosine'")
            self.matching_threshold = matching_threshold
            self.budget = budget
            self.samples = {}
    

    partial_fit

    使用新数据更新距离指标。
    参数:

    • features:ndarray 类型,具有维数 M 的 N 个特征的 N×M 矩阵。
    • targets:ndarray 类型,关联目标标识的整数数组。
    • active_targets:int 型列表,场景中当前存在的目标列表。

    setdefault 如果字典存在键key,返回它的值。如果不存在,插入值为default的键key,并返回defaultdefault默认为None

    由目标及对应特征构造样本字典self.samples并剔除其中不活跃的。

            for feature, target in zip(features, targets):
                self.samples.setdefault(target, []).append(feature)
                if self.budget is not None:
                    self.samples[target] = self.samples[target][-self.budget:]
            self.samples = {k: self.samples[k] for k in active_targets}
    

    ImageViewer

    具有绘图程序和视频捕获功能的图像查看器。

        def __init__(self, update_ms, window_shape=(640, 480), caption="Figure 1"):
            self._window_shape = window_shape
            self._caption = caption
            self._update_ms = update_ms
            self._video_writer = None
            self._user_fun = lambda: None
            self._terminate = False
    
            self.image = np.zeros(self._window_shape + (3, ), dtype=np.uint8)
            self._color = (0, 0, 0)
            self.text_color = (255, 255, 255)
            self.thickness = 1
    
        @property
        def color(self):
            return self._color
    
        @color.setter
        def color(self, value):
            if len(value) != 3:
                raise ValueError("color must be tuple of 3")
            self._color = tuple(int(c) for c in value)
    

    rectangle

    绘制一个矩形。输入矩形参数格式为[x, y, w, h],在矩形左上角放置文本标签。

            pt1 = int(x), int(y)
            pt2 = int(x + w), int(y + h)
            cv2.rectangle(self.image, pt1, pt2, self._color, self.thickness)
    
            if label is not None:
                text_size = cv2.getTextSize(
                    label, cv2.FONT_HERSHEY_PLAIN, 1, self.thickness)
    
                center = pt1[0] + 5, pt1[1] + 5 + text_size[0][1]
                pt2 = pt1[0] + 10 + text_size[0][0], pt1[1] + 10 + \
                    text_size[0][1]
                cv2.rectangle(self.image, pt1, pt2, self._color, -1)
                cv2.putText(self.image, label, center, cv2.FONT_HERSHEY_PLAIN,
                            1, (255, 255, 255), self.thickness)
    

    circle

    绘制圆圈。

            image_size = int(radius + self.thickness + 1.5)  # actually half size
            roi = int(x - image_size), int(y - image_size), \
                int(2 * image_size), int(2 * image_size)
            if not is_in_bounds(self.image, roi):
                return
    
            image = view_roi(self.image, roi)
            center = image.shape[1] // 2, image.shape[0] // 2
            cv2.circle(
                image, center, int(radius + .5), self._color, self.thickness)
            if label is not None:
                cv2.putText(
                    self.image, label, center, cv2.FONT_HERSHEY_PLAIN,
                    2, self.text_color, 2)
    
    

    gaussian

    绘制二维高斯分布的95%置信椭圆。

            # chi2inv(0.95, 2) = 5.9915
            vals, vecs = np.linalg.eigh(5.9915 * covariance)
            indices = vals.argsort()[::-1]
            vals, vecs = np.sqrt(vals[indices]), vecs[:, indices]
    
            center = int(mean[0] + .5), int(mean[1] + .5)
            axes = int(vals[0] + .5), int(vals[1] + .5)
            angle = int(180. * np.arctan2(vecs[1, 0], vecs[0, 0]) / np.pi)
            cv2.ellipse(
                self.image, center, axes, angle, 0, 360, self._color, 2)
            if label is not None:
                cv2.putText(self.image, label, center, cv2.FONT_HERSHEY_PLAIN,
                            2, self.text_color, 2)
    

    annotate

            cv2.putText(self.image, text, (int(x), int(y)), cv2.FONT_HERSHEY_PLAIN,
                        2, self.text_color, 2)
    
    

    colored_points

            if not skip_index_check:
                cond1, cond2 = points[:, 0] >= 0, points[:, 0] < 480
                cond3, cond4 = points[:, 1] >= 0, points[:, 1] < 640
                indices = np.logical_and.reduce((cond1, cond2, cond3, cond4))
                points = points[indices, :]
            if colors is None:
                colors = np.repeat(
                    self._color, len(points)).reshape(3, len(points)).T
            indices = (points + .5).astype(np.int)
            self.image[indices[:, 1], indices[:, 0], :] = colors
    

    enable_videowriter

            fourcc = cv2.VideoWriter_fourcc(*fourcc_string)
            if fps is None:
                fps = int(1000. / self._update_ms)
            self._video_writer = cv2.VideoWriter(
                output_filename, fourcc, fps, self._window_shape)
    
        def disable_videowriter(self):
            """ Disable writing videos.
            """
            self._video_writer = None
    
    

    run

    启动图像查看器。此方法将阻塞,直到用户请求关闭窗口。
    运行传入的函数,保存视频并显示。enable_videowriter 函数会创建 VideoWriter

            if update_fun is not None:
                self._user_fun = update_fun
    
            self._terminate, is_paused = False, False
            # print("ImageViewer is paused, press space to start.")
            while not self._terminate:
                t0 = time.time()
                if not is_paused:
                    self._terminate = not self._user_fun()
                    if self._video_writer is not None:
                        self._video_writer.write(
                            cv2.resize(self.image, self._window_shape))
                t1 = time.time()
                remaining_time = max(1, int(self._update_ms - 1e3*(t1-t0)))
                cv2.imshow(
                    self._caption, cv2.resize(self.image, self._window_shape[:2]))
                key = cv2.waitKey(remaining_time)
                if key & 255 == 27:  # ESC
                    print("terminating")
                    self._terminate = True
                elif key & 255 == 32:  # ' '
                    print("toggeling pause: " + str(not is_paused))
                    is_paused = not is_paused
                elif key & 255 == 115:  # 's'
                    print("stepping")
                    self._terminate = not self._user_fun()
                    is_paused = True
    

    销毁窗口后重新调用imshow

            # Due to a bug in OpenCV we must call imshow after destroying the
            # window. This will make the window appear again as soon as waitKey
            # is called.
            #
            # see https://github.com/Itseez/opencv/issues/4535
            self.image[:] = 0
            cv2.destroyWindow(self._caption)
            cv2.waitKey(1)
            cv2.imshow(self._caption, self.image)
    

    stop

            self._terminate = True
    

    KalmanFilter

    一种简单的卡尔曼滤波器,用于跟踪图像空间中的边界框。8维状态空间[x, y, a, h, vx, vy, va, vh]包含边界框中心位置(x, y),纵横比a,高度h和它们各自的速度。物体运动遵循等速模型。 边界框位置(x, y, a, h)被视为状态空间的直接观察(线性观察模型)。

    创建卡尔曼滤波器模型矩阵self._motion_matself._update_mat

        def __init__(self):
            ndim, dt = 4, 1.
    
            # Create Kalman filter model matrices.
            self._motion_mat = np.eye(2 * ndim, 2 * ndim)
            for i in range(ndim):
                self._motion_mat[i, ndim + i] = dt
            self._update_mat = np.eye(ndim, 2 * ndim)
    

    依据当前状态估计(高度)选择运动和观测不确定性。这些权重控制模型中的不确定性。这有点 hacky。

            # Motion and observation uncertainty are chosen relative to the current
            # state estimate. These weights control the amount of uncertainty in
            # the model. This is a bit hacky.
            self._std_weight_position = 1. / 20
            self._std_weight_velocity = 1. / 160
    

    initiate

    由测量初始化均值向量(8维)和协方差矩阵(8x8维)。
    numpy.r_ 沿第一轴连接切片对象。

            mean_pos = measurement
            mean_vel = np.zeros_like(mean_pos)
            mean = np.r_[mean_pos, mean_vel]
    
            std = [
                2 * self._std_weight_position * measurement[3],
                2 * self._std_weight_position * measurement[3],
                1e-2,
                2 * self._std_weight_position * measurement[3],
                10 * self._std_weight_velocity * measurement[3],
                10 * self._std_weight_velocity * measurement[3],
                1e-5,
                10 * self._std_weight_velocity * measurement[3]]
            covariance = np.diag(np.square(std))
            return mean, covariance
    

    predict

    卡尔曼滤波器由目标上一时刻的均值和协方差进行预测。

    motion_cov是过程噪声 W k W_k Wk 协方差矩阵 Q k Q_k Qk

            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]))
    

    x ^ k ∣ k − 1 = F k x ^ k − 1 ∣ k − 1 + B k u k P k ∣ k − 1 = F k P k − 1 ∣ k − 1 F k ⊤ + Q k \begin{aligned} \hat{\mathrm{x}}_{k|k-1} &amp;= F_k\hat{\mathrm{x}}_{k-1|k-1}+B_k u_k\\ P_{k|k-1}&amp;= F_k P_{k-1|k-1}F^\top_k+Q_k \end{aligned} x^kk1Pkk1=Fkx^k1k1+Bkuk=FkPk1k1Fk+Qk
    self._motion_mat F k F_k Fk 是作用在 x k − 1 \mathrm{x}_{k-1} xk1 上的状态变换模型(/矩阵/矢量)。
    B k B_k Bk 是作用在控制器向量 u k u_k uk 上的输入-控制模型。
    covariance P k ∣ k P_{k|k} Pkk,后验估计误差协方差矩阵,度量估计值的精确程度。

            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
    

    project

    投影状态分布到测量空间。
    参数:

    • mean:ndarray,状态的平均向量(8维数组)。
    • covariance:ndarray,状态的协方差矩阵(8x8维)。

    返回(ndarray,ndarray),返回给定状态估计的预计平均值和协方差矩阵。
    numpy.linalg.multi_dot 在单个函数调用中计算两个或多个数组的点积,同时自动选择最快的求值顺序。

    
            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
    

    update

            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
    

    x ^ k ∣ k = x ^ k ∣ k − 1 + K k y ~ k P k ∣ k = ( I − K k H k ) P k ∣ k − 1 \begin{aligned} \hat{\mathrm{x}}_{k|k} &amp;= \hat{\mathrm{x}}_{k|k-1}+K_k \tilde{\mathrm{y}}_k\\ P_{k|k}&amp;= (I- K_{k}H_k)P_{k|k-1} \end{aligned} x^kkPkk=x^kk1+Kky~k=(IKkHk)Pkk1

            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
    

    gating_distance

    计算状态分布和测量之间的选通距离。可以从 chi2inv95 获得合适的距离阈值。如果only_position为 False,则卡方分布具有4个自由度,否则为2。
    参数:

    • mean:ndarray,状态分布上的平均向量(8维)。
    • covariance:ndarray,状态分布的协方差(8x8维)。
    • measurements:ndarray,N 个测量的 N×4维矩阵,每个矩阵的格式为(x,y,a,h),其中(x,y)是边界框中心位置,纵横比和h高度。
    • only_position:可选[bool],如果为True,则相对于边界进行距离计算盒子中心位置。

    返回,ndarray,返回一个长度为N的数组,其中第i个元素包含(mean,covariance)和measurements [i]之间的平方Mahalanobis距离。

    numpy.linalg.cholesky Cholesky 分解。返回方阵a的 Cholesky 分解 L ∗ L . H L * L.H LL.H,其中 L L L 是下三角形, . H .H .H 是共轭转置算子(如果a是实值则是普通转置)。 a必须是 Hermitian(对称的,如果是实值的)和正定的。实际只返回 L L L
    scipy.linalg.solve_triangular 假设a是三角阵,求解x的等式a x = b

    
            mean, covariance = self.project(mean, covariance)
            if only_position:
                mean, covariance = mean[:2], covariance[:2, :2]
                measurements = measurements[:, :2]
    
            cholesky_factor = np.linalg.cholesky(covariance)
            d = measurements - mean
            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)
            return squared_maha
    

    改进思路

    Challenges on Large Scale Surveillance Video Analysis 跟踪与 DeepSORT 类似,但使用 Re-ranking Person Re-identification with k-reciprocal Encoding 方法。

    参考资料:

    展开全文
  • 为提升运动人体目标的跟踪效果,缩短目标跟踪耗时,提出融合时空特征表示的运动人体目标跟踪算法。利用运动人体目标位置的获取时间关系确定目标初始运动速度,根据目标区域的质心位置计算搜索窗,提取运动人体目标...
  • 多目标跟踪算法综述

    千次阅读 2018-09-12 12:49:24
    参考链接 ...基于确定性优化的离线多目标跟踪算法——基于最小代价流优化的多目标跟踪算法 基于机器学习的确定性推导在线目标跟踪算法—— 基于马尔科夫决策的多目标跟踪算法 基于局部流特征的近似在...

    参考链接

    Table of Contents

    基于贝叶斯概率模型的在线多目标跟踪算法——MHT算法

    1:从卡尔曼滤波器讲起

    2:MHT

    基于贝叶斯概率模型的在线多目标跟踪算法——基于检测可信度的粒子滤波算法

    基于确定性优化的离线多目标跟踪算法——基于最小代价流优化的多目标跟踪算法

    基于机器学习的确定性推导在线目标跟踪算法—— 基于马尔科夫决策的多目标跟踪算法

    基于局部流特征的近似在线多目标跟踪

    视觉多目标跟踪小结


    基于贝叶斯概率模型的在线多目标跟踪算法——MHT算法

    1:从卡尔曼滤波器讲起

    卡尔曼滤波器

    简单来说就是:根据任务本身的计算流程,设计模型参数:模型高斯噪声、从预测值估计观测值的H、卡尔曼系数K(相信预测模型还是详细观测模型;把残差的表现形式从观测域转换到了状态域)。不断在预测与更新K之间循环

    2:MHT

    其中公式右侧

    • 第一项表示基于前期假设集合和当前假设的观察似然概率,即在历史关联的基础上,当关联成立时,表现出当前观测Z(k)的概率;——历史估计和当前估计的情况下,当前观测的概率
    • 第二项表示当前假设的似然概率,即在历史关联的基础上,当前关联假设的概率;——历史观察和历史估计的情况下。当前估计的概率
    • 第三项表示前期假设集合后验概率。——历史观察的情况下,历史估计的概率
    • c是贝叶斯公式中的分母,对于当前观测已知的条件,可以认为是一个常数。

    从上式中可以看出,总体的假设后验概率可以表示为此三项的乘积。而公式第三项表示k-1时的后验概率,因此,只考虑第一项和第二项就可以得到一个递推公式

    如何对第一项和第二项进行建模?MHT采用了二个概率模型:

    • 用均匀分布和高斯分布对关联对应的检测观察建模——第一项
    • 用泊松分布对当前假设的似然概率建模——第二项——历史累计概率密度的连乘

    æ³æ¾åå¸å®ä¾

    前者表示,当观测是来自一个轨迹T时,它符合T的高斯分布,否则观测是一个均匀分布的噪声。

    后者表示,在误检和新对象出现概率确定的情况下,出现当前关联的可能性可以通过泊松分布和二项分布的乘积表示。在以上假设下,关联假设的后验分布是历史累计概率密度的连乘,转化为对数形式,可以看出总体后验概率的对数是每一步观察似然和关联假设似然的求和。因此,选择最佳的关联假设,转化为观察似然和关联假设似然累计求和的最大化。

    基于贝叶斯概率模型的在线多目标跟踪算法——基于检测可信度的粒子滤波算法

    这个算法分为两个步骤:

    • 对每一帧的检测结果(图左),利用贪心匹配算法与已有的对象轨迹进行关联(图中)。
      • 这一步会得到每个检测结果与原始轨迹的相关性
    • 利用关联结果,计算每个对象的粒子群权重,作为粒子滤波框架中的观察似然概率。

    基于确定性优化的离线多目标跟踪算法——基于最小代价流优化的多目标跟踪算法

     

    给出所有的检测结果,求出一个全局最优的多条轨迹。方法就是上面这个公式,求个最优解。

    基于机器学习的确定性推导在线目标跟踪算法—— 基于马尔科夫决策的多目标跟踪算法

    基于局部流特征的近似在线多目标跟踪

    视觉多目标跟踪小结

    相比单目标跟踪问题,多目标跟踪面临更多的子问题,主要的区别在于跟踪对象不再确定不变,因此必须考虑对象之间的交互,新对象的进入和离开视场对象的跟踪终止。由于解决这些问题的角度不同,多目标跟踪算法的形式也是各种各样。尽管算法思路相差较大,但是主要的算法框架和其中的关键部分基本类似。在线多目标跟踪中,基本的算法流程仍然是基于状态预测更新的框架;而离线多目标跟踪中,基于图模型的构造和求解是主要的框架。两种类型中,如何有效的设计和学习检测之间的匹配亲和度,或者轨迹与观测的一致性是非常关键的步骤。深度学习是解决这个问题的非常有效的工具,在最新的多目标跟踪算法中已经表现出明显的优势,深度学习的进一步应用是多目标跟踪领域发展的必然趋势

    真正决定算法性能的可能并不是这些跟踪算法框架,而是一些更加基础的内容,比如如何构造检测结果的表观模型才能反应目标的特征,采用什么样的特征才能使得同一个目标更像,不同目标差异较大。又比如,如何判断检测结果是不是非常准确,如果不准确的话,特征匹配该如何计算匹配相似度。

    对于特征表示的研究,目前广泛采用深度学习的方法

     

    展开全文
  • 目标跟踪算法

    千次阅读 2019-10-31 16:52:28
    目标跟踪算法的跟踪 一、目标跟踪算法简介 1.1 主要任务 1.1.1 Online Visual Tracker BenchMark 1.1.2 VOT 1.2 难点与挑战 目标遮挡、目标消失、运动模糊、目标和环境的剧烈变化、目标的高速运动、相机的抖动...
  • 做传感器研究的朋友,这是关于基于特征融合的目标跟踪算法的一篇文章不错的。
  • 针对美国联合导控器实验室提出的数据融合模型中传感器多目标跟踪算法的仿真验证和定量评估的问题,采用模块化和开放性结构思想,面向对象编程及Visual C++与Matlab混合编程技术,构建了一类通用的可视化传感器...
  • 针对目标视频跟踪中需要主要解决的目标冲突、合并以及分离等问题,提出了基于自适应混合滤波的多目标跟踪算法。采用混合高斯背景建模法获得前景图,并对图中阴影采用一种简化去除算法,即判断前景像素时,将HSV...
  • 针对上述问题,文中提出基于外观模型的自适应加权目标跟踪算法(AWMA). 首先使用 PLS 对目.标区域逐步建立个外观模型. 然后根据各外观模型中特征的重要性及目标的显著度建立自适应权重的综合模.型,融合个外观...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 107,572
精华内容 43,028
关键字:

多站多目标跟踪算法