FCOS文献阅读代码分析

在这里插入图片描述

论文地址:https://arxiv.org/abs/1904.01355
代码地址:https://github.com/tianzhi0549/FCOS
摘要:

在这里插入图片描述
       本文提出一种基于像素级预测一阶全卷积目标检测(FCOS)来解决目标检测问题,类似于语音分割。目前大多数先进的目标检测模型,例如RetinaNet、SSD、YOLOv3、Faster R-CNN都依赖于预先定义的锚框。相比之下,本文提出的FCOS是anchor box free,而且也是proposal free,就是不依赖预先定义的锚框或者提议区域。通过去除预先定义的锚框,FCOS完全的避免了关于锚框的复杂运算,例如训练过程中计算重叠度,而且节省了训练过程中的内存占用。更重要的是,本文避免了和锚框有关且对最终检测结果非常敏感的所有超参数。由于后处理只采用非极大值抑制(NMS),所以本文提出的FCOS比以往基于锚框的一阶检测器具有更加简单的优点。

二.Introduction

       在基于anchor-base的目标检测网络有以下缺点:

  • 检测性能对锚盒的尺寸、长宽比和数量敏感。在RetinaNet上运用了非常多的超参数才使AP在COCO数据集上提升了4%,并且这些超参数在anchor-base中需要精心设计才行。
  • 即使经过精心设计,由于盒的比例尺和长宽比是固定的,探测器在处理形状变化较大的候选对象时也会遇到困难,特别是对于小对象,预定义的锚盒还会影响检测器的泛化能力,在不同的任务上锚框的长宽比需要调整。比如对广告牌进行的检测,往往广告牌的长宽比不协调,这时候如果用在COCO数据集上定义的锚框比例,可能在效果上不会很好。
  • 在训练中往往出现样本不平衡情况,正样本太多,负样本太少。
  • 在计算ground-truth和每个锚框的IoU时,造成了大量的计算量。
    FCOS提出了这样的疑问
    FCOS提出了这样的疑问:能否让目标检测像语义分割一样逐像素的对目标做检测。
            基于anchor-based的检测方法偏离全卷积预测的框架,而本文尝试类似于语义分割的像素级预测应用至目标检测任务中。因此,目标检测,语义分割,关键点检测等几种任务可以糅合到一个模型中。一些工作如Dense-Box,UnitBox曾利用基于FCN-based的框架进行目标检测。但这些方法在某一层的feature map上直接预测4D坐标及类别,如下图左侧所示,4D向量表示了边框的边距离该像素点的偏移。这些方法与语义分割的全卷积相类似,但每个位置需要回归一个连续的4D向量。为了处理不同大小的边框,DenseBox将训练图片调整为固定尺寸。DenseBox必须要在图像金字塔上进行检测,违反了全卷积对所有卷积只计算一次的思想。而且,这些方法大多应用在目标检测的特殊场景中,比如文本检测或者人脸检测。如下图右侧所示,较多的重叠框造成了模糊,无法确定重叠区域应该对哪个框进行回归。本文证明通过FPN结构可以消除这种模糊。本文发现在距离目标中心较远的位置会预测一定数量低质量的边界框。为了打压这些边框,本文设计了一个新的分支"center-ness",用于预测一个像素与对应边框中心的偏差。所得的分数用于down-weight 低质量的检测框,最后通过NMS将检测结果进行融合
FCOS有以下优点:
  • 可以将目标检测与语义分割,等基于全卷积的任务结合,更加简单。

  • 目标检测成为无建议和无锚,这大大减少了设计参数的数量及内存。

  • FCOS也可以与RPN结合取得更好的成绩。

  • 该检测器可直接扩展到其他视觉任务,只需进行最小的修改,包括实例分割和关键点检测。

算法详细介绍

在这里插入图片描述

可以看到网络也用到了FPN的结构,Backone使用的是Resnet-50,得到最后的P3,P4,P5,P6,P7再分别对五个特征层做分类和回归。

与anchor-based算法的区别
  • 对输出特征图(即P3,P4,P5,P6,P7,后面直接成为特征图;)的每个像素点都做回归和分类。分别找到特征图每个像素点映射到原图的位置坐标(s/2+xs,s/2+ys),s为feature map的总步长。
  • 如果它(x,y)落入任何ground-truth框中,就是正样本,落在框外定义为负样本,并且定义落入真实框中要满足一定的条件,而再anchor-based中是选择IOU高的作为正样本。作者认为FCOS主要优于anchor-based的主要原因就是这个。
  • FCOS训练了C个二分类器(C为数据集类别数目)。
FCOS的损失函数:

在这里插入图片描述
使用FPN网络对FCOS进行多级预测
pic2
       如图所示:网球拍和人存在重叠区域,对于重叠的两个物体尺度变化大,为了可以很好的减少这种情况使用了FPN网络结构,对P3,P4,P5,P6,P7层进行检测,这样有利与检测小目标和大目标物体。
       作者为了减少尺度差异大的物体重叠,引入参数 m i m_i mi 为特征层 i i i 的最大距离,如果一个location(x, y)满足 m a x ( l ∗ , t ∗ , r ∗ , b ∗ ) > m i max(l*,t*,r*,b*)>m_{i} max(l,t,r,b)>mi或者max(l*,t*,r*,b*)<m_{i-1},那么我们在这个特征层就将其视为负样本,不在进行回归。其中 [公式] 分别设置为0。因为对于这些框位于gt-boxes的边角处难以回归。如果一个像素点在同一层落到了多个GT区域,这样也会ambiguous。这是作者简单的会使用最小区域来作为回归目标。

生成特征图映射到原始图像中的坐标(x,y)

def coords_fmap2orig(feature,stride):
    '''
    transfor one fmap coords to orig coords
    Args
    featurn [batch_size,h,w,c]
    stride =[8,16,32,64,128]
    Returns 
    coords [n,2]
    '''
    h,w=feature.shape[1:3]
    shifts_x = torch.arange(0, w * stride, stride, dtype=torch.float32)

    # 创建从0开始 以原图宽度结束 步幅为缩放比例的列表
    shifts_y = torch.arange(0, h * stride, stride, dtype=torch.float32)
    # 创建从0开始 以原图高度结束 步幅为缩放比例的列表
    shift_y, shift_x = torch.meshgrid(shifts_y, shifts_x)
    # torch.Size([68, 88]) torch.Size([68, 88])
    shift_x = torch.reshape(shift_x, [-1])
    shift_y = torch.reshape(shift_y, [-1])
    coords = torch.stack([shift_x, shift_y], -1) + stride // 2

    return coords
正负样本的生成
def_gen_level_targets(self,out,gt_boxes,classes,stride,limit_range,sample_radiu_ratio=1.5):
        '''
        Args  
        out list contains [[batch_size,class_num,h,w],[batch_size,1,h,w],[batch_size,4,h,w]]  
        gt_boxes [batch_size,m,4]  
        classes [batch_size,m]  
        stride int  
        limit_range list [min,max]  
        Returns  
        cls_targets,cnt_targets,reg_targets
        '''
        cls_logits,cnt_logits,reg_preds=out

        batch_size=cls_logits.shape[0]
        class_num=cls_logits.shape[1]
        m=gt_boxes.shape[1]

        cls_logits=cls_logits.permute(0,2,3,1) #[batch_size,h,w,class_num]  
        coords=coords_fmap2orig(cls_logits,stride).to(device=gt_boxes.device)#[h*w,2]

        cls_logits=cls_logits.reshape((batch_size,-1,class_num))#[batch_size,h*w,class_num]  
        cnt_logits=cnt_logits.permute(0,2,3,1)
        cnt_logits=cnt_logits.reshape((batch_size,-1,1))
        reg_preds=reg_preds.permute(0,2,3,1)
        reg_preds=reg_preds.reshape((batch_size,-1,4))

        h_mul_w=cls_logits.shape[1]

        x=coords[:,0]# x,y坐标
        y=coords[:,1]

        l_off=x[None,:,None]-gt_boxes[...,0][:,None,:]#[1,h*w,1]-[batch_size,1,m]-->[batch_size,h*w,m]
        t_off=y[None,:,None]-gt_boxes[...,1][:,None,:]# x,y离gt_boxes左上角距离
        r_off=gt_boxes[...,2][:,None,:]-x[None,:,None]# x,y离gt_boxes右下角距离
        b_off=gt_boxes[...,3][:,None,:]-y[None,:,None]
        ltrb_off=torch.stack([l_off,t_off,r_off,b_off],dim=-1)#[batch_size,h*w,m,4]


        areas=(ltrb_off[...,0]+ltrb_off[...,2])*(ltrb_off[...,1]+ltrb_off[...,3])#[batch_size,h*w,m]

        off_min=torch.min(ltrb_off,dim=-1)[0]#[batch_size,h*w,m]
        off_max=torch.max(ltrb_off,dim=-1)[0]#[batch_size,h*w,m]
        # 找到(l,r,t,b)中最小的,如果最小的大于0,那么这个点肯定在对应的gt框里面,则置1,否则为0
        mask_in_gtboxes=off_min>0# 每个点的值(T/F)代表该点是否在标注框内
        # 找到(l,r,t,b)中最大的,如果最大的满足范围约束,则置1,否则为0
        mask_in_level=(off_max>limit_range[0])&(off_max<=limit_range[1])# 判断每个点是否在该层关注大小内


        radiu=stride*sample_radiu_ratio
        gt_center_x=(gt_boxes[...,0]+gt_boxes[...,2])/2# 获得中心x,y坐标
        gt_center_y=(gt_boxes[...,1]+gt_boxes[...,3])/2
        c_l_off=x[None,:,None]-gt_center_x[:,None,:]#[1,h*w,1]-[batch_size,1,m]-->[batch_size,h*w,m]
        c_t_off=y[None,:,None]-gt_center_y[:,None,:]
        c_r_off=gt_center_x[:,None,:]-x[None,:,None]
        c_b_off=gt_center_y[:,None,:]-y[None,:,None]
        c_ltrb_off=torch.stack([c_l_off,c_t_off,c_r_off,c_b_off],dim=-1)#[batch_size,h*w,m,4]
        c_off_max=torch.max(c_ltrb_off,dim=-1)[0]
        mask_center=c_off_max<radiu

        mask_pos=mask_in_gtboxes&mask_in_level&mask_center#[batch_size,h*w,m]
        # 找到(l,r,t,b)中最大的,如果最大的满足范围约束,则置1,否则为0
        # 将不满足范围约束的也置为无穷,因为下面的代码要找最小的
        areas[~mask_pos]=99999999
        # 找到每个点对应的面积最小的gt框(因为可能有多个,论文取了最小的)
        # min_area和min_area_inds的size都为(num_points,)
        areas_min_ind=torch.min(areas,dim=-1)[1]#[batch_size,h*w]

        reg_targets=ltrb_off[torch.zeros_like(areas,dtype=torch.bool).scatter_(-1,areas_min_ind.unsqueeze(dim=-1),1)]#[batch_size*h*w,4]
        reg_targets=torch.reshape(reg_targets,(batch_size,-1,4))#[batch_size,h*w,4]

        classes=torch.broadcast_tensors(classes[:,None,:],areas.long())[0]#[batch_size,h*w,m]
        cls_targets=classes[torch.zeros_like(areas,dtype=torch.bool).scatter_(-1,areas_min_ind.unsqueeze(dim=-1),1)]
        cls_targets=torch.reshape(cls_targets,(batch_size,-1,1))#[batch_size,h*w,1]

        left_right_min = torch.min(reg_targets[..., 0], reg_targets[..., 2])#[batch_size,h*w]
        left_right_max = torch.max(reg_targets[..., 0], reg_targets[..., 2])
        top_bottom_min = torch.min(reg_targets[..., 1], reg_targets[..., 3])
        top_bottom_max = torch.max(reg_targets[..., 1], reg_targets[..., 3])
        cnt_targets=((left_right_min*top_bottom_min)/(left_right_max*top_bottom_max+1e-10)).sqrt().unsqueeze(dim=-1)#[batch_size,h*w,1]

        assert reg_targets.shape==(batch_size,h_mul_w,4)
        assert cls_targets.shape==(batch_size,h_mul_w,1)
        assert cnt_targets.shape==(batch_size,h_mul_w,1)

        #process neg coords
        mask_pos_2=mask_pos.long().sum(dim=-1)#[batch_size,h*w]
        # num_pos=mask_pos_2.sum(dim=-1)
        # assert num_pos.shape==(batch_size,)
        mask_pos_2=mask_pos_2>=1
        assert mask_pos_2.shape==(batch_size,h_mul_w)
        cls_targets[~mask_pos_2]=0#[batch_size,h*w,1]
        cnt_targets[~mask_pos_2]=-1
        reg_targets[~mask_pos_2]=-1  
        return cls_targets,cnt_targets,reg_targets
Center-ness

在这里插入图片描述
        通过多级预测之后发现FCOS和基于锚框的检测器之间仍然存在着一定的距离,主要原因是距离目标中心较远的位置产生很多低质量的预测边框。
        在FCOS中提出了一种简单而有效的策略来抑制这些低质量的预测边界框,而且不引入任何超参数。具体来说,FCOS添加单层分支,与分类分支并行,以预测"Center-ness"位置。
在这里插入图片描述
       center-ness(可以理解为一种具有度量作用的概念,在这里称之为"中心度"),中心度取值为0,1之间,使用交叉熵损失进行训练。并把损失加入前面提到的损失函数中。测试时,将预测的中心度与相应的分类分数相乘,计算最终得分(用于对检测到的边界框进行排序)。因此,中心度可以降低远离对象中心的边界框的权重。因此,这些低质量边界框很可能被最终的非最大抑制(NMS)过程滤除,从而显着提高了检测性能

实验结果

       在这里插入图片描述
在召回率方便表现接近目前最先进的基于锚框的检测器。

2. 有无Center-ness的结果对比

在这里插入图片描述
使用了center-ness分支后AP提升了4%左右。
3.与其他模型相比
在这里插入图片描述
与目前最主流的一些一阶、二阶检测器对比,在检测效率方面FCOS优于Faster R-CNN、YOLO、SSD这些经典算法。
这里提供另外一个容易迁移并理解的FCOS代码:https://github.com/VectXmy/FCOS.Pytorch

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值