obj_tracker.md 6.6 KB
Newer Older
F
fix png  
feilong 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
# 目标跟踪(track by detection)

![](pic/tracker.gif)

在视频分析(或视频结构化)应用开发中,多目标跟踪是非常重要的一个环节。它能有效弥补上一个目标检测环节中算法的不足,如检测算法输出坐标不稳定、漏检等。与此同时,跟踪算法输出的目标轨迹(track-id)对于应用下阶段的行为分析环节也有着至关重要的作用。下面是常见视频分析类应用系统结构:

![](pic/video_process.png)

目标检测算法输出单帧检测结果,目标跟踪算法负责将前后2帧中的目标关联起来、给予唯一标识track-id。假设t帧中检测到了M个目标,t+1帧中检测到了N个目标,跟踪算法本质上是M->N的匹配关联过程。

![](pic/t_and_t+1_match.png)

匹配过程中,目标可以分为以下三大类:

1. `matched_tracks`,t帧目标出现,t+1帧该目标仍然出现,算法匹配上。
2. `unmatched_tracks`,t帧目标出现,t+1帧该目标消失,算法未匹配上。
3. `unmatched_detections`,t帧目标不存在,t+1帧该目标出现,新增检测目标。

其中,对于2和3来说,跟踪算法需要考虑:
> t帧目标出现,t+1帧目标其实仍然存在,但是检测算法出现短暂漏检,误认为其消失。此时的解决方案是: 某帧未被匹配到的tracks不要立即清除,而是做若干帧的缓存,等待若干帧后检测算法恢复检测<br>
> t帧目标不存在,t+1帧该目标仍然不存在,但是检测算法出现短暂误检,误认为其出现。此时的解决方案是:新增的检测目标不要立即生效,而是做若干帧的缓存,等检测算法连续检测超过若干帧、并且都能匹配关联上后再生效

之所以要考虑以上2点,主要原因是对于连续视频帧而言,大部分检测算法基本无法做到100%连续、稳定检测,出现短暂的误检、漏检非常正常。

<br/>

**题目上下文说明**:

现假设有以下跟踪代码,

```python
# 定义跟踪算法类
class Tracker(object):
  # 初始化参数
  def __init__(self, max_age=1, min_hits=3, iou_threshold=0.3):
    self.max_age = max_age
    self.min_hits = min_hits
    self.iou_threshold = iou_threshold
    self.trackers = []
    self.frame_count = 0

  # 跟踪函数,每帧检测结果返回后,调用一次update
  def update(self, dets=np.empty((0, 5))):
    self.frame_count += 1
    
    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)
    
    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, self.iou_threshold)

    # 后处理逻辑
    # TO-DO your code...

    # 返回跟踪结果 [[left, top, right, bottom, track-id]...]
    if(len(ret) > 0):
      return np.concatenate(ret)
    return np.empty((0, 5))
```

其中:

1. `self.max_age`代表跟踪算法允许出现的最大漏检帧数
2. `self.min_hints`代表跟踪算法要求的最低连续匹配帧数
3. `self.trackers`代表跟踪算法维持的目标集合(已生成track-id)
4. `update(self, dets)`代表跟踪函数,其中参数`dets`代表t+1帧中目标检测结果list[[left, top, right, bottom, score]...],即t+1帧中待匹配的detections
5. `associate_detections_to_trackers(...)` 代表IOU+卡尔曼滤波匹配算法,返回上面提到的`matched_tracks``unmatched_tracks``unmatched_detections`三个值
6. `time_since_update`代表目标当前漏检帧数
7. `hit_streak`代表目标当前连续匹配帧数

请你根据上面陈述补充TO-DO部分代码。

## 正确答案

```python
    # 更新matched_tracks
    for m in matched:
      self.trackers[m[1]].update(dets[m[0], :])

    # 初始化unmatched_detections,假设是当前帧新出现的检测目标
    for i in unmatched_dets:
        trk = KalmanBoxTracker(dets[i,:])
        self.trackers.append(trk)

    i = len(self.trackers)
    for trk in reversed(self.trackers):
        d = trk.get_state()[0]

        # 输出满足条件的tracks
        if (trk.time_since_update <= self.max_age) 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))
        
        i -= 1
        # 移除超过self.max_age次的漏检目标
        if(trk.time_since_update > self.max_age):
          self.trackers.pop(i)
```

## 未考虑unmatched_detections

```python
    # 更新matched_tracks
    for m in matched:
      self.trackers[m[1]].update(dets[m[0], :])

    i = len(self.trackers)
    for trk in reversed(self.trackers):
        d = trk.get_state()[0]
        
        # 输出满足条件的tracks
        if (trk.time_since_update <= self.max_age) 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))
        
        i -= 1
        # 移除超过self.max_age次的漏检目标
        if(trk.time_since_update > self.max_age):
          self.trackers.pop(i)
```

## 未考虑移除长时间未检测到的缓存数据

```python
    # 更新matched_tracks
    for m in matched:
      self.trackers[m[1]].update(dets[m[0], :])

    # 初始化unmatched_detections,假设是当前帧新出现的检测目标
    for i in unmatched_dets:
        trk = KalmanBoxTracker(dets[i,:])
        self.trackers.append(trk)
    
    for trk in reversed(self.trackers):
        d = trk.get_state()[0]

        # 输出满足条件的tracks
        if (trk.time_since_update <= self.max_age) 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))
```

## 未考虑需要连续检出并匹配self.min_hints次才生效

```python
    # 更新matched_tracks
    for m in matched:
      self.trackers[m[1]].update(dets[m[0], :])

    # 初始化unmatched_detections,假设是当前帧新出现的检测目标
    for i in unmatched_dets:
        trk = KalmanBoxTracker(dets[i,:])
        self.trackers.append(trk)

    i = len(self.trackers)
    for trk in reversed(self.trackers):
        d = trk.get_state()[0]

        # 输出满足条件的tracks
        if (trk.time_since_update <= self.max_age):
          ret.append(np.concatenate((d,[trk.id+1])).reshape(1,-1))
        
        i -= 1
        # 移除超过self.max_age次的漏检目标
        if(trk.time_since_update > self.max_age):
          self.trackers.pop(i)
```