ByteTrack 数据集和后处理

一、ByteTracker

1.1 数据集

1.1.1 数据集转换

  • MOT17
    • 只使用 FRCNN 视频序列

    • 图像信息: {'file_name': 'MOT17-02-FRCNN/img1/000001.jpg', 'id': 1, 'frame_id': 1, 'prev_image_id': -1, 'next_image_id': 2, 'video_id': 1, 'height': 1080, 'width': 1920}

      • id:整个训练集中的序号,从1开始。image_cnt + i + 1,其中 image_cnt 在本序列中始终保持一个值,就是一个视频序列的帧数
      • frame_id:每个视频序列中的序号,从1开始, 验证集的 frame_id 也是从 1 开始的
      • prev_image_id:整个训练集中的前一帧的序号,若是当前视频中的第一帧,则为-1
      • next_image_id:整个训练集中的前一帧的序号,若是当前视频中的最后一帧,则为-1
    • annotations: 只保留 的信息,其它物体的去掉。 {'id': 3851, 'category_id': 1, 'image_id': 159, 'track_id': 7, 'bbox': [1000.0, 445.0, 36.0, 99.0], 'conf': 1.0, 'iscrowd': 0, 'area': 3564.0}

      • id:对象 ID,这个 object 在整个数据集中的序号
      • category_id:根据gt.txt文件中,原本类别是非静态的行人为1,其余人是-1,其他事物过滤
      • image_id:image_cnt + frame_id,其中 image_cnt 在本序列中始终保持一个值,就是一个视频序列的帧数
      • track_id:轨迹序号,就是一个人在N帧中的同一个序号。因为过滤掉了其他事物的轨迹序号,因此需要重新搞下
          category_id = 1  # pedestrian(non-static)
          if not track_id == tid_last:
               tid_curr += 1
               tid_last = track_id
        
      • bbox:目标框 [x, y, w, h]
      • conf:置信度
      • iscrowd:0
      • area:目标的面积
    • 进一步理解


  • CrowdHuman,不是视频序列数据,就是放在一起的图像,不过图像命名 273271,c9db000d5146c15.jpg 逗号前的是 id
    • 图像信息:{'file_name': '273271,c9db000d5146c15.jpg', 'id': 1, 'height': 1080, 'width': 1920}
      • id:图像在整个数据集中的序号
      • height:1080
      • width:1920
    • annotations {'id': 1, 'category_id': 1, 'image_id': 1, 'track_id': -1, 'bbox_vis': [72, 202, 163, 398], 'bbox': [72, 202, 163, 503], 'area': 81989, 'iscrowd': 0}
      • id:对象 ID,一个物体在整个数据集中的序号
      • category_id:1(人),0 默认是背景
      • image_id:图像 ID,物体所在图像,在整个数据集中的序号
      • track_id:-1,恒为 -1 。因为这个数据是用来做检测人的,没有轨迹信息
      • bbox_vis:检测框可视框
      • bbox:检测框全身框 [x_l, y_u, w, h], 这个根据自己来,主要是在训练检测模型时,也许重写 get_loader
      • area:面积
      • iscrowd:单个的object是0,多个是1

1.1.2 混合不同的数据集

  • python3 tools/mix_data_ablation.py,合成在一个 json 文件中
    • CrowdHumanMOT17 half train 训练,在 MOT17 half val 上测试
    • 根据说明创建路径和软链接
    • json 包括 imagesannotationsvideoscategories 四个部分
      • images: 是一个 list,包含每个图像信息,一个图像信息是一个 dict,如上所述
        • {'file_name': 'MOT17-02-FRCNN/img1/000001.jpg', 'id': 1, 'frame_id': 1, 'prev_image_id': -1, 'next_image_id': 2, 'video_id': 1, 'height': 1080, 'width': 1920}
      • annotations: 是一个 list,包含每个图像信息,一个图像信息是一个 dict,如上所述
        • {'id': 302, 'category_id': 1, 'image_id': 1, 'track_id': 1, 'bbox': [1338.0, 418.0, 167.0, 379.0], 'conf': 1.0, 'iscrowd': 0, 'area': 63293.0}
      • videos: [{'id': 1, 'file_name': 'MOT17-02-FRCNN'}, {'id': 2, 'file_name': 'MOT17-04-FRCNN'}, {'id': 3, 'file_name': 'MOT17-05-FRCNN'}, {'id': 4, 'file_name': 'MOT17-09-FRCNN'}, {'id': 5, 'file_name': 'MOT17-10-FRCNN'}, {'id': 6, 'file_name': 'MOT17-11-FRCNN'}, {'id': 7, 'file_name': 'MOT17-13-FRCNN'}]
      • categories: [{'id': 1, 'name': 'pedestrian'}]
      • crowdhuman 内容的修改, 因为是行人检测数据,所以需要改成 MOT17 的格式,结合 1.1.1 小节
        • images: 添加 frame_id :就是这个数据集中的图像从1开始、prev_image_id原先的 id+10000next_image_id原先的 id+10000video_id:10 ;修改 id原先的 id+10000prev_image_idnext_image_idid 是一样的 (MOT17 是不同的,例如 id: 201, frame_id: 201, prev_image_id: 200, next_image_id: 202)
        • annotations: ann['id'] = ann['id'] + max_ann (对象 ID 变换),ann['image_id'] = ann['image_id'] + max_img (图像ID 变换)
      • 就是图像 id是 image_id,对象 id 是 ann[‘id’]

1.2 Byte 后处理

  • 检测模型 yolox 预处理
    • 测试尺寸 800*1440(h*w)
    • cv2.imread() 读取图像
    • 生成一个 800*1440 的 全为 114 的数组 padded_img
    • 然后 r = min(input_size[0] / img.shape[0], input_size[1] / img.shape[1]),取最小缩放边缩放图像
    • 然后填充到 padded_imgbrg->rgb
    • 除以 255.0,减去 mean=[0.485, 0.456, 0.406],除以方差 std=[0.229, 0.224, 0.225] (PS: 在最新的yolox中已经没有均值和方差了)
    • hwc->chw
  • 后处理
    • 模型输出是 [1, 23625, 6],经过后处理,输出是[N, 7]

1.2.1 tracker 更新步骤

更新 tracker,获得当前帧活跃的 tracker (包括新的 tracker)使用 tracker/byte_tracker.py BYTETracker 类中 update 函数
BYTETracker 类时用来更新每一帧的 tracker,STrack 是初始化每个 tracker 的类

1. tracker 介绍

注意区分 tracker 的种类状态两个概念

  • 先列举下所有种类和状态的 tracker,5 种

    • 正常的,未激活
    • 正常的,激活
    • 丢失的,激活
    • 删除的,激活
    • 删除的,未激活
  • tracker 的种类0-新的1-正常的2-丢失的3-删除的, 由 state 属性标记

    class TrackState(object):
    	New = 0
    	Tracked = 1
    	Lost = 2
    	Removed = 3
    
  • tracker 的状态激活休眠(未激活),由 is_activated 属性标记

  • 新的检测目标 初始化为 tracker,state=0,is_activated =False

    # 一个 tracker实例的基本类(包含 tracker 的基本属性)
    class BaseTrack(object):
    	_count = 0
    	track_id = 0
    	is_activated = False
    	state = TrackState.New
    	
    	history = OrderedDict()
    	features = []
    	curr_feature = None
    	score = 0
    	start_frame = 0
    	frame_id = 0
    	time_since_update = 0
    
    # STrack 类,用来初始化每个 tracker 实例
    class STrack(BaseTrack):
    	shared_kalman = KalmanFilter()
    	def __init__(self, tlwh, score):
    		
    		# wait activate
    		self._tlwh = np.asarray(tlwh, dtype=np.float)
    		self.kalman_filter = None
    		self.mean, self.covariance = None, None
    		self.is_activated = False
    		
    		self.score = score
    		self.tracklet_len = 0
    

2. 步骤
  • 保存当前帧处理结束之后的 tracker
    activated_starcks = []   # 激活列表
    refind_stracks = []      # 重新列表
    lost_stracks = []        # 丢失列表
    removed_stracks = []     # 删除列表
    

  1. 筛选当前帧的检测结果

    # 0.5 高分检测框,score 是 cls_conf*obj_conf
    remain_inds = scores > self.args.track_thresh    
    
    inds_low = scores > 0.1    # 低分检测框最小值
    inds_high = scores < self.args.track_thresh  # 低分检测框最大值
    inds_second = np.logical_and(inds_low, inds_high)    # 0.1
    
    # 高分检测框以及置信度(cls_conf*obj_conf)
    dets = bboxes[remain_inds]
    scores_keep = scores[remain_inds]
    
    # 低分检测框以及置信度(cls_conf*obj_conf)
    dets_second = bboxes[inds_second]
    scores_second = scores[inds_second]
    
  2. 区分上一帧中休眠和激活两种状态的 tracker

    • 根据 trackers 的 状态,区分(新的)休眠、(正常的)激活,不论高分和低分检测框,都是优先和(正常的)激活的 tracker 关联
    • BYTETracker 类中self.tracked_stracks 保存的是(上一帧)正常的(激活) tracker以及前一帧新的tracker(休眠)
    • 注意,这里新的tracker指的是每个tracker的第一个(帧),连续两帧才会从休眠改为激活;只有新的tracker才是休眠状态。丢失、删除种类的tracker都是激活状态,因为丢失和删除的tracker肯定是经过激活后的,激活之后的tracker状态不会变,只会改变种类
    unconfirmed = []
    tracked_stracks = []  # type: list[STrack]
    for track in self.tracked_stracks:
    if not track.is_activated:
    	unconfirmed.append(track)     # 休眠状态的trackers / 新的
    else:
    	tracked_stracks.append(track) # 优先关联激活状态的(正常的)trackers
    
  3. 第一次关联,使用高分检测框

    这里关联的(上一帧) trackers(状态是激活的),新的、正常的、丢失的删除的 都可以和高分检测框 匹配(没有新的、删除的,注意这里丢失的trackers也会关联)
    若匹配到正常的tracker,则更新位置,放入 activated_starcks 列表
    若匹配到丢失的tracker,则将种类从丢失改为正常,状态改为激活,放入 refind_stracks 列表
    与第二次关联的区别

    if len(dets) > 0:
    	'''Detections'''
    	# 将检测框初始化为 tracker
    	# bbox和score;均值、方差为None、is_activated=False、tracklet_len=0、state=0
    	detections = [STrack(STrack.tlbr_to_tlwh(tlbr), s) for (tlbr, s) in zip(dets, scores_keep)]
    else:
    	detections = []
    
    ''' Step 2: First association, with high score detection boxes'''
    # 将正常的 tracker 和 已丢失的 tracker,根据 tracker_id 放在一起
    strack_pool = joint_stracks(tracked_stracks, self.lost_stracks) 
    # Predict the current location with KF,更新均值和方差
    # 卡尔曼预测,例 len(strack_pool)=17
    STrack.multi_predict(strack_pool)   
    
    # 计算trackers和当前(高分)检测框的iou,然后 dists=1-iou,即iou越大dsits中的值越小,代价矩阵。例len(detections)=20,dists.shape=[17, 20]
    dists = matching.iou_distance(strack_pool, detections) 
    if not self.args.mot20:
    	# dists=1-(iou 和 当前检测框的置信度相乘),先把score(20,)repeat成(17, 20)
    	# 确保代价函数更可信?
    	dists = matching.fuse_score(dists, detections)
    # 仅根据代价函数进行数据关联,match_thresh=0.8,匈牙利&KM。
    # matches.shape=[17,2], u_track:[], u_detection:[16,18,19]
    matches, u_track, u_detection = matching.linear_assignment(dists, thresh=self.args.match_thresh)
    
    # 根据匹配到的检测框,更新参数
    for itracked, idet in matches:
    	track = strack_pool[itracked]
    	det = detections[idet]
    	if track.state == TrackState.Tracked:
    	  # 正常的(state=1) trackers 直接使用 det 更新
    	  # 更新 tracklet_len,frame_id,坐标,置信度,卡尔曼的均值和方差,state=1,is_activated=True,track_id 不变
    		track.update(detections[idet], self.frame_id)
    		activated_starcks.append(track) # 放入激活列表
    	else:
    	  # 若不是正常tracker(这里只有丢失的),丢失的tracker根据det更新参数
    	  # tracklet_len=0,frame_id,坐标,置信度,卡尔曼的均值和方差,state=1,is_activated=True,track_id 不变
    		track.re_activate(det, self.frame_id, new_id=False)
    		refind_stracks.append(track) # 放入重新列表
    
  4. 第二次关联,与低分数检测框

    将上一轮 strack_pool 中没有匹配到的trackers:u_track,与低分检测框(detections_second)匹。strack_pool 中是上一帧的正常的,激活状态的 tracker,以及 丢失的,激活的tracker
    注意: u_track 会有一个种类过滤,state=1(正常的tracker) 的才会在第二次关联中匹配。丢失的(不会在第二轮中匹配,与第一次关联的区别
    注意:新的、删除的,本来就不在 strack_pool,从而不会在 u_track

    ''' Step 3: Second association, with low score detection boxes'''
    if len(dets_second) > 0:
    	'''Detections'''
    	detections_second = [STrack(STrack.tlbr_to_tlwh(tlbr), s) for (tlbr, s) in zip(dets_second, scores_second)]
    	else:
    		detections_second = []
    # u_track 高分检测框没有匹配到的 trackers,这里有一个tracker的种类过滤
    r_tracked_stracks = [strack_pool[i] for i in u_track if strack_pool[i].state == TrackState.Tracked]   
    # 以下和第一次关联相同
    dists = matching.iou_distance(r_tracked_stracks, detections_second)
    matches, u_track, u_detection_second = matching.linear_assignment(dists, thresh=0.5)
    for itracked, idet in matches:
    	track = r_tracked_stracks[itracked]
    	det = detections_second[idet]
    	if track.state == TrackState.Tracked:
    		track.update(det, self.frame_id)
    		activated_starcks.append(track)
    	else:  # 这个 else 分支应该多余了
    		track.re_activate(det, self.frame_id, new_id=False)
    		refind_stracks.append(track)
    
  5. 高低分检测结果关联都没有匹配到的(上一帧的)tracker(正常、丢失两种),其种类设为 丢失

    丢失的tracker,种类 设为丢失种类(状态没有变,还是激活状态),添加到丢失列表 lost_stracks
    后续若有一帧匹配到了,就重新激活,若 30帧内,没有匹配到则设为删除种类

    for it in u_track:
    	track = r_tracked_stracks[it]
    	if not track.state == TrackState.Lost:
    	   # 若不是丢失种类的tracker,改变种类,放入 lost_stracks
    		track.mark_lost()
    		lost_stracks.append(track)
    
  6. 更新(上一帧的)新的,休眠状态的 tracker,与 没有匹配到 tracker 的 高分检测框关联

    休眠状态的 tracker,是上一帧没有匹配到任何 tracker 的高分检测框(一个全新的tracker,tracker 初始化is_activate=False),因为只有连续两帧匹配到,才会设置is_activate=True,即状态为激活
    若匹配到就更新,加入到 activated_starcks 列表
    没有匹配到直接设为删除种类,加入删除列表。注意此种 tracker 种类为删除,状态是未激活。还有连续30帧没有匹配到,种类为删除,状态是激活。

    # 第一次关联没有匹配到的高分检测框
    detections = [detections[i] for i in u_detection]
    # 与休眠状态的tracker匹配 
    dists = matching.iou_distance(unconfirmed, detections)   
    if not self.args.mot20:
    	dists = matching.fuse_score(dists, detections)
    matches, u_unconfirmed, u_detection = matching.linear_assignment(dists, thresh=0.7)
    for itracked, idet in matches: 
    	# 匹配到休眠的 tracker,将其参数和状态更新,放入到激活列表中
    	unconfirmed[itracked].update(detections[idet], self.frame_id)
    	activated_starcks.append(unconfirmed[itracked])
    for it in u_unconfirmed:   
    	# 没有关联的tracker,种类设为3(删除种类),放入删除列表
    	track = unconfirmed[it]
    	track.mark_removed()
    	removed_stracks.append(track)
    
  7. 初始化新的tracker,处理没有匹配到 任何 tracker的 高分检测框,作为新的tracker

    注意 state1(正常种类)is_activated 设为 False,即状态是未激活,放入到激活列表 activated_starcks
    只有处理全视频第一帧图像时,新的 tracker状态才被设为激活
    等到下一帧匹配到检测框才被设为激活状态,即连续两帧都检测并且匹配到

    # 对没有匹配到的检测框判断,<0.6 的过滤掉,>0.6 更新 tracker_id,均值、方差,状态,is_activate=False
    for inew in u_detection:
    	track = detections[inew]
    	if track.score < self.det_thresh:
    		continue
    	track.activate(self.kalman_filter, self.frame_id)
    	activated_starcks.append(track)
    
  8. 更新上一帧丢失种类的tracker当前帧与此tracker最近帧超过 30帧,设为删除种类,加入删除列表

    # lost_stracks 中记载的是丢失的traker,如果大于 30 帧,则设为删除种类
    for track in self.lost_stracks:
    	# 有的丢失 tracker 在第一次关联中已经匹配到了,因此 end_frame 已经更新    
    	if self.frame_id - track.end_frame > self.max_time_lost:
    		track.mark_removed()
    		removed_stracks.append(track)   # 放入删除列表
    
  9. 整合与更新

    • 更新所有的tracker的种类和状态

    activated_starcks[]:包含正常种类,激活状态的tracker;包含正常的,未激活状态的(新的)tracker
    refind_stracks[]:包含正常的,激活状态的tracker(丢失又捡回来的tracker)
    lost_stracks[]:包含丢失的,激活状态的 tracker
    removed_stracks[]:包含删除的,激活状态的tracker;包含删除的;未激活状态的(新的)tracker

    • 所有帧的所有tracker状态,会根据上面四个列表的结果更新

    self.tracked_stracks:包含正常的,激活状态的tracker、包含正常的;未激活状态的(新的)trcker;
    self.lost_stracks:包含丢失的,激活状态的tracker
    self.removed_stracks:包含删除的,激活状态的tracker

    # 根据当前帧的匹配结果,过滤前一帧 (正常种类)tracker,包括(上一帧)新的、正常的,tracked_stracks 中若在当前帧匹配到了,state 必会设为1(正常种类)
    self.tracked_stracks = [t for t in self.tracked_stracks if t.state == TrackState.Tracked]
    
    # 上一步(过滤的)tracked_stracks 和 当前的激活列表(主要是当前帧产生的新的 tracker)
    self.tracked_stracks = joint_stracks(self.tracked_stracks, activated_starcks)
    
    # 上一步 tracked_stracks 和 当前的重新列表(主要是丢失的又重新匹配的)
    self.tracked_stracks = joint_stracks(self.tracked_stracks, refind_stracks)
    
    # self.lost_stracks 里是上一帧判定为丢失的 trackers,在经过前面的处理,有可能放入了重新列表和删除列表
    # 返回 self.lost_stracks -(self.lost_stracks and self.tracked_stracks)的 tracker,这一步就是将重新列表里的tracker从 self.lost_stracks 删除
    self.lost_stracks = sub_stracks(self.lost_stracks, self.tracked_stracks)
    # 合并self.lost_stracks 和 当前帧的新的丢失的 tracker 
    self.lost_stracks.extend(lost_stracks)
    
    # 返回 self.lost_stracks -(self.lost_stracks and removed_stracks)的 tracker
    self.lost_stracks = sub_stracks(self.lost_stracks, self.removed_stracks)
    
    # 合并以前帧和当前帧删除类型的 traker 
    self.removed_stracks.extend(removed_stracks)
    
    # 筛选出 tracked_stracks 和 lost_stracks iou>0.85 的,然后比较时间跨度,时间跨度小的 tracker 去掉
    # 就是去重,在 tracked_stracks 和 lost_stracks 中,将iou大的选择一个时间久的tracker保留
    self.tracked_stracks, self.lost_stracks = remove_duplicate_stracks(self.tracked_stracks, self.lost_stracks)
    
    # 输出,当前帧激活状态的tracker,新的、丢失的、删除的都不会输出
    output_stracks = [track for track in self.tracked_stracks if track.is_activated]
    

总结

  • 删除列表 removed_stracks[]
    • 只有一帧的新的tracker,在当前帧没有匹配到
    • 原先就是丢失 tracker,30帧内没有匹配到新的
  • 激活列表 activated_starcks[]
    • 不是丢失的、删除的,是正常的且激活状态的,在当前帧匹配到了
    • 新的 tracker (没有任何匹配的高分检测框)
  • 重新列表 refind_stracks[]
    • 丢失的、删除的,在当前帧匹配到了
  • 丢失列表 lost_stracks[]
    • 高、低分关联都没有匹配到的 tracker

1.2.2 当前帧结果保存

会有一个 垂直判断 vertical = tlwh[2] / tlwh[3] > 1.6,为 False 时才会算作最后目标
这个是行人的标准,若训练自己的数据,需要根据实际修改,否则容易出现漏跟踪!

online_targets = tracker.update(outputs[0], [img_info['height'], img_info['width']], exp.test_size)
online_tlwhs = []
online_ids = []
online_scores = []
for t in online_targets:
	tlwh = t.tlwh
	tid = t.track_id
	vertical = tlwh[2] / tlwh[3] > 1.6    # False 才会最终入选
	if tlwh[2] * tlwh[3] > args.min_box_area and not vertical:
		online_tlwhs.append(tlwh)
		online_ids.append(tid)
		online_scores.append(t.score)
results.append((frame_id + 1, online_tlwhs, online_ids, online_scores))

1.2.3 流程图

ByteTrack 数据集和后处理_第1张图片


1.3 ByteTrack 评估

1.3.1 评估流程

  • 代码中评估使用的是 MOT17 中的 train,data loader 使用的和训练检测模型一样,评估使用的是 yolox/evaluators/mot_evaluator.py 中的 MOTEvaluator 类,调用其 evaluate() 函数

  • 步骤

    • 遍历每张图,得到检测结果 outputs,将 outputs 搞成 coco 格式,就是annnotations 格式,然后放入一个 data_list 中;同时将每个 outputs 中的检测框经过 Bytetrack 后处理,得到每张图中的所有 tracker,放入 results
      • results 中的格式为 results.append((frame_id, online_tlwhs, online_ids, online_scores))
      • 按照视频序列的名字,将 rsult 单独写入TXT
      • 写入格式为:save_format = '{frame},{id},{x1},{y1},{w},{h},{s},-1,-1,-1\n'
      • 视频帧,tracker id,x1,y1,w,h,socre,-1,-1,-1
    • 进入 evaluate_prediction 函数,将检测结果转化成**标准的 coco 格式 **, cocoDt,还有读取的 val_half.hsoncocoGt
    • 使用 coco 的标准接口得到模型评估结果,至此检测模型评估完成
    • 使用 py-motmetrics库 进行计算,指标和代码,指标

1.3.2 跟踪评估的数据选择和过滤

都是根据标准库来计算的,就是一个视频序列对应一个 gt.txtp.txt

  • gt 的标注的标准格式如上所示,参考 mot17,读入 pandas 是[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VZ7bpboY-1638276570201)(./1638274177722.png)]
    • 不是行人的 confidence 都 小于 1
    • 制作 JSON 文件,用于训练检测的时候,不是人的都过滤掉,作为背景;静止的人、车上的人、(类人)等, c a t e g o r y 的 i d = − 1 category 的 id = -1 categoryid=1,上一步 已经 过滤掉 非行人了
    • 遮挡行人,有一些已经全部遮挡了,代码里还是没有去掉,DETRAC 数据集也是,这个该怎么处理呢?
      ByteTrack 数据集和后处理_第2张图片
  • 跟踪的结果,可以是下图
    ByteTrack 数据集和后处理_第3张图片

  • COCOeval 讲解
    • 在评估过程中,会在 cocoeval.pyCOCOeval 类中_prepare 函数 中,过滤掉不在 GT JSONcategories 中的 类别,并且过滤掉不是 GT 中的 imgIds
      在这里插入图片描述
      示例如下,imgIds 可以是几个,也可以是所有图像 ids:
      ByteTrack 数据集和后处理_第4张图片
      因此,不用担心 val_half.json 中的 mask,只要其对应的 category_id 不在 categories 里就可以
      但是,训练集读取的,没有过滤,因为 getAnnIds 函数 给的参数不同 !!! (没有给 catIds 这个类别过滤参数 )

COCO JSON 的 annotations 格式

for ind in range(bboxes.shape[0]):
	label = self.dataloader.dataset.class_ids[int(cls[ind])]
	pred_data = {
       "image_id": int(img_id),
       "category_id": label,
       "bbox": bboxes[ind].numpy().tolist(),   # xywh
       "score": scores[ind].numpy().item(),
       "segmentation": [],
   }  # COCO json format
   data_list.append(pred_data)

  • evaluate_prediction 函数详解
    • 就是先把检测结果(data_list)写入 json,再使用 coco.loadRes函数,最终搞成 标准的 COCO JSNO 格式
      • (由于pycocotools不能处理py36中的dict,所以将数据写入json文件)
      • 其中,现将 dataset[‘images’] (加载 val_half.json 的图像信息)(这个图像的 image id 不是从 1 开始的)
        • 例如:{'file_name': 'MOT17-02-FRCNN/img1/000302.jpg', 'id': 302, 'frame_id': 1, 'prev_image_id': 301, 'next_image_id': 303, 'video_id': 1, 'height': 1080, 'width': 1920}
      • 添加 categories 属性
      • 然后遍历每个检测框,xywh2xyxy、增加 areaidiscrowd=0 属性
      • 至此检测结果转化成 标准的 coco JSON 格式

你可能感兴趣的:(目标跟踪,深度学习,目标跟踪)