目标检测之GFL

文章目录

  • 前言
  • 1.GFL的主要创新部分
  • 2.结合代码体现创新部分
    • 1.训练阶段的预测输出
    • 2.将bbox边框的回归值由单一确定值(狄拉克分布)变为一定范围的任意概率分布。
    • 3.Distribution Focal Loss
    • 4.Quality Focal Loss
    • 5.后处理部分
  • 3.消融实验
    • 1.QFL
    • 2.DFL
    • 3.QFL+DFL
  • 总结

前言

论文地址:https://arxiv.org/pdf/2006.04388.pdf
本博客的讲解代码:https://github.com/open-mmlab/mmdetection
源码:https://github.com/implus/GFocal

1.GFL的主要创新部分

Generalized Focal Loss: Learning Qualified and Distributed Bounding Boxes for Dense Object Detection
论文通过分析预测输出时的分类,回归和定位质量评估的表示,发现他们之间存在一些问题:

  • classification score 和 IoU/centerness score 训练测试不一致(如FCOS的分类分数和中心预测分数)。
    bbox regression 采用的表示不够灵活,没有办法建模复杂场景下的uncertainty
    为了解决这两个问题,作者给这三种预测输出表示设计了新的representations,并提出了Generalized Focal Loss来优化网络。

提出了两种新的表征方式:
将分类分数和检测框质量分数结合,得到分类-质量联合分数,标签变为连续值。
bbox边框的回归值由单一确定值(狄拉克分布)变为一定范围的任意概率分布。
提出了两种新的Focal Loss损失函数:

Quality Focal Loss 对分类-质量联合分数连续值标签进行优化。
Distribution Focal Loss 对边界框的任意概率分布进行优化。
具体可以看原作者的解析:https://zhuanlan.zhihu.com/p/147691786
或者:https://zhuanlan.zhihu.com/p/310229973

2.结合代码体现创新部分

1.训练阶段的预测输出

这部分分数预测输出不变,主要是回归框的变化

    def forward_single(self, x, scale):
        """Forward feature of a single scale level.

        Args:
            x (Tensor): Features of a single scale level.
            scale (:obj: `mmcv.cnn.Scale`): Learnable scale module to resize
                the bbox prediction.

        Returns:
            tuple:
                cls_score (Tensor): Cls and quality joint scores for a single
                    scale level the channel number is num_classes.
                bbox_pred (Tensor): Box distribution logits for a single scale
                    level, the channel number is 4*(n+1), n is max value of
                    integral set.
        """
        cls_feat = x
        reg_feat = x
        for cls_conv in self.cls_convs:
            cls_feat = cls_conv(cls_feat)
        for reg_conv in self.reg_convs:
            reg_feat = reg_conv(reg_feat)
        cls_score = self.gfl_cls(cls_feat)
        bbox_pred = scale(self.gfl_reg(reg_feat)).float()	# 注意这里输出C的变化[B,4*(16+1),H,W]
        return cls_score, bbox_pred

这里,gfl的head部分,self.gfl_reg()输出的是一个一定数量的离散框的回归值。一定数量:表示的是每条边的预测回归输出为(self.reg_max + 1)倍,作者实验得出self.reg_max =16为最佳。

self.gfl_reg = nn.Conv2d(self.feat_channels, 4 * (self.reg_max + 1), 3, padding=1)	# (256,4*(16+1),3)

2.将bbox边框的回归值由单一确定值(狄拉克分布)变为一定范围的任意概率分布。

作者使用softmax函数实现离散形式的任意分布。 P i P_i Pi表示用softmax函数使预测值变为0-1的概率, y i y_i yi从所给的边框范围(16)均匀取值,这样就从一个回归问题变为一个“多分类问题”。
目标检测之GFL_第1张图片
通过self.integral(pos_bbox_pred)来变化分布

pos_bbox_pred_corners = self.integral(pos_bbox_pred)    # [N,4]

这里注释解释的很清楚,就是通过一个任意分布+一个线性变换来确定预测的回归框lrtb值

class Integral(nn.Module):
    """A fixed layer for calculating integral result from distribution.

    This layer calculates the target location by :math: `sum{P(y_i) * y_i}`,
    P(y_i) denotes the softmax vector that represents the discrete distribution
    y_i denotes the discrete set, usually {0, 1, 2, ..., reg_max}

    Args:
        reg_max (int): The maximal value of the discrete set. Default: 16. You
            may want to reset it according to your new dataset or related
            settings.
    """

    def __init__(self, reg_max=16):
        super(Integral, self).__init__()
        self.reg_max = reg_max
        self.register_buffer('project',
                             torch.linspace(0, self.reg_max, self.reg_max + 1))  #

    def forward(self, x):
        """Forward feature from the regression head to get integral result of
        bounding box location.

        Args:
            x (Tensor): Features of the regression head, shape (N, 4*(n+1)),
                n is self.reg_max.

        Returns:
            x (Tensor): Integral result of box locations, i.e., distance
                offsets from the box center in four directions, shape (N, 4).
        """
        x = F.softmax(x.reshape(-1, self.reg_max + 1), dim=1)
        x = F.linear(x, self.project.type_as(x)).reshape(-1, 4)
        return x

每条边的任意分布为{ x i : p i x_i:p_i xi:pi}, x i x_i xi表示预测的离散lrtb的值, p i p_i pi表示它们离散值对应的分布概率。输入x经过F.softmax处理后就是对应的 p i p_i pi
self.project:给了一个0-16的17个间距为1的值。用来表示边框周围的范围。

F.linear(x, self.project.type_as(x)).reshape(-1, 4)

将每条边的17个预测值通过概率分布预测出一个值,最后重新变为常见的4个lrtb预测输出。
目标检测之GFL_第2张图片
从这个图可以看出,一些检测对象的边界并非十分明确。对于滑板左侧被水花模糊,引起对左边界的预测分布是任意而扁平的,对右边界的预测分布是明确而尖锐的。

3.Distribution Focal Loss

    需要获得的分布虽然不再像狄拉克分布那么极端,但也应该在标签值附近。因此作者提出Distribution Focal Loss损失函数,目的让网络快速聚焦到标签附近的数值,使标签处的概率密度尽量大。思想是使用交叉熵函数,来优化标签y附近左右两个位置的概率,使网络分布聚焦到标签值附近。
在这里插入图片描述

def distribution_focal_loss(pred, label):
"""
    Args:
        pred (torch.Tensor): Predicted general distribution of bounding boxes
            (before softmax) with shape (N, n+1), n is the max value of the
            integral set `{0, ..., n}` in paper.
        label (torch.Tensor): Target distance label for bounding boxes with
            shape (N,).

    Returns:
        torch.Tensor: Loss tensor with shape (N,).
    """
    dis_left = label.long()     # 转换为长整型,向下取整。偏向左边的值,label:是ltrb格式
    dis_right = dis_left + 1	# 偏向右边的值
    weight_left = dis_right.float() - label   # 小数,公式里的(y_{i+1}-y)
    weight_right = label - dis_left.float()		# (y-y_{i})
    #F.cross_entropy会对输入的pred进行softmax,dis_left为pre进行log_softmax后的索引
    loss = F.cross_entropy(pred, dis_left, reduction='none') * weight_left \
        + F.cross_entropy(pred, dis_right, reduction='none') * weight_right
    return loss

4.Quality Focal Loss

由下图可知,就是联合分类和IOU:预测的box与target box的IOU值作为监督的label,这样label值由一个0,1的离散值,变为0-1的连续值.所以计算分类Loss的时候,是通过分类和IOU的联系进行优化。
目标检测之GFL_第3张图片

score[pos_inds] = bbox_overlaps(    # 没传中心置信度,是将预测的box与target box的IOU值作为置信度分数
                pos_decode_bbox_pred.detach(),
                pos_decode_bbox_targets,
                is_aligned=True)

计算分类分数的时候将score传入,好进行分类-质量联合分数Loss的计算
# cls (qfl) loss
loss_cls = self.loss_cls(
    cls_score, (labels, score),
    weight=label_weights,
    avg_factor=num_total_samples)

公式中,原始的Fcoal loss针对的标签是离散值(y=0或y=1),这里提出的分类-检测框质量联合表示,其标签y为[0,1]的连续值,σ为预测值。
在这里插入图片描述

def quality_focal_loss(pred, target, beta=2.0):
"""
    Args:
        pred (torch.Tensor): Predicted joint representation of classification
            and quality (IoU) estimation with shape (N, C), C is the number of
            classes.
        target (tuple([torch.Tensor])): Target category label with shape (N,)
            and target quality label with shape (N,).
        beta (float): The beta parameter for calculating the modulating factor.
            Defaults to 2.0.

    Returns:
        torch.Tensor: Loss tensor with shape (N,).
    """
    assert len(target) == 2, """target for QFL must be a tuple of two elements,
        including category label and quality label, respectively"""
    # label denotes the category id, score denotes the quality score
    label, score = target
	# 负样本的loss为什么要单独出来计算呢?这里没太明白
    # negatives are supervised by 0 quality score,负样本用0去监督
    pred_sigmoid = pred.sigmoid()   # 前面都没用过sigmod
    scale_factor = pred_sigmoid     # [N,c]
    zerolabel = scale_factor.new_zeros(pred.shape)  #[N,c]
    loss = F.binary_cross_entropy_with_logits(	# 自带sigmod的交叉熵,传入后会对pred进行sigmod
        pred, zerolabel, reduction='none') * scale_factor.pow(beta)  #这里其实全部样本都用0监督了,后面正样本部分会被替换掉

    # FG cat_id: [0, num_classes -1], BG cat_id: num_classes
    bg_class_ind = pred.size(1)
    pos = ((label >= 0) & (label < bg_class_ind)).nonzero().squeeze(1)	#正样本
    pos_label = label[pos].long()	#[pos,]
    # positives are supervised by bbox quality (IoU) score,正样本的
    scale_factor = score[pos] - pred_sigmoid[pos, pos_label]
    loss[pos, pos_label] = F.binary_cross_entropy_with_logits(
        pred[pos, pos_label], score[pos],
        reduction='none') * scale_factor.abs().pow(beta)

    loss = loss.sum(dim=1, keepdim=False)
    return loss

5.后处理部分

        for cls_score, bbox_pred, stride, anchors in zip(
                cls_scores, bbox_preds, self.anchor_generator.strides,
                mlvl_anchors):
            assert cls_score.size()[-2:] == bbox_pred.size()[-2:]
            assert stride[0] == stride[1]
            scores = cls_score.permute(0, 2, 3, 1).reshape(
                batch_size, -1, self.cls_out_channels).sigmoid()   # sigmoid,[N,HWC]
            bbox_pred = bbox_pred.permute(0, 2, 3, 1)   # [N,H,W,4*(16+1)]
            bbox_pred = self.integral(bbox_pred) * stride[0]   # [N,H,W,4]
            bbox_pred = bbox_pred.reshape(batch_size, -1, 4)    # [N,HW4]

bbox_pred = self.integral(bbox_pred) * stride[0] # [N,H,W,4]不同之处只需使用self.integral变换回原来的lrtb格式就行。

3.消融实验

1.QFL

目标检测之GFL_第4张图片

2.DFL

目标检测之GFL_第5张图片
目标检测之GFL_第6张图片
这里对DFL中取合适的数量和间隔进行了实验。

3.QFL+DFL

目标检测之GFL_第7张图片

总结

十分优秀论文和研究工作!作者在论文后面还有些数据分析,值得阅读。并且作者后续已经发布了GFLV2,基于V1的情况下,利用学习到的bbox的分布来指导分类-质量评估的生成,能带来几乎无损涨点。后续有时间应该会介绍下GFLV2。
博客里所说都是个人理解,欢迎大家进行留言讨论!

你可能感兴趣的:(目标检测,深度学习,计算机视觉)