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回归的就越不准
Loss
- 优化的目标:如上所示,我们优化的目标是l、r、t、b。他们的定义如下所示,其中(x,y)代表特征图上的location在原图上的位置。
代表的就是真值
- 上面确定了我们的优化目标,针对上述的优化函数可以任意选择,例如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