FCOS:简洁的anchor-free目标检测器

论文题目:FCOS: Fully Convolutional One-Stage Object Detection

其亮点:

  • 基于FCN构建全卷积检测器,使得视觉任务(如语义分割)可以统一在FCN框架
  • anchor-free,proposal free,避免了训练阶段关于anchor或者proposal的iou计算.更重要的是,避免了一切与anchor有关的超参数
  • 简单的Backbone;neck;head检测算法框架

原始anchor-base的缺点:

  • 检测器对anchor的大小、纵横比、数量比较敏感;在RetinaNet,更改这些超参数会影响性能高达4% ap(coco基准).因此在使用基于anchor的检测器时要仔细调关于anchor的超参数
  • 由于anchor的比例和纵横比在初始时保持固定,检测器在处理形状变化较大的候选对象时能力不够
  • 为了实现较高的召回率,anchor-base的检测器将anchor密集地放置在图像特征中,导致训练过程加大了正负样本的不平衡,当然也显著增加训练过程的计算量(一般通过计算与GT之间的IOU来计算loss,anchor生成的proposal越多,计算量就越大)

本文方法:

逐像素回归预测

信息表示:

对于训练目标ground-truth bounding boxes,我们用其四元向量表示:

其中代表了边框的左上角点坐标,代表了边框的右上角坐标,代表了其目标框的类别.

backbone CNN网络提取的特征图属于第层,其中缩放的步幅(stride)为.
对于特征图中的每一对坐标点,我们可以与原始图像建立一一对应关系;

不同于anchor-base的检测器,fcos对每一个特征图上的坐标都作为训练样本进行回归(也就是像素级别回归).

如同上面的对应关系,如果(x,y)落在任何一个ground-truth bounding box中,那么它是一个正训练样本,其标签是ground-truth的标签,如果不落在box中,则该样本则为负样本,.

除此之外,fcos还对每一个像素进行回归预测一个四元组向量,分别代表了其四个边框到中心点的距离;

某个点落入边框内,则回归的目标为:

image

作者认为fcos优于其他训练器的方式在于利用了尽可能多的前景样本来训练回归器,而不是通过计算iou来获取正样本.

网络输出

image

根据逐像素级别的回归目标,我们得到其训练目标为-d向量代表了其分类结果,4-d向量代表了其边框坐标回归信息.

fcos采取训练个二分类器的方式进行类别判断,而不是直接训练一个多分类判别器.

Moreover, since the regression targets are always positive, we employ exp(x) to map any real number to (0,∞) on the top of the regression branch

在回归分支中像yolo一样采取exp函数对坐标进行比例map.

loss 函数

整体loss分为分类loss与边框信息loss:

\begin{aligned} L\left(\left\{\boldsymbol{p}_{x, y}\right\},\left\{\boldsymbol{t}_{x, y}\right\}\right) &=\frac{1}{N_{\text {pos }}} \sum_{x, y} L_{\text {cls }}\left(\boldsymbol{p}_{x, y}, c_{x, y}^{*}\right) \\ &+\frac{\lambda}{N} \sum \mathbb{1}_{\left\{c_{x, y}^{*}>0\right\}} L_{\text {reg }}\left(\boldsymbol{t}_{x, y}, \boldsymbol{t}_{x, y}^{*}\right) \end{aligned}

其中采取focal loss;

则是计算预测框与真实框的 iou loss.

为正样本的数量.

为平衡系数,用来平衡分类损失与坐标损失.

多级预测提高召回率

利用FPN多级特征图尺寸来提高召回率;

image

如结构图所示,作者使用了FPN的5个层次的特征图为别为,其stride分别为是:8,16,32,64和128.

那么对于回归目标,其处于不同的特征图,最大最小值也有了一定的限制;

如果,或者,则其是一个负样本,不参与训练计算;

的取值分别为0,64,128,256,512;

center-ness 分支

作者发现,通过原始的办法,由于在远离中心点的位置产生了许多低质量的预测边框,导致检测器性能不高,所以作者提出了一个新的分支,center-ness,用来抑制这些低质量边框;

对于同一个坐标点的回归坐标信息,通过以下公式计算其中心度:

远离中心点的边框,其会很大,导致center-ness分数很低.这样就可以有效地抑制这些低质量边框.

center-ness 乘以分类分数来得到最终score(用来计算预测框的排名).最后,这些低质量的边框可能通过最终的NMS过程来滤除掉.

代码阅读

本代码采取mmdetion 框架中的fcos代码部分;

其backbone跟fpn均采取普通设置(resnet+fpn多尺度预测),来窥探下fcos_heads.py

#/mmdet/models/anchor_heads/fcos_heads.py
@HEADS.register_module
class FCOSHead(nn.Module):
    """
    Fully Convolutional One-Stage Object Detection head from [1]_.

    The FCOS head does not use anchor boxes. Instead bounding boxes are
    predicted at each pixel and a centerness measure is used to supress
    low-quality predictions.

    References:
        .. [1] https://arxiv.org/abs/1904.01355

    Example:
        >>> self = FCOSHead(11, 7)
        >>> feats = [torch.rand(1, 7, s, s) for s in [4, 8, 16, 32, 64]]
        >>> cls_score, bbox_pred, centerness = self.forward(feats)
        >>> assert len(cls_score) == len(self.scales)
    """

    def __init__(self,
                 num_classes,
                 in_channels,
                 feat_channels=256,
                 stacked_convs=4,
                 #定义了其采样步长,回归边框
                 strides=(4, 8, 16, 32, 64),
                 regress_ranges=((-1, 64), (64, 128), (128, 256), (256, 512),
                                 (512, INF)),
                #三种loss分别为focalloss,iouloss 跟CrossEntropyLoss计算
                 loss_cls=dict(
                     type='FocalLoss',
                     use_sigmoid=True,
                     gamma=2.0,
                     alpha=0.25,
                     loss_weight=1.0),
                 loss_bbox=dict(type='IoULoss', loss_weight=1.0),
                 loss_centerness=dict(
                     type='CrossEntropyLoss',
                     use_sigmoid=True,
                     loss_weight=1.0),
                 conv_cfg=None,
                 norm_cfg=dict(type='GN', num_groups=32, requires_grad=True)):
        super(FCOSHead, self).__init__()

        self.num_classes = num_classes
        self.cls_out_channels = num_classes - 1
        self.in_channels = in_channels
        self.feat_channels = feat_channels
        self.stacked_convs = stacked_convs
        self.strides = strides
        self.regress_ranges = regress_ranges
        self.loss_cls = build_loss(loss_cls)
        self.loss_bbox = build_loss(loss_bbox)
        self.loss_centerness = build_loss(loss_centerness)
        self.conv_cfg = conv_cfg
        self.norm_cfg = norm_cfg
        self.fp16_enabled = False

        self._init_layers()
    def _init_layers(self):
        #每一个head都有两个分支:分类分支和回归坐标分支,经过四次采样.
        self.cls_convs = nn.ModuleList()
        self.reg_convs = nn.ModuleList()
        for i in range(self.stacked_convs):
            chn = self.in_channels if i == 0 else self.feat_channels
            self.cls_convs.append(
                ConvModule(
                    chn,
                    self.feat_channels,
                    3,
                    stride=1,
                    padding=1,
                    conv_cfg=self.conv_cfg,
                    norm_cfg=self.norm_cfg,
                    bias=self.norm_cfg is None))
            self.reg_convs.append(
                ConvModule(
                    chn,
                    self.feat_channels,
                    3,
                    stride=1,
                    padding=1,
                    conv_cfg=self.conv_cfg,
                    norm_cfg=self.norm_cfg,
                    bias=self.norm_cfg is None))
        self.fcos_cls = nn.Conv2d(
            self.feat_channels, self.cls_out_channels, 3, padding=1)
        self.fcos_reg = nn.Conv2d(self.feat_channels, 4, 3, padding=1)
        self.fcos_centerness = nn.Conv2d(self.feat_channels, 1, 3, padding=1)

        self.scales = nn.ModuleList([Scale(1.0) for _ in self.strides])

        #前馈计算方式,分类分支,回归分支,center-ness 分支分别计算结果
    def forward_single(self, x, scale):
        cls_feat = x
        reg_feat = x

        for cls_layer in self.cls_convs:
            cls_feat = cls_layer(cls_feat)
        cls_score = self.fcos_cls(cls_feat)
        centerness = self.fcos_centerness(cls_feat)

        for reg_layer in self.reg_convs:
            reg_feat = reg_layer(reg_feat)
        # scale the bbox_pred of different level
        # float to avoid overflow when enabling FP16
        bbox_pred = scale(self.fcos_reg(reg_feat)).float().exp()
        return cls_score, bbox_pred, centerness
        #center-ness 的定义
    def centerness_target(self, pos_bbox_targets):
        # only calculate pos centerness targets, otherwise there may be nan
        left_right = pos_bbox_targets[:, [0, 2]]
        top_bottom = pos_bbox_targets[:, [1, 3]]
        centerness_targets = (
            left_right.min(dim=-1)[0] / left_right.max(dim=-1)[0]) * (
                top_bottom.min(dim=-1)[0] / top_bottom.max(dim=-1)[0])
        return torch.sqrt(centerness_targets)

总结

  • FCN的方式进行像素级别中心点回归预测边框方式
  • 提出center-ness分支,有效解决了离中心点较远的低质量预测框问题
  • 建立统一的FCN视觉任务框架

reference

[1] Tian Z, Shen C, Chen H, et al. Fcos: Fully convolutional one-stage object detection[C]//Proceedings of the IEEE International Conference on Computer Vision. 2019: 9627-9636.

你可能感兴趣的:(FCOS:简洁的anchor-free目标检测器)