前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MMTracking 食用指南 | 多目标跟踪篇

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

作者头像
OpenMMLab 官方账号
发布2022-01-18 08:58:30
1.9K0
发布2022-01-18 08:58:30
举报
文章被收录于专栏:OpenMMLabOpenMMLab

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

本文内容

MOT 任务简介

MOT 数据集介绍

MMTracking 支持的算法与数据集

上手指南

Tracktor 实现解析

1. MOT 任务简介

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

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

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表示跟踪算法需要外部的检测结果作为输入。

代码语言:javascript
复制
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 文件里,所以并不需要将模型权重作为参数传入到执行脚本里。

代码语言:javascript
复制
./tools/dist_test.sh \
    configs/mot/tracktor/tracktor_faster-rcnn_r50_fpn_4e_mot17-public-half.py \
    8 --eval track

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

代码语言:javascript
复制
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 文件如下:

代码语言:javascript
复制
model = dict(
    type='FasterRCNN',
    ...
)

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

代码语言:javascript
复制
model = dict(
    detector=dict(
        type='FasterRCNN',
        ...
    )
)

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

代码语言:javascript
复制
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。

代码语言:javascript
复制
bash ./tools/dist_train.sh \
    ./configs/reid/resnet50_b32x8_MOT17.py \
    8 --work-dir ./work_dirs/

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

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

快速上手教程:

https://mmtracking.readthedocs.io/en/latest/quick_run.html

5. Tracktor 实现解析

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

Tracktor 的配置文件

代码语言:javascript
复制
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 的配置文件

代码语言:javascript
复制
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 的配置文件

代码语言:javascript
复制
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 的配置文件

代码语言:javascript
复制
motion=dict(
    type='CameraMotionCompensation',
    warp_mode='cv2.MOTION_EUCLIDEAN',
    num_iters=100,
    stop_eps=1e-05)

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

Tracker 的配置文件

代码语言:javascript
复制
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 个步骤分别贴在了代码里的相应位置上作为注释,方便读者理解代码的逻辑,具体如下:

代码语言:javascript
复制
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 是您永远的家!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-10-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 OpenMMLab 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档