[论文阅读] FCOS: Fully Convolutional One-Stage Object Detection

FCOS: Fully Convolutional One-Stage Object Detection

Background

  • anchor-based 方法的不足:

    • anchor的参数过多,且对最终的检测性能有较大的影响。例如anchor的大小,长宽比等。
    • anchor导致的正负样本不平衡问题较为严重
    • anchor-based的方法分为two-stage和one-stage。two-stage的典型代表:faster rcnn等。one-stage的典型代表:SSD、YOLO系列、Retina等。
  • 现有anchor-free的方法的不足:

    • 描述:anchor-free方法是指算法不需要事先指定anchor的参数,通过检测关键点来直接预测边界框。
    • CornerNet检测物体的左上角和右上角。但是需要通过耗时的分组策略来找到左上角和右上角的对应关系
    • CenterNet通过预测物体的中心点和大小来检测物体
  • Contributions

    • 本文提出的FCOS方法一方面可以利用原有的位置信息,另一方面又不需要anchor的参数。并且推理起来也不需要太过复杂的规则,速度较快。

Method

算法的整个思路是针对特征图上的每一个位置来进行预测(ps有点像anchor-based的方法,anchor-based每个pixel预测多个框)。判断每个位置是否需要负责预测bbox,如果需要则确定其类别和对应的bbox优化值。如果不需要则将pixel作为背景处理。

Framework

  • 网络的结构图如下所示,是一个传统的FPN结构,作者对FPN每一层输出的特征图都接了一个head用于预测如下三个信息
    • classification:当前pixel类别,包括背景和实例类别
    • Regression:当前pixel如果为非背景,则需要预测regression信息,其优化的目标如下图所示。(l,r,t,b).其中黄色为特征图上的点。
    • centerness:预测当前pixel的中心值,用于回归bbox的难度。先验在于难度越高,可能bbox回归的就越不准
      image.png
      image.png

Loss

  • 优化的目标:如上所示,我们优化的目标是l、r、t、b。他们的定义如下所示,其中(x,y)代表特征图上的location在原图上的位置。 x 0 i x_0^{i} 代表的就是真值
    image.png
  • 上面确定了我们的优化目标,针对上述的优化函数可以任意选择,例如smooth l1, l2, iou loss等
  • 那么我们如何生成每个location对应的ground truth呢?
    • 本质上,我们需要找到每个location对应的gt_bbox,然后将找到的gt_bbox作为真值,计算真实的l、r、t、b四个值即可。
    • 如何找到确认location和gt_bbox之间的关系呢?作者提出了如下两条规则
      • location落在某个gt_bbox内部,这里的落在内部可以直接的理解为落在内部,也可以替换成其他规则
      • 为了加速收敛,l、r、t、b应该在某一个范围内,如果不在这个范围内,就以为着应该由其他location来优化
    • 在满足上述条件下,可能存在多个gt_bbox,那怎么办?作者提出选择面积最小的bbox作为真值。其实也可以优化多个?(multiple instance prection)
    • code
import numpy as np


def compute_targets_for_locations(locations, targets, size_ranges, num_loc_list,
                                  center_sampler):
    """

    Args:
        locations (): [N, 2] 所有FPN层cat后的位置, 针对每张图片都是一样的
        targets (): [M, K, 5] 对应的bbox, M是batch size
        size_ranges (): [N, 2] 各个位置所在FPN回归的限制[min_max]
        num_loc_list (): list, 各个FPN层的location个数
        center_sampler (): bool, 采样的策略
    Description:
        - Target: 针对每个location我们需要计算其对应的label和reg_bbox值(lrtb)。
        - Condition: 成为正样本有两个条件,针对每个location
            - 其对应的location落在某个gt_bbox内部。这里也可以通过center_sampler控制一个更复杂的策略
            - lrtb最大的值不应该超过size_range
            - 如果上述两个条件针对多个gt_bbox同时成立,如果存在多个gt_bbox则选择最小面积的一个作为该location的GT
    Returns:
        labels (): [M, N]
        reg_bboxes (): [M, N]
    """
    num_classes = 10
    labels = []
    reg_bboxes = []
    xs, ys = locations[:, 0], locations[:, 1]
    for sample_id in range(len(targets)):
        # [K, 4]
        gt_bboxes = targets[sample_id, :4]
        gt_labels = targets[sample_id, 4]
        area = (gt_bboxes[:, 2] - gt_bboxes[:, 0]) * \
               (gt_bboxes[:, 3] - gt_bboxes[:, 1])

        l = xs[:, None] - gt_bboxes[:, 0][None]
        r = gt_bboxes[:, 2][None] - xs[:, None]
        t = xs[:, None] - gt_bboxes[:, 1][None]
        b = gt_bboxes[:, 3][None] - ys[:, None]
        if gt_bboxes.shape[0] == 0:
            # without any gt_bboxes
            # bg is zero, if bg is last, then add the num_classes
            labels.append(
                np.zeros(len(locations)) + num_classes
            )
            reg_bboxes.append(
                np.zeros([len(locations), 4])
            )
            continue

        reg_bboxes_per_img = np.stack([l, r, t, b], axis=2)

        if center_sampler:
            # 中心采样
            # TODO 如何理解采样,是指针对什么采样
            pass
        else:
            # 全部采样
            # 通过判断location有没有落在bbox内部判断,如果落在内部,则min应该是大于0
            is_in_gt_bboxes = np.min(reg_bboxes_per_img, axis=-1) > 0

        # [N, M], 每个位置需要回归的最大值
        max_reg_bboxes_per_img = np.max(reg_bboxes_per_img, axis=-1)

        is_card_in_the_level = np.logical_and(
            max_reg_bboxes_per_img > size_ranges[:, 0],
            max_reg_bboxes_per_img < size_ranges[:, 1]
        )

        # [N, M]
        locations_to_gt_area = np.tile(np.expand_dims(area, axis=0),
                                       reps=[len(locations), 1])
        locations_to_gt_area[is_in_gt_bboxes == 0] = np.Inf
        locations_to_gt_area[is_card_in_the_level == 0] = np.Inf

        # [N]
        min_inds = np.argmin(locations_to_gt_area, axis=-1)
        min_area = locations_to_gt_area[:, min_inds]
        cur_sample_labels = gt_labels[min_inds]
        cur_sample_labels[min_area == np.Inf] = num_classes
        cur_sample_reg_bboxes = reg_bboxes_per_img[range(len(locations)),
                                                   min_inds]
        labels.append(cur_sample_labels)
        reg_bboxes.append(cur_sample_reg_bboxes)
    return labels, reg_bboxes

(完)