End-to-End Object Detection with Fully Convolutional Network学习笔记

0.摘要

cvpr2021
作者提出的是一种新的检测,也可以稍微节约的点时间,本片文章是基于transformer,fcos(Fully Convolutional One-Stage Object Detection),fcn(Fully Convolutional),但是本片文章的实现细节基本上没怎么描述。
思路是基于目前主流的一阶段或者两阶段检测器严重依赖预定义的由滑动窗组成的anchor。由于anchor是人工设计和不依赖数据的(yolo和f-rcnn),anchor-based检测器的训练目标通常是次优的,需要仔细调整超参数。
Learnable NMS,Soft-NMS和CenterNet做了很多工作去除重复预测,但仍然没有实现彻底的端到端训练 。由于NMS是一种启发式方法,并对所有实例采用一个恒定的阈值,因此NMS需要仔细调优阈值,而且可能不够鲁棒,影响密集目标的检测性能。
作者使用的是预训练模型,所以没有提出使用什么骨干网络

def build_backbone(cfg, input_shape=None):
    """
    Build a backbone from `cfg.MODEL.BACKBONE.NAME`.

    Returns:
        an instance of :class:`Backbone`
    """
    if input_shape is None:
        input_shape = ShapeSpec(channels=len(cfg.MODEL.PIXEL_MEAN))

    backbone = build_retinanet_resnet_fpn_p5_backbone(cfg, input_shape)
    assert isinstance(backbone, Backbone)
    return backbone


MODEL=dict(
        WEIGHTS="detectron2://ImageNetPretrained/MSRA/R-50.pkl",
        RESNETS=dict(DEPTH=50),
        SHIFT_GENERATOR=dict(
            NUM_SHIFTS=1,
            OFFSET=0.5,
        ),

0.1小概念

one-to-many先对每个目标生成多个预测
many-to-one将多个预测去重,一般使用的是nms等手段
one-to-one需要网络输出的feature非常好,要能把所有的类比清晰的分辨出来,这对CNN提出了较严苛的要求(这也是Transformer的优势);
one-to-many带来了更强的监督和更快的收敛速度。
label assignment:就是要对目标检测中的anchor box或者anchor point打上label,是positive、negative还是ignore

1.Prediction-aware One-to-one Label Assignment

1.1损失函数

想要实现端到端,必须取消掉NMS,使用NMS的目的是由于真实的box在网络输出会预测多个预测框,然后使用NMS来对真实标签做简化。我们使用one to one就是要让一个Groud Truth网络只生成一个Prediction,也就是 one-to-one label assignment。使用固定的手工设置的一对一标签预测的回归效果不好,有很多失败的预测,网络也不收敛所以使用了新的损失函数。
在这里插入图片描述
由前景损失Lfg和背景损失Lbg组成,
左边cb分别是Ground Truth的类别标签以及回归坐标,pb分别对应其预测分类得分和预测边界框坐标。
右边Ψ所有预测的索引集,其中r表示已分配的前景样本的相应索引集

以前的工作通过使用前景损失作为匹配成本将其视为二分匹配问题,可以通过匈牙利算法快速解决:
在这里插入图片描述
但是本文提出的是
在这里插入图片描述
在这里插入图片描述
q(quality)表示第个ground-truth与第[公式]个预测的所提出的匹配质量,其中考虑了空间先验,分类的置信度以及回归的质量。Ω 表示第i个地面真值的候选预测集,即空间先验,同时对分类回归利用α(一般为0.8) 进行了加权几何平均数.
α越低,分类权重越大,有无NMS的差距越小,但性能也会降低;α太高也不好,我们后续所有实验用α=0.8;

POTO=dict(
            ALPHA=0.8,
            CENTER_SAMPLING_RADIUS=0.0,  # inside gt box

作者提出poto的目的就是在没有nms的前提下,保持一定的性能,但是依旧干不过one-to-many+NMS

1.2 poto

定义的原文 Prediction-aware One-To-One (POTO) label as-signment by dynamically assigning samples according to the quality of predictions. Our POTO aims to generate a suitable permutation π of predictions as the foreground samples作者要动态的生成π,用π来限制one to one乱预测的可能性。

利用POTO解决one-to-many label assignment存在的NMS后处理过程,作者同时提出3DMF来抑制重复的预测。但是使用上面两个技巧,依旧达不到NMS FCOS baseline。为了提升网络的特征表示能力,作者引入上一节的 loss。最终达到了FCOS的性能
在这里插入图片描述

    def get_ground_truth(self, shifts, targets, box_cls, box_delta):
        """
        Args:
            shifts (list[list[Tensor]]): a list of N=#image elements. Each is a
                list of #feature level tensors. The tensors contains shifts of
                this image on the specific feature level.
            targets (list[Instances]): a list of N `Instances`s. The i-th
                `Instances` contains the ground-truth per-instance annotations
                for the i-th input image.  Specify `targets` during training only.

        Returns:
            gt_classes (Tensor):
                An integer tensor of shape (N, R) storing ground-truth
                labels for each shift.
                R is the total number of shifts, i.e. the sum of Hi x Wi for all levels.
                Shifts in the valid boxes are assigned their corresponding label in the
                [0, K-1] range. Shifts in the background are assigned the label "K".
                Shifts in the ignore areas are assigned a label "-1", i.e. ignore.
            gt_shifts_deltas (Tensor):
                Shape (N, R, 4).
                The last dimension represents ground-truth shift2box transform
                targets (dl, dt, dr, db) that map each shift to its matched ground-truth box.
                The values in the tensor are meaningful only when the corresponding
                shift is labeled as foreground.
        """
        gt_classes = []
        gt_shifts_deltas = []

        box_cls = torch.cat([permute_to_N_HWA_K(x, self.num_classes) for x in box_cls], dim=1)
        box_delta = torch.cat([permute_to_N_HWA_K(x, 4) for x in box_delta], dim=1)
        box_cls = box_cls.sigmoid_()

        num_fg = 0
        num_gt = 0

        for shifts_per_image, targets_per_image, box_cls_per_image, box_delta_per_image in zip(
                shifts, targets, box_cls, box_delta):
            shifts_over_all_feature_maps = torch.cat(shifts_per_image, dim=0)

            gt_boxes = targets_per_image.gt_boxes

            prob = box_cls_per_image[:, targets_per_image.gt_classes].t()
            boxes = self.shift2box_transform.apply_deltas(
                box_delta_per_image, shifts_over_all_feature_maps
            )
            iou = pairwise_iou(gt_boxes, Boxes(boxes))
            quality = prob ** (1 - self.poto_alpha) * iou ** self.poto_alpha

            deltas = self.shift2box_transform.get_deltas(
                shifts_over_all_feature_maps, gt_boxes.tensor.unsqueeze(1))

            if self.center_sampling_radius > 0:
                centers = gt_boxes.get_centers()
                is_in_boxes = []
                for stride, shifts_i in zip(self.fpn_strides, shifts_per_image):
                    radius = stride * self.center_sampling_radius
                    center_boxes = torch.cat((
                        torch.max(centers - radius, gt_boxes.tensor[:, :2]),
                        torch.min(centers + radius, gt_boxes.tensor[:, 2:]),
                    ), dim=-1)
                    center_deltas = self.shift2box_transform.get_deltas(
                        shifts_i, center_boxes.unsqueeze(1))
                    is_in_boxes.append(center_deltas.min(dim=-1).values > 0)
                is_in_boxes = torch.cat(is_in_boxes, dim=1)
            else:
                # no center sampling, it will use all the locations within a ground-truth box
                is_in_boxes = deltas.min(dim=-1).values > 0

            quality[~is_in_boxes] = -1

            gt_idxs, shift_idxs = linear_sum_assignment(quality.cpu().numpy(), maximize=True)

            num_fg += len(shift_idxs)
            num_gt += len(targets_per_image)

            gt_classes_i = shifts_over_all_feature_maps.new_full(
                (len(shifts_over_all_feature_maps),), self.num_classes, dtype=torch.long
            )
            gt_shifts_reg_deltas_i = shifts_over_all_feature_maps.new_zeros(
                len(shifts_over_all_feature_maps), 4
            )
            if len(targets_per_image) > 0:
                # ground truth classes
                gt_classes_i[shift_idxs] = targets_per_image.gt_classes[gt_idxs]
                # ground truth box regression
                gt_shifts_reg_deltas_i[shift_idxs] = self.shift2box_transform.get_deltas(
                    shifts_over_all_feature_maps[shift_idxs], gt_boxes[gt_idxs].tensor
                )

            gt_classes.append(gt_classes_i)
            gt_shifts_deltas.append(gt_shifts_reg_deltas_i)

        get_event_storage().put_scalar("num_fg_per_gt", num_fg / num_gt)

        return torch.stack(gt_classes), torch.stack(gt_shifts_deltas)

        ),

1.3 3D Max Filtering

是用来抑制重复预测,作者实验获得nearby spatial regions of the most confident prediction,就是大部分重复的bbox是来自于最准确区间的附近。max filter只考虑了单尺度特征,所以作者使用max filter的进化版3D max filter只考虑了单尺度特征。
给定FPN的尺度s中的一个输入特征xs,用双线性算子对τ相邻尺度的相邻特征插值到与输入xs相同的尺度。双线性映射的具体形式作者没说。
在这里插入图片描述
对于s尺度下的空间位置i,在预先定义的三维相领域中,根据比例τ尺度和φ × φ的空间距离,得到ysi的最大值。这个操作可以通过高效的3D max-pooling
在这里插入图片描述
在这里插入图片描述
ϕ和 τ对于性能的影响
在这里插入图片描述

1.4辅助损失函数(Auxiliary Loss)

因为之前的效果不好所以使用了one-to-many辅助损失函数,下图的虚线部分就是辅助损失函数

在这里插入图片描述

2.结论

是在CrowdHuman上进行的评估,是一个具有各种遮挡的大型人体检测数据集,用来当检测器判别的数据集比较好。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

作者说:相对于基于使用NMS的检测器,我们的3DMF模块只有增加了轻微的计算开销。确实在前几十轮就可以迅速收敛,但他没说一轮的计算量有多大。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值