MMTracking 食用指南 | 多目标跟踪篇

简介: MOT 旨在检测和跟踪视频中出现的物体。与视频目标检测相比,MOT 更加侧重于对视频内的同一目标进行关联。

本期我们提供 MMTracking 里多目标跟踪(MOT)任务的食用指南。后续单目标跟踪的食用指南也在路上哦~


本文内容

MOT 任务简介

MOT 数据集介绍

MMTracking 支持的算法与数据集

上手指南

Tracktor 实现解析


1.  MOT 任务简介



MOT 旨在检测和跟踪视频中出现的物体。


与视频目标检测相比,MOT 更加侧重于对视频内的同一目标进行关联。

image.png

文章链接:https://mp.weixin.qq.com/s/POimc9lhveOXuowEMnPzqw


2. MOT 数据集介绍



目前 MOT 领域主流的数据集为 MOT 15、MOT 16、 MOT 17、MOT 20,主要侧重于密集场景下行人跟踪任务


以 MOT 17 为例,训练集 7 个视频,测试集 7 个视频。该数据集的评估指标为 CLEAR MOT ,其中主要指标为 MOTA 和 IDF1。


3. MMTracking 支持的算法与数据集



MMTracking 目前支持以下 MOT 算法:


- SORT (ICIP 2016)

- DeepSORT (ICIP 2017)

- Tracktor (ICCV 2019)


MMTracking 目前支持 MOT 15、MOT 16、 MOT 17、MOT 20 数据集。


4. 上手指南



接下来,我们详细地介绍在 MMTracking 里如何运行 MOT demo、测试 MOT 模型、训练 MOT 模型


使用 MMTracking,你只需要克隆一下 github 上面的仓库到本地,然后按照安装手册配置一下环境即可,如果安装遇到什么问题,可以给 MMTracking 提 issue,我们会尽快为小伙伴们解答。


假设已经将预训练权重放置在 MMTracking 根目录下的 checkpoints/ 文件夹下(预训练权重可以在相应的 configs 页面下载)。


运行 MOT demo


在 MMTracking 根目录下只需执行以下命令,即可使用 Tracktor 算法运行 MOT demo。


请注意,当运行 demo 时,需要 config 文件名包含 private字段,这是因为 private表示跟踪算法不需要外部的检测结果作为输入,而public表示跟踪算法需要外部的检测结果作为输入。


python demo/demo_mot.py \
    configs/mot/deepsort/sort_faster-rcnn_fpn_4e_mot17-private.py \
    --input demo/demo.mp4 \
    --output mot.mp4 \
    --show


测试 MOT 模型


在 MMTracking 根目录下使用以下命令即可测试 MOT 模型,并且评估模型的 CLEAR MOT metrics。


由于 Tracktor 算法由检测器和 ReID 模型两部分构成,MMTracking 将检测器的模型权重和 ReID 的模型权重写在了 config 文件里,所以并不需要将模型权重作为参数传入到执行脚本里。

./tools/dist_test.sh \
    configs/mot/tracktor/tracktor_faster-rcnn_r50_fpn_4e_mot17-public-half.py \
    8 --eval track

如上所提到的,如果想使用自己训练的检测器和 ReID 模型来测试 Tracktor 算法,则需要修改 config 文件里的检测器模型权重文件和 ReID 模型权重文件,具体方式如下:

model = dict(
    detector=dict(
        init_cfg=dict(
            type='Pretrained',
            checkpoint='/path/to/detector_model')),
    reid=dict(
        init_cfg=dict(
            type='Pretrained',
            checkpoint='/path/to/reid_model'))
    )


训练 MOT 模型

对于像 SORT、DeepSORT、Tracktor 这样的 MOT 算法,由于算法本身由检测器和 ReID 模型两部分构成,所以需要分别训练一个检测器和一个 ReID 模型,之后再测试 MOT 模型。


A. 训练检测器


MMTracking 可以直接调用 MMDetection 的接口来训练检测器,为了达到这一目的,需要在 config 文件里加上一行 USE_MMDET=True。


请注意 MMTracking 下的 base config 和 MMDetection 下的有一些不同,detector仅仅是model的一个子组件,例如 MMDetection 下的 Faster R-CNN config 文件如下:


model = dict(    type='FasterRCNN',    ...)

但是在 MMTracking 下,config 文件如下:

model = dict(
    detector=dict(
        type='FasterRCNN',
        ...
    )
)

比如使用以下命令即可在 MOT 17 数据集上训练检测器,并在每一个 epoch 之后评估 bbox mAP。

bash ./tools/dist_train.sh \
    ./configs/det/faster-rcnn_r50_fpn_4e_mot17-half.py \
    8 --work-dir ./work_dirs/


B. 训练 ReID 模型


MMTracking 支持基于 MMClassification 来训练 ReID 模型。


比如使用以下命令即可在 MOT 17 数据集上训练 ReID 模型,并在每个 epoch 之后评估 mAP。

bash ./tools/dist_train.sh \
    ./configs/reid/resnet50_b32x8_MOT17.py \
    8 --work-dir ./work_dirs/

在训练得到自己的检测器和 ReID 模型之后,就可以按照步骤 “2. 测试 MOT 模型”里提到的方式来运行自己的跟踪模型了。


其实在 MMTracking 当中已经支持了很多的 MOT 模型,并且提供了公共的 checkpoint 供大家把玩,在快速上手教程中有更详细地介绍,欢迎大家来试用并且提出你们宝贵的意见。


5. Tracktor 实现解析



经过上述步骤,我们已经了解了怎样运行 MOT 算法,但同时也对算法实现方式产生了一些兴趣,接下来将介绍 Tracktor 在 MMTracking 下的实现。


Tracktor 的配置文件


model = dict(
    type='Tracktor',
    detector=dict(type='FasterRCNN'),
    reid=dict(type='BaseReID'),
    motion=dict(type='CameraMotionCompensation'),
    tracker=dict(type='TracktorTracker'))


Tracktor 的配置文件如上所示,可以看到 Tracktor 由 4 部分构成:


(1)detector:使用 Faster RCNN 算法,用来检测视频里的物体;


(2)reid:使用 ReID 模型对未跟踪上的物体进行关联;


(3)motion:使用 CameraMotionCompensation 算法,进行相邻帧的运动补偿;


(4)tracker:综合调配使用 detector、reid、motion 来关联相邻帧的物体。


接下来详细介绍这几个部分。


Detector 的配置文件


detector=dict(
    type='FasterRCNN',
    backbone=dict(type='ResNet'),
    neck=dict(type='FPN'),
    rpn_head=dict(type='RPNHead'),
    roi_head=dict(type='StandardRoIHead',
        bbox_roi_extractor=dict(type='SingleRoIExtractor'),
        bbox_head=dict(type='Shared2FCBBoxHead')),
    train_cfg=dict(
        rpn=dict(
            assigner=dict(type='MaxIoUAssigner'),
            sampler=dict(type='RandomSampler')),
        rpn_proposal=dict(),
        rcnn=dict(
            assigner=dict(type='MaxIoUAssigner'),
            sampler=dict(type='RandomSampler'))),
    test_cfg=dict(
        rpn=dict(),
        rcnn=dict()),
    init_cfg=dict(type='Pretrained'))


detector 部分使用 Faster RCNN 算法,主要包含 7 个部分:


(1)backbone:使用 ResNet,用于提取图像特征图;


(2)neck:使用 FPN 算法,用于获取多尺度特征图;


(3)rpn_head:使用 RPN 算法,用于获取图像中的 proposals;


(4)roi_head:由 RoI Align 和 全连接层构成,用于预测 proposals 的类别和位置;


(5)train_cfg:detector 的训练配置;


(6)test_cfg:detector 的测试配置;


(7)init_cfg:detector 的初始化方式,在 Tracktor 里,通过这个参数来 load detector 的模型权重。



Reid 的配置文件


reid=dict(
    type='BaseReID',
    backbone=dict(type='ResNet'),
    neck=dict(type='GlobalAveragePooling'),
    head=dict(type='LinearReIDHead'),
    init_cfg=dict(type='Pretrained'))


reid 部分包含 4 个部分:



(1)backbone:使用 ResNet,用于提取图像特征图;


(2)neck:全局池化平均,用于压缩图像特征大小;


(3)head:使用全连接层,来进一步提取图像特征,并压缩特征大小;


(4)init_cfg:reid 的初始化方式,在 Tracktor 里,通过这个参数来 load reid 的模型权重。



Motion 的配置文件


motion=dict(
    type='CameraMotionCompensation',
    warp_mode='cv2.MOTION_EUCLIDEAN',
    num_iters=100,
    stop_eps=1e-05)

motion 部分使用 CMC 算法来进行相邻帧的运动补偿,CMC 算法通过调用 ECC 算法来使用这 3 个参数,来获取矫正矩阵。


Tracker 的配置文件


tracker=dict(
    type='TracktorTracker',
    obj_score_thr=0.5,
    regression=dict(
        obj_score_thr=0.5,
        nms=dict(type='nms', iou_threshold=0.6),
        match_iou_thr=0.3),
    reid=dict(
        num_samples=10,
        img_scale=(256, 128),
        img_norm_cfg=None,
        match_score_thr=2.0,
        match_iou_thr=0.2),
    momentums=None,
    num_frames_retain=10)


tracker 部分由 5 部分构成:


(1)obj_score_thr:用于过滤检测框的 score;


(2)regression:当使用 detector 进行前后两帧之间的跟踪时,用于过滤跟踪框的配置;


(3)reid:对于 regression 部分未跟踪上的物体框,使用 reid 模型进行进一步关联;


(4)momentum:以动量的方式更新 tracklet,默认为 None,表示不使用动量的方式进行更新;


(5)num_frames_retain:如果一个物体持续消失了 num_frames_retain 帧,则认为该物体消失。如果一个物体在消失的 num_frames_retain 帧内,又被匹配上了,则认为该物体重新出现。


Tracker 的实现细节


由于 tracker 是 Tracktor 算法的核心,所以本文将它单独拿出来分析其在 MMTracking 下具体的实现细节。


tracker 的实现方式在

$MMTracking/mmtracking/mmtrack/models/mot/trackers/tracktor_tracker.py 下可以找到,在这里只将 forward 函数贴进来。


forward 函数的步骤根据算法原理主要分为七步:


第一步,初始化 tracklet,这一步一般在第一帧执行,也有可能在中间物体全部消失的某一帧进行;


第二步,使用 CMC 算法来进行相邻帧的运动补偿;


第三步,使用 detector 将上一帧的物体跟踪到当前帧;


第四步,使用第三步得到的跟踪坐标框,基于 IOU 过滤当前帧的坐标框;


第五步,使用 reid 模型将未跟踪上的物体关联起来;


第六步,对于 reid 模型也没有关联上的坐标框来说,认为其是新物体出现,分配新的 id;


第七步,根据上述得到的跟踪结果更新 tracklets。


本文将这 7 个步骤分别贴在了代码里的相应位置上作为注释,方便读者理解代码的逻辑,具体如下:

class TracktorTracker(BaseTracker):
    def __init__(self,
                 obj_score_thr=0.5,
                 regression=dict(
                     obj_score_thr=0.5,
                     nms=dict(type='nms', iou_threshold=0.6),
                     match_iou_thr=0.3),
                 reid=dict(
                     num_samples=10,
                     img_scale=(256, 128),
                     img_norm_cfg=None,
                     match_score_thr=2.0,
                     match_iou_thr=0.2),
                 init_cfg=None,
                 **kwargs):
        super().__init__(init_cfg=init_cfg, **kwargs)
        self.obj_score_thr = obj_score_thr
        self.regression = regression
        self.reid = reid
    def regress_tracks(self, x, img_metas, detector, frame_id, rescale=False):
        """Regress the tracks to current frame."""
    @force_fp32(apply_to=('img', 'feats'))
    def track(self,
              img,
              img_metas,
              model,
              feats,
              bboxes,
              labels,
              frame_id,
              rescale=False,
              **kwargs):
        if self.with_reid:
            if self.reid.get('img_norm_cfg', False):
                reid_img = imrenormalize(img, img_metas[0]['img_norm_cfg'],
                                         self.reid['img_norm_cfg'])
            else:
                reid_img = img.clone()
        valid_inds = bboxes[:, -1] > self.obj_score_thr
        bboxes = bboxes[valid_inds]
        labels = labels[valid_inds]
        # 第一步,初始化 tracklet,这一步一般在第一帧执行,也有可能在中间物体全部消失的某一帧进行;
        if self.empty:
            num_new_tracks = bboxes.size(0)
            ids = torch.arange(
                self.num_tracks,
                self.num_tracks + num_new_tracks,
                dtype=torch.long)
            self.num_tracks += num_new_tracks
            if self.with_reid:
                embeds = model.reid.simple_test(
                    self.crop_imgs(reid_img, img_metas, bboxes[:, :4].clone(),
                                   rescale))
        else:
            # 第二步,使用 CMC 算法来进行相邻帧的运动补偿;
            # motion
            if model.with_cmc:
                if model.with_linear_motion:
                    num_samples = model.linear_motion.num_samples
                else:
                    num_samples = 1
                self.tracks = model.cmc.track(self.last_img, img, self.tracks,
                                              num_samples, frame_id)
            if model.with_linear_motion:
                self.tracks = model.linear_motion.track(self.tracks, frame_id)
            # 第三步,使用 detector 将上一帧的物体跟踪到当前帧;
            # propagate tracks
            prop_bboxes, prop_labels, prop_ids = self.regress_tracks(
                feats, img_metas, model.detector, frame_id, rescale)
            # 第四步,使用第三步得到的跟踪坐标框,基于 IOU 过滤当前帧的坐标框;
            # filter bboxes with propagated tracks
            ious = bbox_overlaps(bboxes[:, :4], prop_bboxes[:, :4])
            valid_inds = (ious < self.regression['match_iou_thr']).all(dim=1)
            bboxes = bboxes[valid_inds]
            labels = labels[valid_inds]
            ids = torch.full((bboxes.size(0), ), -1, dtype=torch.long)
            # 第五步,使用 reid 模型将未跟踪上的物体关联起来;
            if self.with_reid:
                prop_embeds = model.reid.simple_test(
                    self.crop_imgs(reid_img, img_metas,
                                   prop_bboxes[:, :4].clone(), rescale))
                if bboxes.size(0) > 0:
                    embeds = model.reid.simple_test(
                        self.crop_imgs(reid_img, img_metas,
                                       bboxes[:, :4].clone(), rescale))
                else:
                    embeds = prop_embeds.new_zeros((0, prop_embeds.size(1)))
                # reid
                active_ids = [int(_) for _ in self.ids if _ not in prop_ids]
                if len(active_ids) > 0 and bboxes.size(0) > 0:
                    track_embeds = self.get(
                        'embeds',
                        active_ids,
                        self.reid.get('num_samples', None),
                        behavior='mean')
                    reid_dists = torch.cdist(track_embeds,
                                             embeds).cpu().numpy()
                    track_bboxes = self.get('bboxes', active_ids)
                    ious = bbox_overlaps(track_bboxes,
                                         bboxes[:, :4]).cpu().numpy()
                    iou_masks = ious < self.reid['match_iou_thr']
                    reid_dists[iou_masks] = 1e6
                    row, col = linear_sum_assignment(reid_dists)
                    for r, c in zip(row, col):
                        dist = reid_dists[r, c]
                        if dist <= self.reid['match_score_thr']:
                            ids[c] = active_ids[r]
            # 第六步,对于 reid 模型也没有关联上的坐标框来说,认为其是新物体出现,分配新的 id;
            new_track_inds = ids == -1
            ids[new_track_inds] = torch.arange(
                self.num_tracks,
                self.num_tracks + new_track_inds.sum(),
                dtype=torch.long)
            self.num_tracks += new_track_inds.sum()
            if bboxes.shape[1] == 4:
                bboxes = bboxes.new_zeros((0, 5))
            if prop_bboxes.shape[1] == 4:
                prop_bboxes = prop_bboxes.new_zeros((0, 5))
            bboxes = torch.cat((prop_bboxes, bboxes), dim=0)
            labels = torch.cat((prop_labels, labels), dim=0)
            ids = torch.cat((prop_ids, ids), dim=0)
            if self.with_reid:
                embeds = torch.cat((prop_embeds, embeds), dim=0)
        # 第七步,根据上述得到的跟踪结果更新 tracklets。
        self.update(
            ids=ids,
            bboxes=bboxes[:, :4],
            scores=bboxes[:, -1],
            labels=labels,
            embeds=embeds if self.with_reid else None,
            frame_ids=frame_id)
        self.last_img = img
        return bboxes, labels, ids

作为 MM 系列的成员, MMTracking 将持续更新,力图早日成长为一个完善的视频目标感知平台,而社区的声音能够帮助我们更好地了解到大家的需求,所以如果大家在使用的过程中遇到什么问题、想法、建议,或者有想支持的新数据集、新方法、新任务,欢迎在评论区里发言。请记住我们的 repo 是您永远的家!


文章来源:公众号【OpenMMLab】

 2021-10-09 12:52








目录
相关文章
|
6月前
|
机器学习/深度学习 算法
应用规则学习算法识别有毒的蘑菇
应用规则学习算法识别有毒的蘑菇
|
6月前
|
人工智能
姿态识别+康复训练矫正+代码+部署(AI 健身教练来分析深蹲等姿态)-2
姿态识别+康复训练矫正+代码+部署(AI 健身教练来分析深蹲等姿态)-2
|
6月前
|
机器学习/深度学习 人工智能 算法
姿态识别+康复训练矫正+代码+部署(AI 健身教练来分析深蹲等姿态)-1
姿态识别+康复训练矫正+代码+部署(AI 健身教练来分析深蹲等姿态)-1
|
机器学习/深度学习 人工智能 TensorFlow
检测脸部情绪有多难?10行代码就可以搞定!
检测脸部情绪有多难?10行代码就可以搞定!
|
C++ 计算机视觉 Python
Python+Yolov5跌倒摔倒人体特征识别
这篇博客针对<<Python+Yolov5跌倒摔倒人体特征识别>>编写代码,代码整洁,规则,易读。 学习与应用推荐首选。
135 0
Python+Yolov5跌倒摔倒人体特征识别
|
机器学习/深度学习 算法 测试技术
人体姿态识别(毕业设计+代码)
人体姿态识别(毕业设计+代码)
人体姿态识别(毕业设计+代码)
|
机器学习/深度学习 人工智能 算法
硬刚 ArcFace | ECCV 2022 人脸识别新方法 BoundaryFace:一种基于噪声标签自校正框架(附源码实现)
硬刚 ArcFace | ECCV 2022 人脸识别新方法 BoundaryFace:一种基于噪声标签自校正框架(附源码实现)
543 0
|
机器学习/深度学习 监控 vr&ar
姿态识别、手势识别(附代码)
姿态识别、手势识别(附代码)
|
机器学习/深度学习 人工智能 编解码
DeepFake换头术升级:浙大新模型,GAN出一头秀发
DeepFake换头术升级:浙大新模型,GAN出一头秀发
280 0
|
人工智能 缓存 并行计算
欢度中秋节!从零开始实现一个月饼检测器(一)
欢度中秋节!从零开始实现一个月饼检测器
欢度中秋节!从零开始实现一个月饼检测器(一)
下一篇
无影云桌面