MPViT : Multi-Path Vision Transformer for Dense Prediction

https://arxiv.org/abs/2112.11010(CVPR2022)

https://github.com/youngwanLEE/MPViT

Introduction

  • 在这项工作中,作者以不同于现有Transformer的视角,探索多尺度path embedding与multi-path结构,提出了Multi-path Vision Transformer(MPViT)
  • 通过使用 overlapping convolutional patch embedding,MPViT同时嵌入相同大小的patch特征。然后,将不同尺度的Token通过多条路径独立地输入Transformer encoders,并对生成的特征进行聚合,从而在同一特征级别上实现精细和粗糙的特征表示。
  • 在特征聚合步骤中,引入了一个global-to-local feature interaction(GLI)过程,该过程将卷积局部特征与Transformer的全局特征连接起来,同时利用了卷积的局部连通性和Transformer的全局上下文。

Vision Transformers for dense predictions.

  • 密集的计算机视觉任务,如目标检测和分割,需要有效的多尺度特征表示,以检测或分类不同大小的物体或区域。Vision Transformer(ViT)构建了一个简单的多阶段结构(即精细到粗糙),用于使用单尺度patch的多尺度表示。然而ViT的变体专注于降低自注意的二次复杂度,较少关注构建有效的多尺度表示。
  • CoaT通过使用一种co-scale机制,同时表示精细和粗糙的特征,允许并行地跨层注意,从而提高了检测性能。然而,co-scale机制需要大量的计算和内存开销,因为它为基础模型增加了额外的跨层关注(例如,CoaT-Lite)。因此,对于ViT体系结构的多尺度特征表示仍有改进的空间。

Comparison to Concurrent work.

  • CrossViT利用了不同的patch大小和单级结构中的双路径,如ViT和XCiT。然而,CrossViT的分支之间的相互作用只通过[CLS]token发生,而MPViT允许所有不同规模的patch相互作用。此外,与CrossViT(仅限分类)不同的是,MPViT更普遍地探索更大的路径维度(例如,超过两个维度),并采用多阶段结构进行密集预测。

Method

Conv-stem

输入图像大小为:H×W×3,两层卷积:采用两个3×3的卷积,通道分别为C2/2,C2,stride为2,生成特征的大小为H/4×W/4×C2,其中C2为stage 2的通道大小。

  • 说明:每个卷积之后都是Batch Normalization 和一个Hardswish激活函数。
  • In LeViT , a convolutional stem block shows better low-level representation (i.e., without losing salient information) than non-overlapping patch embedding.

从stage 2到stage 5,作者在每个阶段对所提出的Multi-scale Patch Embedding(MS-PatchEmbed)和Multi-path Transformer(MP-Transformer)块进行堆叠

Multi-Scale Patch Embedding

通过改变stride和padding来调整token的序列长度。也就是说,可以输出具有不同patch大小的相同大小(即分辨率)的特征。因此,作者并行地形成了几个具有不同卷积核大小的卷积patch embedding层。例如,如图1所示,可以生成相同序列长度的不同大小的vision token,patch大小分别为3×3,5×5,7×7。

由于具有相同通道和滤波器大小的连续卷积操作扩大了接受域,并且需要更少的参数,在实践中选择了连续的3×3卷积层。为了减少参数量,在实践中选择了两个连续的3×3卷积层代替5×5卷积。对于triple-path结构,使用三个连续的3×3卷积,通道大小为C’,padding为1,步幅为s,其中s在降低空间分辨率时为2,否则为1。

  • 说明:为了减少模型参数和计算开销,采用3×3深度可分离卷积,包括3×3深度卷积和1×1点卷积。
  • 每个卷积之后都是Batch Normalization 和一个Hardswish激活函数。

接着,不同大小的token embedding features 分别输入到transformer encoder中。

Multi-path Transformer

原因:Transformer中的self-attention可以捕获长期依赖关系(即全局上下文),但它很可能会忽略每个patch中的结构性信息和局部关系。相反,cnn可以利用平移不变性中的局部连通性,使得CNN在对视觉对象进行分类时,对纹理有更强的依赖性,而不是形状。

因此,MPViT以一种互补的方式将CNN与Transformer结合起来。

  • 为了表示局部特征 
  • 采用了一个 depthwise residual bottleneck block,包括1×1卷积、3×3深度卷积和1×1卷积和残差连接。
  • 为了减轻多路径结构的计算负担,使用了CoaT中提出的有效的因素分解自注意:

Global-to-Local Feature Interaction

将局部特征和全局特征聚合起来:

为了保持可比性的参数和FLOPs,增加路径的数量需要减少通道C或层数L(即,transformer encoder的数量)。作者通过减少C而不是L,从单路径(即CoaT-Lite baseline)扩展到triple-path。在消融研究中,验证了减少C比减少L获得更好的性能(见表5)。由于stage2的特征分辨率较高,导致计算成本较高,作者在stage2中将triple-path模型的路径数设置为2。从stage3开始,三路径模型有3条路径。

作者还发现,虽然 triple-path和双路径在ImageNet分类中产生相似的精度,但 triple-path模型在密集预测任务中表现出更好的性能。因此,建立了基于 triple-path结构的MPViT模型。MPViT的详细情况见表1。

Experiments

Ablation study

对MPViT-XS的每个组件进行消融研究,以研究提出的多路径结构对图像分类和使用Mask R-CNN检测的有效性。

Exploring path dimension.

DETR :End to End Object Detection with Transformers

目标检测领域的里程碑式的工作

https://arxiv.org/abs/2005.12872

code:https://github.com/facebookresearch/detr Facebook AI(meta AI)

DETRDetection Transformers

文章题目:简单明了,包含两个关键词:端到端、transformer

目标检测领域:从目标检测开始火到detr都很少有端到端的方法,大部分方法最后至少需要后处理操作(NMS,non-maximum suppression非极大值抑制)。无论是proposal based方法、anchor based方法、non-anchor based方法,最后都会生成很多预测框,如何去除这些冗余的框就是NMS要做的事情。

问题:有了NMS,模型调参就会很复杂,而且即使训练好了一个模型,部署起来也非常困难(NMS不是所有硬件都支持)。所以一个简单的、端到端模型一直是大家梦寐以求的,而detr的出现解决了这些痛点。

一、Detr目标:

1、不需要proposal、不需要anchor,直接利用transformer这种全局建模的能力,把目标检测看做是集合预测问题

2、因为有了这种全局建模的能力,detr不会有那么多冗余框,最后出什么结果就是什么结果,不需要NMS做后处理,让模型的训练和部署简单不少

目的:不想让大家觉得目标检测是比图像分类难很多的任务,都可以用简单的,优雅的框架做出来

二、摘要

作者说,他们就是把目标检测的任务看成是一个集合预测问题:目标检测本来任务就是给定一个图像,预测一堆框,每个框不仅要知道的其坐标,还要知道框里包含物体的类别,这些框就是一个集合,不同的图像对应的集合也是不同的,给定一个图片,我要预测这个集合

因此这篇文章就是把目标检测做成一个端到端的框架,把之前特别依赖人的先验知识的部分删掉了(NMS部分、anchor),一旦把这两个部分拿掉之后,我们也不用费尽心思设计这种anchor,最后不会出现这么多框,不会用到NMS,也不会用到很多超参去调。两个贡献:1、使用新的目标函数,通过二分图匹配的方式,强制模型输出一组独一无二的预测(没有那么多冗余框,每个物体理想状态下就会生成一个框)。2、另外使用encoder-decoder的架构。

两个小贡献:

1、decoder还有另外一个输入learned object query,类似anchor的意思(给定这些object query之后,detr就可以把learned object query和全局图像信息结合一起,通过不同的做注意力操作,从而让模型直接输出最后的一组预测框)

2、想法&&实效性:并行比串行更合适,并不是检测一个大物体前必须先检测一个小物体,或从左到右检测,我们希望越快越好

DETR的好处:

1、简单性:想法上简单,不需要一个特殊的library,只要硬件支持transformer或CNN,就一定支持detr

2、性能:在coco数据集上,detr和一个训练非常好的faster RCNN基线网络取得了差不多的效果,模型内存和速度也和faster RCNN差不多

3、想法好,解决了目标检测领域很多痛点,写作好

4、别的任务:全景分割任务上detr效果很好,detr能够非常简单拓展到其他任务上

三、引言

1、目标检测任务:对每一个感兴趣的物体,去预测一些框,和物体类别,就是一个集合预测问题。

2、现在大多数好用的目标检测器,都是用间接的方式去处理集合预测问题,(1)比如proposal方式(如RCNN系列工作),(2)anchor方式(YOLO系列,focal loss),non-anchor based方法(物体中心点center net,FCOS),他们都没有直接做集合预测任务,而是设计一个替代(回归、分类)解决目标检测问题。所有这些方法性能受限于后处理操作(NMS),由于用了anchor和NMS导致检测器都非常复杂,难以优化和调参。

3、端到端的思想已经在别的很多任务里大范围使用,而且使任务更加简单好用,我们不要先验知识,就是要用一个端到端网络。

detr流程(训练):

1、CNN提特征

2、特征拉直,送到encoder-decoder中,encoder作用:进一步学习全局信息,为近下来的decoder,也就是最后出预测框做铺垫。直观的解释为什么需要使用transformer encoder呢?如果使用了transformer encoder,那么每一个点或者说每一个特征就会跟着图片里面的其他的特征有交互了,这样大概就知道那块是那个物体,对于同一个物体就应该只出一个框而不是好多框,所以全局的建模有利于移除冗余的框。

3、decoder生成框的输出,当你有了图像特征之后,还会有一个object query(限定了你要出多少框),通过query和特征在decoder里进行自注意力操作,得到输出的框(文中是100,无论是什么图片都会预测100个框)

4、生成的100个框如何与ground truth这个框做匹配并计算 loss? :二分图匹配,如上图,我们计算100个预测的框和2个GT框的matching loss,决定100个预测框哪两个是独一无二对应到红黄色的GT框,用匹配的框去算目标检测的loss。而没有匹配到的98个框就会被标记为没有物体。

5、推理1、2、3一致,第四步loss不需要,直接在最后的输出上用一个阈值卡一个输出的置信度,置信度比较大(>0.7的)保留,置信度小于0.7的当做背景物体。

结果:

1、detr对大物体预测很准,归功于transformer,能进行全局建模(原来使用anchor的话就会受限于anchor大小)

2、缺陷:对小物体效果不好(多尺度、多特征,可以提高小物体的检测)后续改进:Deformable DETR

3、detr训练很慢,500个epoch(coco大多数模型一般训练几十个epoch就行)

检测效果:

detr由于使用transformer全局建模,没有用anchor,想检测多大物体就检测多大,所以检测大物体效果较好。detr框架太简单,没有多尺度特征,没有FPN,没有复杂的目标检测头,所以在小目标检测效果不好

四、相关工作

目标检测:

目前大多数的检测器是根据初始猜测做预测:

1、two-stage:初始猜测是中间的proposal

2、one-stage:初始猜测是anchor或物体中心点

最近一篇论文做了详细比较,发现他们的性能和刚开始的初始猜测非常相关,怎么做后处理对性能影响至关重要

怎么后处理:

1、集合思想:可学习的NMS方法、关系型网络,可以利用自注意力方法去处理物体之间的联系,得出独一无二的预测,就不需要后处理的步骤(性能较低)

解决:人工干预:手工设计的场景特征帮助模型学习,但是detr目标是想让目标检测任务更加简单,不希望用到过多人工先验知识

2、循环检测器:encoder-decoder:让detr工作主要原因:transformer

五、方法

分两块:1、基于集合的目标函数怎么做,作者如何通过二分图匹配把预测的框和GT框连接在一起,算得目标函数 2、detr具体模型架构

目标函数部分:

detr模型最后输出是一个固定集合,无论图片是什么,最后都会输出n个(本文n=100)预测框

问题:detr每次都会出100个输出,但是实际上一个图片的GT的bounding box可能只有几个,如何匹配?如何计算loss?怎么知道哪个预测框对应GT框?

作者这里把这个问题转换成了一个二分图匹配的问题:

二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。简而言之,就是顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻。

加权二分图匹配可以认为是有ABC三个工人,以及xyz三个工作,每个工人去做xyz工作的花费不同,如何去为每一个个人安排一个工作,使得最后我们的花费最低,可以使用遍历的方法,亦可以有很多高效的方法:匈牙利算法。

另外scipy包提供的linear sum assignment可以完成这个最优排列。detr论文里:代码也用的linear sum assignment函数来计算对应的匹配关系,只需要提供一个cost matrix矩阵就可以。a,b,c看成100个预测框,x,y,z看成GT框, cost matrix 损失矩阵未必都是正方形,最后丢到这个函数里面得到一个最优匹配。

那么对于目标检测任务,cost matrix 损失矩阵的值应该放些什么?loss包含两部分:分类loss、出框的准确度。所以也就是遍历所有的预测的框,那这些预测的框和gt框去算两个loss,然后把这个loss放到cost matrix矩阵 就可以了。这样就得到了对应gt的预测框(一对一),进而计算loss,梯度回传更新模型参数。

detr主体网络框架:

输入图片大小:3*800*1066(3:rgb),首先使用卷积网络获得特征:2048*25*34,然后降维变成256*25*34,然后给transformer添加位置信息:大小也是256*25*34,特征+位置作为transformer输入,特征拉直: 256*25*34 ==》 850*256,850就是序列长度,256是向量维度。后面的transformer encoder就跟普通的transformer encoder一样,输出==输出,仍然是850*256,接下来送入decoder里面。不同于一般的decoder,这里的object queries是一个可学习的,100*256大小的向量。在decoder里面做cross attension。输入 object queries ,另外一个输入是来自encoder的全局特征850*256.这两个去做自注意力操作,得到一个100*256的特征的decoder输出。最后添加一个检测头全连接层(FFN),获得类别预测(91类)和框预测(4:框的中心的+高度宽度),获得了100个框,利用匈牙利算法跟gt匹配,然后求loss,更新模型。

六、实验

检测效果:detr由于使用transformer全局建模,没有用anchor,想检测多大物体就检测多大,所以检测大物体效果较好。detr框架太简单,没有多尺度特征,没有FPN,没有复杂的目标检测头,所以在小目标检测效果不好。

下面的表格给出了 DETR 与基线 Faster RCNN 的定量性能对比。最上面一部分的 Faster RCNN 的性能结果是 Detectron2 的实现,之所以将 Faster RCNN 分成两部分,是因为 DETR 中使用了近年来很多新的训练 trick,如 GIoU loss、更强的数据增强策略、更长的训练时间,因此作者团队添加这些策略重新训练了 Faster RCNN,以作公平的对比。

近年来的新的训练策略对于目标检测模型的提升非常明显。对比表格的第一、第二部分,完全相同的模型,只是用了更优的训练策略,基本能稳定涨两个点。在同样的训练策略、网络规模大小的情况下,DETR 比 Faster RCNN 高 1-2 个点。对比表格的后两部分可以观察到这一点,DETR 对比基线的 Faster RCNN 还是还是有提升的。

DETR 在大物体的检测上远超 Faster RCNN,但是在小物体的检测上却也低了不少。

表格的后三列分别是小、中、大物体的检测性能,可以观察到 DETR 在大物体的检测上更出色,但是对于小物体的检测甚至远不如 Faster RCNN。大物体检测性能的提升得益于 Transformer 结构的全局建模能力,且没有预置的固定 anchor 的限制,因此预测框想多大就多大。而 DETR 在小物体上表现不佳,是因为本文中 DETR 的模型还是一个比较简单的模型,没有做很多针对目标检测的优化设计,比如针对小物体、多尺度的 FPN 设计。DETR 的网络结构还有待后续工作来改进。

表 1 detr和faster RCNN的对比,+表示用更好的训练策略把三个模型重新训练一遍

gflops参数:每秒进行的浮点运算次数,flops越小,模型越小,跑起来越快?X。如果更关心速度,比较fps

首先我们来看对于 Encoder 的可视化,下图展示了对于一组参考点的 Encoder 注意力热力图的可视化,即参考点对于图像中所有其他点自注意力值的大小。可以观察到,Transformer Encoder 基本已经能够非常清晰地区分开各个物体了,甚至热力图已经有一点实例分割的 mask 图的意思了。在有一定遮挡的情况下(左侧两头牛),也能够清楚地分开哪个是哪个。这种效果正是 Transformer Encoder 的全局建模能力所带来的,每个位置能够感知到图像中所有的其他位置。因此能够区分出图像中的不同物体,从而对于一个物体,尽量只出一个预测框。

通过前面的可视化,我们已经看到,Encoder 学习了一个全局的特征,基本已经能够区分开图中不同的物体。但是对于目标检测来说,大致地区分开不同的物体是不够的,我们还需要精确的物体的边界框坐标,这部分就由 Decoder 来做。

下图在 Decoder 特征中对每个不同的物体做了注意力的可视化,比如左图中的两头大象分别由蓝色和橙色表示。可以观察到,Decoder 网络中对于每个物体的注意力都集中在物体的边界位置,如大象的鼻子、尾巴、象腿等处。作者认为这是 Decoder 在区分不同物体边界的极值点(extremities),在 Encoder 能够区分开不同的物体之后,Decoder 再来关注不同物体边界的具体位置,最终精准地预测出不同物体的边框位置。因此,Encoder-Decoder 的结构是必要的,它们各司其职,一个都不能少。

扩展到全景分割任务

作者同时还将该网络应用于全景分割任务中.增加一个分割的head就可以。

下图是20个object query可视化(n=100,这里只有20个)

object query 到底学了什么(绿色代表小的bounding box,红色代表大的横向bounding box,蓝色代表大的竖向bounding box)object query和anchor有些像,anchor是提前定一些bounding box,把预测和这些提前定好的bounding box对比,object query是可以学习的。以第一个 object query 来说:对于一个图片, object query 会去问图片的左下角有没有小物体,以及中间有没有横向的大物体。

为了说明端到端的 DETR 框架的简洁性,作者在论文末尾给出了 DETR 模型定义、推理的 “伪代码”,总共不到 50 行。之所以这里的伪代码要加引号,是因为其实这已经不算是伪代码了,而是直接可运行的 PyTorch 代码。当然这个版本缺少了一些细节,但也完全能够展现出 DETR 的流程了。该版本直接用来训练,最终也能达到 40 的 AP。读者可以对应伪代码再过一遍刚才介绍的 DETR 完成流程,体会一下一个端到端的目标检测框架有多幺简洁。

代码实现:

后续关于detr的改进工作:

1、Deformable DETR: Deformable Transformers for End-to-End Object Detection

2、Omni-DETR: Omni-Supervised Object Detection with Transformers

3、UP-DETR: Unsupervised Pre-training for Object Detection with Transformers

4、PnP-DETR: Towards Efficient Visual Analysis with Transformers

5、SMAC-DETR

6、DAB-DETR: Dynamic Anchor Boxes are Better Queries for DETR

7、Accelerating DETR Convergence via Semantic-Aligned Matching

8、DN-DETR: Accelerate DETR Training by Introducing Query DeNoising

9、Open-Vocabulary DETR with Conditional Matching

10、OW-DETR: Open-world Detection Transformer

Cityscapes数据集

官网:https://www.cityscapes-dataset.com/

这个大型数据集包含了来自50个不同城市的街景场景中记录的多样化的双目视频序列,除了20000个弱注释帧以外,还有5000帧的高质量像素级注释。

主要参考如下:

下载前3个文件即可。其中3文件代表训练使用的原图,1文件代表精细标注label,2文件代表非精细标注label。有的同学要问了,那我下载1、3不就行了吗?我要这2有何用?其实Cityscapes数据集提供了34种分类,但有时我们不需要那么多,比如仅需要19分类(默认的)或任意多个分类,进行图像语意分割的神经网络训练,我们就需要用到他Cityscapes提供的自带工具进行label的转换,若缺少2文件,转换代码会报错无法进行。

该数据集由gtFine和leftImg8bit这两个目录组成,结构如下所示,其中aachen等表示拍摄场景的城市名:

├── gtFine
│   ├── train
│   │   ├── aachen
│   │   ├── bochum
│   │   └── bremen
│   └── val
│       └── frankfurt
└── leftImg8bit
    ├── train
    │   ├── aachen
    │   ├── bochum
    │   └── bremen
    └── val
        └── frankfurt

   原图存放在leftImg8bit文件夹中,精细标注的数据存放在gtFine (gt : ground truth) 文件夹中 。其中训练集共2975张(train),验证集500张(val),都是有相应的标签的。但测试集(test)只给了原图,没有给标签,官方用于线上评估大家提交的代码(防止有人用test集训练刷指标)。因此,实际使用中可以用validation集做test使用。

   标签文件中每张图像对应4个文件,其中_gtFine_polygons.json存储的是各个类和相应的区域(用多边形顶点的位置表示区域的边界);_gtFine_labelIds.png的值是0-33,不同的值代表不同的类,值和类的对应关系在代码中cityscapesscripts/helpers/labels.py中定义;_gtFine_instaceIds.png是示例分割的; _gtFine_color.png是给大家可视化的,不同颜色与类别的对应关系也在labels.py文件中说明。

可以使用Cityscapes的coarse标签做初步训练然后再用精细标签训练

这个数据集包含语义分割,实例分割,深度估计等标签数据,对应的训练标签如下所示:

cd $CITYSCAPES_ROOT
# 训练和校准对应的数据集
ls leftImg8bit/train/*/*.png > trainImages.txt
ls leftImg8bit/val/*/*.png > valImages.txt

# 训练和校准标签对应的数据集
ls gtFine/train/*/*labelIds.png > trainLabels.txt
ls gtFine/val/*/*labelIds.png.png > valLabels.txt

# 训练和校准实例标签对应的数据集
ls gtFine/train/*/*instanceIds.png > trainInstances.txt
ls gtFine/val/*/*instanceIds.png.png > valInstances.txt

# 训练和校准深度标签对应的数据集
ls disparity/train/*/*.png > trainDepth.txt
ls disparity/val/*/*.png.png > valDepth.txt

另外,torchvision支持很多现成数据集:

Class Definitions:

cityscapesscripts 脚本工具:

cityscapes scripts公开以下工具:

  • csDownload: 命令行下载cityscapes包
  • csViewer: 查看图像并覆盖批注(overlay the annotations)。
  • csLabelTool: 标注工具.
  • csEvalPixelLevelSemanticLabeling: Evaluate pixel-level semantic labeling results on the validation set. This tool is also used to evaluate the results on the test set.像素级评估
  • csEvalInstanceLevelSemanticLabeling: Evaluate instance-level semantic labeling results on the validation set. This tool is also used to evaluate the results on the test set.实例级评估
  • csEvalPanopticSemanticLabeling: Evaluate panoptic segmentation results on the validation set. This tool is also used to evaluate the results on the test set.全景分割评估
  • csCreateTrainIdLabelImgs: Convert annotations in polygonal format to png images with label IDs, where pixels encode “train IDs” that you can define in labels.py.将多边形格式的注释转换为带标签ID的png图像,其中像素编码“序列ID”,可以在labels.py中定义。
  • csCreateTrainIdInstanceImgs: Convert annotations in polygonal format to png images with instance IDs, where pixels encode instance IDs composed of “train IDs”.将多边形格式的注释转换为具有实例ID的png图像,其中像素对由“序列ID”组成的实例ID进行编码。
  • csCreatePanopticImgs: Convert annotations in standard png format to COCO panoptic segmentation format.将标准png格式的注释转换为COCO全景分割格式。

cityscapes scripts文件夹

文件夹内容如下:

  • helpers: 被其他脚本文件调用的帮助文件
  • viewer: 用于查看图像和标注的脚本
  • preparation: 用于将GroundTruth注释转换为适合您的方法的格式的脚本
  • evaluation: 评价你的方法的脚本
  • annotation: 被用来标注数据集的标注工具
  • download: 下载Cityscapes packages

请注意,所有文件顶部都有一个小型documentation。 非常重要

  • helpers/labels.py: 定义所有语义类ID的中心文件,并提供各种类属性之间的映射。
  • helpers/labels_cityPersons.py: 文件定义所有CityPersons行人类的ID并提供各种类属性之间的映射。
  • viewer/cityscapesViewer.py 查看图像并覆盖注释。
  • preparation/createTrainIdLabelImgs.py 将多边形格式的注释转换为带有标签ID的png图像,其中像素编码可以在“labels.py”中定义的“训练ID”。
  • preparation/createTrainIdInstanceImgs.py 将多边形格式的注释转换为带有实例ID的png图像,其中像素编码由“train ID”组成的实例ID。
  • evaluation/evalPixelLevelSemanticLabeling.py 该脚本来评估验证集上的像素级语义标签结果。该脚本还用于评估测试集的结果。
  • evaluation/evalInstanceLevelSemanticLabeling.py 该脚本来评估验证集上的实例级语义标签结果。该脚本还用于评估测试集的结果。
  • setup.py 运行 setup.py build_ext --inplace 启用cython插件以进行更快速的评估。仅针对Ubuntu进行了测试。

Mask Scoring RCNN

论文: https://arxiv.org/abs/1903.00241 CVPR2019

code: https://github.com/zjhuang22/maskscoring_rcnn

这篇论文从实例分割中mask 的分割质量角度出发,提出过去的经典分割框架存在的一个缺陷:用Bbox bounding box的classification confidence作为mask score,导致mask score和mask quality不配准。因此文章基于Mask R-CNN提出一个新的框架Mask Scoring R-CNN,能自动学习出mask quality,试图解决不配准的问题。

摘要

让一个深度网络意识到自己预测的质量是一个有趣但重要的问题。在实例分割任务中,大多数实例分割框架使用实例分类的置信度作为mask质量分数。然而,将mask质量量化为实例mask与其ground truth之间的IoU,通常与分类分数的相关性并不好。

在本文中,我们提出Mask Scoring R-CNN来学习预测实例mask的质量。提出的网络块将实例特征和相应的预测mask结合起来回归mask IoU。Mask评分策略校准mask质量和mask评分之间的偏差,并通过在COCO AP评估期间优先处理更准确的mask预测来改进实例分割性能。

通过对COCO数据集的广泛评估,Mask Scoring R-CNN与不同的模型带来一致和显着的增益,并优于Mask RCNN。

总而言之,这项工作的主要贡献突出如下:

1. 提出Mask Scoring R-CNN,这是第一个解决实例分割假设得分问题的框架。它探索了改善实例分割模型性能的新方向。通过考虑实例mask的完整性,如果实例mask的得分较高而mask不够好,则可以对实例mask的分数进行惩罚。

2. MaskIoU head非常简单有效。能够在各个backbone上涨点。

正文:

在实例分割(instance segmentation)中,比如Mask R-CNN,mask 分支的分割质量(quality)来源于检测分支的classification confidence。Mask R-CNN其实Faster R-CNN系列的延伸,其在Faster R-CNN的基础上添加一个新的分支用来预测object mask,该分支以检测分支的输出作为输入,mask的质量一定程度上依赖于检测分支。这种简单粗暴的做法取得了SOTA的性能,近年来COCO比赛的冠军或者前几名基本是Mask R-CNN及其变体,但依然有上升的空间。

更仔细的来讲,Mask R-CNN存在的问题是:bounding box的classification confidence不能代表mask的分割质量。classification confidence高可以表示检测框的置信度高(严格来讲不能表示框的定位精准),但也会存在mask分割的质量差的情况。高的分类置信度也应该同时有好的mask 结果。

回到原始的初衷,文章希望得到精准的mask质量,那么如何评价输出的mask质量呢?

是AP,或者说是instance-level的IoU。这个IoU和检测用到的IoU是一个东西,前者是predict mask和gt mask的pixel-level的Intersection-over-Union,而后者则是predict box和gt box的box-level的Intersection-over-Union。所以一个直观的方法就是用IoU来表示分割的质量,那么让网络自己学习输出分割的质量也是简单直观的做法。学习出mask的IoU,那么最后的mask score就等于maskIoU乘以classification score,mask score就同时表示分类置信度和分割的质量。

作者在Mask R-CNN的基础上添加了一个MaskIoU分支用于预测当前输出的mask和gt mask的IoU。MaskIoU的输入由两部分组成,一是ROIAlign得到的RoI feature map,二是mask分支输出的mask。两者concat之后经过3层卷积和2层全连接输出MaskIoU。

training过程:

box分支和mask保持不变,输出的mask先经过阈值为0.5的binarize,再计算binary mask和gt的IoU作为target,采用L2 loss作为损失函数,loss weight设为1,3个分支同时end-to-end训练。

inference过程:

检测分支输出score最高的100个框,再送入mask分支,得到mask结果,RoI feature map再和mask送入MaskIoU分支得到mask iou,与box的classification score相乘就得到最后的mask score。

实验结果,在COCO 2017 test集上,相对于Mask R-CNN,mask AP有1个点多的提升。

同时作者还做了对比实验,验证不同的MaskIoU输入对性能的影响。文章列举了4种输入方式:

  1. target mask和ROI feature concat
  2. target mask和ROI feature 相乘
  3. 所有mask和ROI feature concat
  4. target mask和高分辨率的ROI feature concat

其网络结构示意图如下:

验证不同training target对性能的影响:

  1. 只学习target类别的MaskIoU,忽略其他类别
  2. 学习所有类别的MaskIoU,相应的其他类别的MaskIoU的学习目标就是0
  3. 学习出现在ROI区域的类别的MaskIoU。

可以看出,setting#1的效果最好,setting#2的效果最差。

同时作者还做了实验探索Mask Scoring R-CNN的性能上界。

对每个输出的MaskIoU,用输出的mask 和匹配的gt mask iou来代替,MaskIoU分支就输出了mask分支的真实quality,这时就得到了Mask Scoring R-CNN的性能上界。实验结果表明Mask Scoring R-CNN依然比Mask R-CNN更好,说明MaskIoU起到了alignment的效果,但很显然会比用gt mask iou 代替的效果差,说明一方面box的准确性和mask分支本身也会影响mask任务的性能,另一方面MaskIoU 分支的学习能力可以进一步提升,Mask Scoring R-CNN依然有提升的空间。

速度方面,作者在Titan V GPU上测试一张图片,对于ResNet18-FPN用时0.132s,Resnet101-DCN-FPN用时0.202s,Mask Scoring R-CNN和Mask R-CNN速度一样。

总结:

作者motivation就是想让mask的分数更合理,从而基于mask rcnn添加一个新的分支预测来得到更准确的分数,做法简单粗暴,从结果来看也有涨点。其实mask的分割质量也跟box输出结果有很大关系,这种detection-based分割方法不可避免,除非把detection结果做的非常高,不然mask也要受制于box的结果。这种做法与IoU-Net类似,都是希望直接学习最本质的metric方式来提升性能。

为了同时提升detection和mask的效果,最近的Cascade方法很受欢迎,从人脸检测领域的Cascade CNN, 到Cascade R-CNN: Delving into High Quality Object Detection,再到友商的HTC不仅在COCO中拿了冠军,同时也被CVPR2019接收,Cascade方式展现了强大实力,相信在未来会出现越来越多的Cascade,如Cascade RetinaNet,Cascade TridentNet。。。

HRNet 论文和代码详解

github: https://github.com/HRNet/HRNet-Semantic-Segmentation

Paper: https://arxiv.org/abs/1908.07919

High-Resoultion Net(HRNet)由微软亚洲研究院和中科大提出,发表在CVPR2019

摘要:高分辨率表示对于位置敏感的视觉问题十分重要,比如目标检测、语义分割、姿态估计。为了这些任务位置信息更加精准,很容易想到的做法就是维持高分辨率的feature map,事实上HRNet之前几乎所有的网络都是这么做的,通过下采样得到强语义信息,然后再上采样恢复高分辨率恢复位置信息(如下图所示),然而这种做法,会导致大量的有效信息在不断的上下采样过程中丢失。而HRNet通过并行多个分辨率的分支,加上不断进行不同分支之间的信息交互,同时达到强语义信息和精准位置信息的目的。

模型的主要特点是在整个过程中特征图(Feature Map)始终保持高分辨率,这与之前主流方法思路上有很大的不同。在HRNet之前,2D人体姿态估计算法是采用(Hourglass/CPN/Simple Baseline/MSPN等)将高分辨率特征图下采样至低分辨率,再从低分辨率特征图恢复至高分辨率的思路(单次或重复多次),以此过程实现了多尺度特征提取的一个过程。

HRNet在整个过程中保持特征图的高分辨率,但多尺度特征提取是姿态估计模型一定要实现的过程,那么HRNet是如何实现多尺度特征提取的呢?模型是通过在高分辨率特征图主网络逐渐并行加入低分辨率特征图子网络,不同网络实现多尺度融合与特征提取实现的。

特点与优势:

(1)作者提出的方法是并行连接高分辨率与低分辨率网络,而不是像之前方法那样串行连接。因此,其方法能够保持高分辨率,而不是通过一个低到高的过程恢复分辨率,因此预测的heatmap可能在空间上更精确。

(2)本文提出的模型融合相同深度和相似级别的低分辨率特征图来提高高分辨率的特征图的表示效果,并进行重复的多尺度融合。

缺点:因为特征图分辨率过大,而且数量多,这样肯定会导致巨额的耗时计算,对显存对硬件要求更高了

HRNet结构细节

Backbone设计

我将HRNet整个backbone部分进行了拆解,分成4个stage,每个stage分成蓝色框和橙色框两部分。其中蓝色框部分是每个stage的基本结构,由多个branch组成,HRNet中stage1蓝色框使用的是BottleNeck,stage2&3&4蓝色框使用的是BasicBlock。其中橙色框部分是每个stage的过渡结构,HRNet中stage1橙色框是一个TransitionLayer,stage2&3橙色框是一个FuseLayer和一个TransitionLayer的叠加,stage4橙色框是一个FuseLayer。

解释一下为什么这么设计,FuseLayer是用来进行不同分支的信息交互的,TransitionLayer是用来生成一个下采样两倍分支的输入feature map的,stage1橙色框显然没办法做FuseLayer,因为前一个stage只有一个分支,stage4橙色框后面接neck和head了,显然也不再需要TransitionLayer了。

整个backbone的构建流程可以总结为:make_backbone -> make_stages -> make_branches

有关backbone构建相关的看源码,主要讲一下FuseLayerTransitionLayerNeck的设计

FuseLayer设计

FuseLayer部分以绿色框为例,融合前为pre,融合后为post,静态构建一个二维矩阵,然后将pre和post对应连接的操作一一填入这个二维矩阵中。

以上图为例,图1的pre1和post1的操作为空,pre2和post1的操作为2倍上采,pre3和post1的操作为4倍上采;图2的pre1和post2的操作为3×3卷积下采,pre2和post2的操作为空,pre3和post2的操作为2倍上采;图3的pre1和post3的操作为连续两个3×3卷积下采,pre2和post3的操作为3×3卷积下采,pre3和post的操作为空。

前向计算时用一个二重循环将构建好的二维矩阵一一解开,将对应同一个post的pre转换后进行融合相加。比如post1 = f11(pre1) + f12(pre2) + f13(pre3)

FuseLayer的整体code如下:

def _make_fuse_layers(self):
  fuse_layers = []
  for post_index, out_channel in enumerate(self.out_channels[:len(self.in_channels)]):
      fuse_layer = []
      for pre_index, in_channel in enumerate(self.in_channels):
          if pre_index > post_index:
              fuse_layer.append(nn.Sequential(
                  nn.Conv2d(in_channel, out_channel, 1, 1, 0, bias=False),
                  nn.BatchNorm2d(out_channel, momentum=0.1),
                  nn.Upsample(scale_factor=2**(pre_index-post_index), mode='nearest')))
          elif pre_index < post_index:
              conv3x3s = []
              for cur_index in range(post_index - pre_index):
                  out_channels_conv3x3 = out_channel if cur_index == post_index - pre_index - 1 else in_channel
                  conv3x3 = nn.Sequential(
                      nn.Conv2d(in_channel, out_channels_conv3x3, 3, 2, 1, bias=False),
                      nn.BatchNorm2d(out_channels_conv3x3, momentum=0.1)
                  )
                  if cur_index < post_index - pre_index - 1:
                      conv3x3.add_module('relu_{}'.format(cur_index), nn.ReLU(False))
                  conv3x3s.append(conv3x3)
              fuse_layer.append(nn.Sequential(*conv3x3s))
          else:
              fuse_layer.append(None)
      fuse_layers.append(nn.ModuleList(fuse_layer))
  return nn.ModuleList(fuse_layers)

def forward(self, x):
  x_fuse = []
  for post_index in range(len(self.fuse_layers)):
      y = 0
      for pre_index in range(len(self.fuse_layers)):
          if post_index == pre_index:
              y += x[pre_index]
          else:
              y += self.fuse_layers[post_index][pre_index](x[pre_index])
      x_fuse.append(self.relu(y))

TransitionLayer设计

TransitionLayer以黄色框为例,静态构建一个一维矩阵,然后将pre和post对应连接的操作一一填入这个一维矩阵中。当pre1&post1、pre2&post2、pre3&post3的通道数对应相同时,一维矩阵填入None;通道数不相同时,对应位置填入一个转换卷积。post4比较特殊,这一部分代码和图例不太一致,图例是pre1&pre2&pre3都进行下采然后进行融合相加得到post4,而代码中post4通过pre3下采得到。

TransitionLayer整体code如下

def _make_transition_layers(self):
  num_branches_pre = len(self.in_channels)
  num_branches_post = len(self.out_channels)
  transition_layers = []
  for post_index in range(num_branches_post):
      if post_index < len(self.in_channels):
          if self.in_channels[post_index] != self.out_channels[post_index]:
              transition_layers.append(nn.Sequential(
                  nn.Conv2d(self.in_channels[post_index], self.out_channels[post_index], 3, 1, 1, bias=False),
                  nn.BatchNorm2d(self.out_channels[post_index], momentum=0.1),
                  nn.ReLU(inplace=True)
              ))
          else:
              transition_layers.append(None)
      else:
          conv3x3s = []
          for pre_index in range(post_index + 1 - num_branches_pre):
              in_channels_conv3x3 = self.in_channels[-1]
              out_channels_conv3x3 = self.out_channels[post_index] if pre_index == post_index - \
                  num_branches_pre else in_channels_conv3x3
              conv3x3s.append(nn.Sequential(
                  nn.Conv2d(in_channels_conv3x3, out_channels_conv3x3, 3, 2, 1, bias=False),
                  nn.BatchNorm2d(out_channels_conv3x3, momentum=0.1),
                  nn.ReLU(inplace=True)
              ))
          transition_layers.append(nn.Sequential(*conv3x3s))
  return nn.ModuleList(transition_layers)

def forward(self, x):
  x_trans = []
  for branch_index, transition_layer in enumerate(self.transition_layers):
      if branch_index < len(self.transition_layers) - 1:
          if transition_layer:
              x_trans.append(transition_layer(x[branch_index]))
          else:
              x_trans.append(x[branch_index])
      else:
          x_trans.append(transition_layer(x[-1]))

Neck设计

我把HRNet所描述的make_head过程理解成make_neck(因为一般意义上将最后的fc层理解成head更为清晰,这个在很多开源code中都是这样子拆解的)。下面着重讲解一下HRNet的neck设计。

HRNet的backbone输出有四个分支,paper中给出了几种方式对输出分支进行操作。

(a)图是HRNetV1的操作方式,只使用分辨率最高的feature map。

(b)图是HRNetV2的操作方式,将所有分辨率的feature map(小的特征图进行upsample)进行concate,主要用于语义分割和面部关键点检测。

(c)图是HRNetV2p的操作方式,在HRNetV2的基础上,使用了一个特征金字塔,主要用于目标检测。

而在图像分类任务上,HRNet有另一种特殊的neck设计

HRNet的neck可以分成三个部分,IncreLayer(橙色框),DownsampLayer(蓝色框)和FinalLayer(绿色框)。对每个backbone的输出分支进行升维操作,然后按照分辨率从大到小依次进行下采样同时从上到下逐级融合相加,最后用一个1x1conv升维。

def _make_neck(self, in_channels):
  head_block = Bottleneck
  self.incre_channels = [32, 64, 128, 256]
  self.neck_out_channels = 2048

  incre_modules = []
  downsamp_modules = []
  num_branches = len(self.in_channels)
  for index in range(num_branches):
      incre_module = self._make_layer(head_block, in_channels[index], incre_channels[index], 1, stride=1)
      incre_modules.append(incre_module)
      if index < num_branches - 1:
          downsamp_in_channels = self.incre_channels[index] * incre_module.expansion
          downsamp_out_channels = self.incre_channels[index+1] * incre_module.expansion
          downsamp_module = nn.Sequential(
              nn.Conv2d(in_channels=downsamp_in_channels, out_channels=downsamp_out_channels,
                        kernel_size=3, stride=2, padding=1),
              nn.BatchNorm2d(downsamp_out_channels, momentum=0.1),
              nn.ReLU(inplace=True)
          )
          downsamp_modules.append(downsamp_module)
  incre_modules = nn.ModuleList(incre_modules)
  downsamp_modules = nn.ModuleList(downsamp_modules)
  final_layer = nn.Sequential(
      nn.Conv2d(in_channels=self.out_channels[-1] * 4, out_channels=2048,
                kernel_size=1, stride=1, padding=0),
      nn.BatchNorm2d(2048, momentum=0.1),
      nn.ReLU(inplace=True)
  )
  return incre_modules, downsamp_modules, fine_layer

def forward(self, x):
  y = self.incre_modules[0](x[0])
  for index in range(len(self.downsamp_modules)):
      y = self.incre_modules[index+1](x[index+1]) + self.downsamp_modules[index](y)
  y = self.final_layer(y)
  y = F.avg_pool2d(y, kernel_size=y.size()[2:]).view(y.size(0), -1)

还有几个小细节

  1. BN层的momentom都设置为0.1
  2. stem使用的是两层stried为2的conv3x3
  3. FuseLayer的ReLU的inplace都设置为False

Swin Transformer论文解读与思考

论文: https://arxiv.org/abs/2103.14030

github:https://github.com/microsoft/Swin-Transformer

代码详解 https://zhuanlan.zhihu.com/p/384514268

Vision Transformer , Vision MLP 超详细解读 (原理分析+代码解读) (目录)

论文详解:https://space.bilibili.com/1567748478/channel/collectiondetail?sid=32744

Swin Transformer视频讲解:

https://github.com/WZMIAOMIAO/deep-learning-for-image-processing

摘要 :目前transformer应用于CV领域的挑战主要有两个,一个是图片多尺度语义信息的问题,同一个物体在不同图片中的大小尺度变化很大,另外就是难以处理高分辨的图片,如果以pix像素作为序列元素,那么计算成本太大,因此一部分方法是将CNN提取图片特征在送进transformer中 ,或者通过patch,将图片变成一个个的patch。作者提出Swin Transformer 目标是希望作为一种计算机视觉的通用主干网络(因为VIT的提出已经证明了Transformer在CV的可行性),这是一种层级的架构。通过窗口注意力以及转移窗口注意力,不仅降低了计算量,同时层级架构对于不同尺度的信息处理都十分灵活,该架构在图像分类、目标检测、语义分割等任务中表现出色。(对于图像分类、目标检测、语义分割等下游任务,尤其是密集预测任务,多尺度特征是十分必要的)

引言

首先来看作者给出的Swin Transformer 和 VIT结构对比:

VIT的patch固定16*16(可以认为是16倍下采样),多尺度特征处理不好,因为整个过程都是在同一尺度下操作的,出来的特征是单尺度的,优点是全局的特征处理比较强,因为是在全局的尺度进行操作的,但因此他的复杂度跟图像尺寸成平方倍的增长,很难处理目前图像分割检测。再来看 Swin Transformer ,通过图可以看出作者借鉴了CNN的很多设计思路,为了减少序列长度,减低计算量,仅在上面的红框中进行自注意力计算,计算复杂度会跟整张图片的大小成线性关系。另外作者使用基于窗口的注意力的也可以很好的把握物体的全局信息(因为在CV中,一个物体的绝大部分都存在单个windows窗口中,很少会横跨多个窗口),另外CNN网络的如何抓住物体的多尺度特征?是因为pool池化层的存在,每次池化能够增大卷积核看到的感受野。因此作者提出了patch merging,将相邻的四个patch合并成一个大patch(可以认为是加权池化),这样合并出来的一个 大patch就可以看到四个小patch内容感受野增大。有了多尺度特征(4*,8*,16*多尺度特征图)以后,可以接一个FPN头,由于做检测任务,也可以放在unet做分割任务,这就是作者所说的, Swin Transformer 是可以做一个通用骨干网络。

    Transformer的初衷就是更好的理解上下文,如果窗口都是不重叠的,那自注意力真的就变成孤立自注意力,就没有全局建模的能力   
    Swin Transformer 的一个关键设计因素:移动窗口操作。在第l层,通过划分不同的小窗口(实际中是一个窗口有7*7个patch(最小单位),这里示意图以4*4的patch作为一个窗口),自注意力只在窗口中计算 ,就可以有效降低序列长度,从而减少计算复杂度。shift操作可以认为是将l层的窗口整体向右下加移动两个patch所形成的新的窗口,新的特征图进行分割windws以后就有l+1层所示的这些窗口(如下图共九个)了。如果没有shift,那么所有窗口不重叠,在窗口进行自注意力时候,窗口之间无法交互,就无法达到transformer的初衷了(更好的理解上下文),shift后不同窗口的patch就可以进行交互了。再加上一个patch merging操作,不断扩大感受野,到最后几层的时候,每个patch的感受野已经很大了,实际上就可以看到大部分图片了,shift操作以后,就可以看成是全局注意力操作,这样即省内存效果也好。

引言的最后,作者坚信,一个CV和NLP大一统的框架是可以促进两个领域共同发展的,但实际上 Swin Transformer 更多的是利用了CNN的先验知识,从而在计算机视觉领域大杀四方。但是在模型大一统上,也就是 unified architecture 上来说,其实 ViT 还是做的更好的,因为它真的可以什么都不改,什么先验信息都不加,就能让Transformer在两个领域都能用的很好,这样模型不仅可以共享参数,而且甚至可以把所有模态的输入直接就拼接起来,当成一个很长的输入,直接扔给Transformer去做,而不用考虑每个模态的特性

先看结论:

这篇论文提出了 Swin Transformer,它是一个层级式的Transformer,而且它的计算复杂度是跟输入图像的大小呈线性增长的。Swin Transformerr 在 COCO 和 ADE20K上的效果都非常的好,远远超越了之前最好的方法,所以作者说基于此,希望 Swin Transformer 能够激发出更多更好的工作,尤其是在多模态方面。

因为在Swin Transformer 这篇论文里最关键的一个贡献就是基于 Shifted Window 的自注意力,它对很多视觉的任务,尤其是对下游密集预测型的任务是非常有帮助的,但是如果 Shifted Window 操作不能用到 NLP 领域里,其实在模型大一统上论据就不是那么强了,所以作者说接下来他们的未来工作就是要把 Shifted Windows用到 NLP 里面,而且如果真的能做到这一点,那 Swin Transformer真的就是一个里程碑式的工作了,而且模型大一统的故事也就讲的圆满了

方法

主要分为两大块

  • 大概把整体的流程讲了一下,主要就是过了一下前向过程,以及提出的 patch merging 操作是怎么做的
  • 基于 Shifted Window 的自注意力,Swin Transformer怎么把它变成一个transformer block 进行计算

前向过程

  • 假设说有一张224*224*3(ImageNet 标准尺寸)的输入图片
  • 第一步就是像 ViT 那样把图片打成 patch,在 Swin Transformer 这篇论文里,它的 patch size 是4*4,而不是像 ViT 一样16*16,所以说它经过 patch partition 打成 patch 之后,得到图片的尺寸是56*56*48,56就是224/4,因为 patch size 是4,向量的维度48,因为4*4*3,3 是图片的 RGB 通道
  • 打完了 patch ,接下来就要做 Linear Embedding,也就是说要把向量的维度变成一个预先设置好的值,就是 Transformer 能够接受的值,在 Swin Transformer 的论文里把这个超参数设为 c,对于 Swin tiny 网络来说,也就是上图中画的网络总览图,它的 c 是96,所以经历完 Linear Embedding 之后,输入的尺寸就变成了56*56*96,前面的56*56就会拉直变成3136,变成了序列长度,后面的96就变成了每一个token向量的维度,其实 Patch Partition 和 Linear Embedding 就相当于是 ViT 里的Patch Projection 操作,而在代码里也是用一次卷积操作就完成了,
  • 第一部分跟 ViT 其实还是没有区别的,但紧接着区别就来了
  • 首先序列长度是3136,对于 ViT 来说,用 patch size 16*16,它的序列长度就只有196,是相对短很多的,这里的3136就太长了,是目前来说Transformer不能接受的序列长度,所以 Swin Transformer 就引入了基于窗口的自注意力计算,每个窗口按照默认来说,都只有七七四十九个 patch,所以说序列长度就只有49就相当小了,这样就解决了计算复杂度的问题
  • 所以也就是说, stage1中的swin transformer block 是基于窗口计算自注意力的,现在暂时先把 transformer block当成是一个黑盒,只关注输入和输出的维度,对于 Transformer 来说,如果不对它做更多约束的话,Transformer输入的序列长度是多少,输出的序列长度也是多少,它的输入输出的尺寸是不变的,所以说在 stage1 中经过两层Swin Transformer block 之后,输出还是56*56*96
  • 到这其实 Swin Transformer的第一个阶段就走完了,也就是先过一个 Patch Projection 层,然后再过一些 Swin Transformer block,接下来如果想要有多尺寸的特征信息,就要构建一个层级式的 transformer,也就是说需要一个像卷积神经网络里一样,有一个类似于池化的操作

Patch Merging

Patch Merging 其实在之前一些工作里也有用到,它很像 Pixel Shuffle 的上采样的一个反过程,Pixel Shuffle 是 lower level 任务中很常用的一个上采样方式

  • 假如有一个张量, Patch Merging 顾名思义就是把临近的小 patch 合并成一个大 patch,这样就可以起到下采样一个特征图的效果了
  • 这里因为是想下采样两倍,所以说在选点的时候是每隔一个点选一个,也就意味着说对于这个张量来说,每次选的点是1、1、1、1
  • 其实在这里的1、2、3、4并不是矩阵里有的值,而是给它的一个序号,同样序号位置上的 patch 就会被 merge 到一起,这个序号只是为了帮助理解
  • 经过隔一个点采一个样之后,原来的这个张量就变成了四个张量,也就是说所有的1都在一起了,2在一起,3在一起,4在一起,如果原张量的维度是 h * w * c ,当然这里 c 没有画出来,经过这次采样之后就得到了4个张量,每个张量的大小是 h/2、w/2,它的尺寸都缩小了一倍
  • 现在把这四个张量在 c 的维度上拼接起来,也就变成了下图中所画出来的形式,张量的大小就变成了 h/2 * w/2 * 4c,相当于用空间上的维度换了更多的通道数
  • 通过这个操作,就把原来一个大的张量变小了,就像卷积神经网络里的池化操作一样,为了跟卷积神经网络那边保持一致(不论是 VGGNet 还是 ResNet,一般在池化操作降维之后,通道数都会翻倍,从128变成256,从256再变成512),所以这里也只想让他翻倍,而不是变成4倍,所以紧接着又再做了一次操作,就是在 c 的维度上用一个1乘1的卷积,把通道数降下来变成2c,通过这个操作就能把原来一个大小为 h*w*c 的张量变成 h/2 * w/2 *2c 的一个张量,也就是说空间大小减半,但是通道数乘2,这样就跟卷积神经网络完全对等起来了

这里其实会发现,特征图的维度真的跟卷积神经网络好像,因为如果回想残差网络的多尺寸的特征,就是经过每个残差阶段之后的特征图大小也是56*56、28*28、14*14,最后是7*7

而且为了和卷积神经网络保持一致,Swin Transformer这篇论文并没有像 ViT 一样使用 CLS token,ViT 是给刚开始的输入序列又加了一个 CLS token,所以这个长度就从196变成了197,最后拿 CLS token 的特征直接去做分类,但 Swin Transformer 没有用这个 token,它是像卷积神经网络一样,在得到最后的特征图之后用global average polling,就是全局池化的操作,直接把7*7就取平均拉直变成1了

作者这个图里并没有画,因为 Swin Transformer的本意并不是只做分类,它还会去做检测和分割,所以说它只画了骨干网络的部分,没有去画最后的分类头或者检测头,但是如果是做分类的话,最后就变成了1*768,然后又变成了1*1,000

所以看完整个前向过程之后,就会发现 Swin Transformer 有四个 stage,还有类似于池化的 patch merging 操作,自注意力还是在小窗口之内做的以及最后还用的是 global average polling,所以说 Swin Transformer 这篇论文真的是把卷积神经网络和 Transformer 这两系列的工作完美的结合到了一起,也可以说它是披着Transformer皮的卷积神经网络

主要贡献

这篇论文的主要贡献就是基于窗口或者移动窗口的自注意力,这里作者又写了一段研究动机,就是为什么要引入窗口的自注意力,其实跟之前引言里说的都是一个事情,就是说全局自注意力的计算会导致平方倍的复杂度,同样当去做视觉里的下游任务,尤其是密集预测型的任务,或者说遇到非常大尺寸的图片时候,这种全局算自注意力的计算复杂度就非常贵了,所以就用窗口的方式去做自注意力

重点:窗口注意力

原图片会被平均的分成一些没有重叠的窗口,拿第一层之前的输入来举例,它的尺寸就是56*56*96,也就说有一个维度是56*56张量,然后把它切成一些不重叠的方格(论文中使用7*7的patch作为一个window窗口)

  • 现在所有自注意力的计算都是在这些小窗口里完成的,就是说序列长度永远都是7*7=49
  • 原来大的整体特征图到底里面会有多少个窗口呢?其实也就是每条边56/7就8个窗口,也就是说一共会有8*8等于64个窗口,就是说会在这64个窗口里分别去算它们的自注意力

基于窗口的自注意力模式的计算复杂度计算:

  • 如果现在有一个输入,自注意力首先把它变成 q k v 三个向量,这个过程其实就是原来的向量分别乘了三个系数矩阵
  • 一旦得到 query 和 k 之后,它们就会相乘,最后得到 attention,也就是自注意力的矩阵
  • 有了自注意力之后,就会和 value 做一次乘法,也就相当于是做了一次加权
  • 最后因为是多头自注意力,所以最后还会有一个 projection layer,这个投射层会把向量的维度投射到我们想要的维度

如果这些向量都加上它们该有的维度,也就是说刚开始输入是 h*w*c

  • 公式(1)对应的是标准的多头自注意力的计算复杂度
  • 每一个图片大概会有 h*w 个 patch,在刚才的例子里,h 和 w 分别都是56,c 是特征的维度
  • 公式(2)对应的是基于窗口的自注意力计算的复杂度,这里的 M 就是刚才的7,也就是说一个窗口的某条边上有多少个patch

基于窗口的自注意力计算复杂度又是如何得到的呢?

  • 因为在每个窗口里算的还是多头自注意力,所以可以直接套用公式(1),只不过高度和宽度变化了,现在高度和宽度不再是 h * w,而是变成窗口有多大了,也就是 M*M,也就是说现在 h 变成了 M,w 也是 M,它的序列长度只有 M * M 这么大
  • 所以当把 M 值带入到公式(1)之后,就得到计算复杂度是4 * M^2 * c^2 + 2 * M^4 * c,这个就是在一个窗口里算多头自注意力所需要的计算复杂度
  • 那我们现在一共有 h/M * w/M 个窗口,现在用这么多个窗口乘以每个窗口所需要的计算复杂度就能得到公式(2)了

对比公式(1)和公式(2),虽然这两个公式前面这两项是一样的,只有后面从 (h*w)^2变成了 M^2 * h * w,看起来好像差别不大,但其实如果仔细带入数字进去计算就会发现,计算复杂的差距是相当巨大的,因为这里的 h*w 如果是56*56的话, M^2 其实只有49,所以是相差了几十甚至上百倍的

这种基于窗口计算自注意力的方式虽然很好地解决了内存和计算量的问题,但是窗口和窗口之间没有通信,这样就达不到全局建模了,也就文章里说的会限制模型的能力,所以最好还是要有一种方式能让窗口和窗口之间互相通信起来,这样效果应该会更好,因为具有上下文的信息,所以作者就提出移动窗口的方式

移动窗口:

移动窗口就是把原来的窗口往右下角移动一半窗口的距离,如果Transformer是上下两层连着做这种操作,先是 window再是 shifted window 的话,就能起到窗口和窗口之间互相通信的目的了

所以说在 Swin Transformer里, transformer block 的安排是有讲究的,每次都是先要做一次基于窗口的多头自注意力,然后再做一次基于移动窗口的多头自注意力,这样就达到了窗口和窗口之间的互相通信。如下图所示

  • 每次输入先进来之后先做一次 Layernorm,然后做窗口的多头自注意力,然后再过 Layernorm 过 MLP,第一个 block 就结束了
  • 这个 block 结束以后,紧接着做一次Shifted window,也就是基于移动窗口的多头自注意力,然后再过 MLP 得到输出
  • 这两个 block 加起来其实才算是 Swin Transformer 一个基本的计算单元,这也就是为什么stage1、2、3、4中的 swin transformer block 为什么是 *2、*2、*6、*2,也就是一共有多少层 Swin Transformer block 的数字总是偶数,因为它始终都需要两层 block连在一起作为一个基本单元,所以一定是2的倍数

到此,Swin Transformer整体的故事和结构就已经讲完了,主要的研究动机就是想要有一个层级式的 Transformer,为了这个层级式,所以介绍了 Patch Merging 的操作,从而能像卷积神经网络一样把 Transformer 分成几个阶段,为了减少计算复杂度,争取能做视觉里密集预测的任务,所以又提出了基于窗口和移动窗口的自注意力方式,也就是连在一起的两个Transformer block,最后把这些部分加在一起,就是 Swin Transformer 的结构

提高移动窗口的计算效率:

  • 一个是怎样提高移动窗口的计算效率,他们采取了一种非常巧妙的 masking(掩码)的方式
  • 另外一个点就是这篇论文里没有用绝对的位置编码,而是用相对的位置编码

masking(掩码)的方式计算移动窗口自注意力:为什么需要使用?

为了提高计算效率,因为如果直接计算右下图的九个窗口的自注意力,不同大小的窗口无法合并成一个batch进行计算。

  • 上图是一个基础版本的移动窗口,就是把左边的窗口模式变成了右边的窗口方式
  • 虽然这种方式已经能够达到窗口和窗口之间的互相通信了,但是会发现一个问题,就是原来计算的时候,特征图上只有四个窗口,但是做完移动窗口操作之后得到了9个窗口,窗口的数量增加了,而且每个窗口里的元素大小不一,比如说中间的窗口还是4*4,有16个 patch,但是别的窗口有的有4个 patch,有的有8个 patch,都不一样了,如果想做快速运算,就是把这些窗口全都压成一个 patch直接去算自注意力,就做不到了,因为窗口的大小不一样
  • 有一个简单粗暴的解决方式就是把这些小窗口周围再 pad 上0 ,把它照样pad成和中间窗口一样大的窗口,这样就有9个完全一样大的窗口,这样就还能把它们压成一个batch,就会快很多
  • 但是这样的话,无形之中计算复杂度就提升了,因为原来如果算基于窗口的自注意力只用算4个窗口,但是现在需要去算9个窗口,复杂度一下提升了两倍多,所以还是相当可观的
  • 那怎么能让第二次移位完的窗口数量还是保持4个,而且每个窗口里的patch数量也还保持一致呢?作者提出了一个非常巧妙的掩码方式,如下图所示

上图是说,当通过普通的移动窗口方式,得到9个窗口之后,现在不在这9个窗口上算自注意力,先再做一次循环移位( cyclic shift )

  • 经过这次循环移位之后,原来的窗口(虚线)就变成了现在窗口(实线)的样子,那如果在大的特征图上再把它分成四宫格的话,我在就又得到了四个窗口,意思就是说移位之前的窗口数也是4个,移完位之后再做一次循环移位得到窗口数还是4个,这样窗口的数量就固定了,也就说计算复杂度就固定了
  • 但是新的问题就来了,虽然对于移位后左上角的窗口(也就是移位前最中间的窗口)来说,里面的元素都是互相紧挨着的,他们之间可以互相两两做自注意力,但是对于剩下几个窗口来说,它们里面的元素是从别的很远的地方搬过来的,所以他们之间,按道理来说是不应该去做自注意力,也就是说他们之间不应该有什么太大的联系
  • 解决这个问题就需要一个很常规的操作,也就是掩码操作,这在Transformer过去的工作里是层出不穷,很多工作里都有各式各样的掩码操作
  • 在 Swin Transformer这篇论文里,作者也巧妙的设计了几种掩码的方式,从而能让一个窗口之中不同的区域之间也能用一次前向过程,就能把自注意力算出来,但是互相之间都不干扰,也就是后面的 masked Multi-head Self Attention(MSA)
  • 算完了多头自注意力之后,还有最后一步就是需要把循环位移再还原回去,也就是说需要把A、B、C再还原到原来的位置上去,原因是还需要保持原来图片的相对位置大概是不变的,整体图片的语义信息也是不变的,如果不把循环位移还原的话,那相当于在做Transformer的操作之中,一直在把图片往右下角移,不停的往右下角移,这样图片的语义信息很有可能就被破坏掉了
  • 所以说整体而言,上图介绍了一种高效的、批次的计算方式比如说本来移动窗口之后得到了9个窗口,而且窗口之间的patch数量每个都不一样,为了达到高效性,为了能够进行批次处理,先进行一次循环位移,把9个窗口变成4个窗口,然后用巧妙的掩码方式让每个窗口之间能够合理地计算自注意力,最后再把算好的自注意力还原,就完成了基于移动窗口的自注意力计算

掩码操作如何实现 :

作者通过这种巧妙的循环位移的方式和巧妙设计的掩码模板,从而实现了只需要一次前向过程,就能把所有需要的自注意力值都算出来,而且只需要计算4个窗口,也就是说窗口的数量没有增加,计算复杂度也没有增加,非常高效的完成了这个任务

作者给出了不同窗口的不同掩码矩阵:

上图示例的Cyclic Shifting方法,可以保持面向计算的window数量保持不变(还是2X2),在window内部通过attention mask来计算子window中的自注意力。

Swin Transformer的几个变体

  • Swin Tiny
  • Swin Small
  • Swin Base
  • Swin Large

Swin Tiny的计算复杂度跟 ResNet-50 差不多,Swin Small 的复杂度跟 ResNet-101 是差不多的,这样主要是想去做一个比较公平的对比

这些变体之间有哪些不一样呢?,其实主要不一样的就是两个超参数

  • 一个是向量维度的大小 c
  • 另一个是每个 stage 里到底有多少个 transform block

这里其实就跟残差网络就非常像了,残差网络也是分成了四个 stage,每个 stage 有不同数量的残差块

实验

分类

首先是分类上的实验,这里一共说了两种预训练的方式

  • 第一种就是在正规的ImageNet-1K(128万张图片、1000个类)上做预训练
  • 第二种方式是在更大的ImageNet-22K(1,400万张图片、2万多个类别)上做预训练

当然不论是用ImageNet-1K去做预训练,还是用ImageNet-22K去做预训练,最后测试的结果都是在ImageNet-1K的测试集上去做的,结果如下表所示

  • 上半部分是ImageNet-1K预训练的模型结果
  • 下半部分是先用ImageNet-22K去预训练,然后又在ImageNet-1K上做微调,最后得到的结果
  • 在表格的上半部分,作者先是跟之前最好的卷积神经网络做了一下对比,RegNet 是之前 facebook 用 NASA 搜出来的模型,EfficientNet 是 google 用NASA 搜出来的模型,这两个都算之前表现非常好的模型了,他们的性能最高会到 84.3
  • 接下来作者就写了一下之前的 Vision Transformer 会达到什么效果,对于 ViT 来说,因为它没有用很好的数据增强,而且缺少偏置归纳,所以说它的结果是比较差的,只有70多
  • 换上 DeiT 之后,因为用了更好的数据增强和模型蒸馏,所以说 DeiT Base 模型也能取得相当不错的结果,能到83.1
  • 当然 Swin Transformer 能更高一些,Swin Base 最高能到84.5,稍微比之前最好的卷积神经网络高那么一点点,就比84.3高了0.2
  • 虽然之前表现最好的 EfficientNet 的模型是在 600*600 的图片上做的,而 Swin Base 是在 384*384 的图片上做的,所以说 EfficientNet 有一些优势,但是从模型的参数和计算的 FLOPs 上来说 EfficientNet 只有66M,而且只用了 37G 的 FLOPs,但是 Swin Transformer 用了 88M 的模型参数,而且用了 47G 的 FLOPs,所以总体而言是伯仲之间
  • 表格的下半部分是用 ImageNet-22k 去做预训练,然后再在ImageNet-1k上微调最后得到的结果
  • 这里可以看到,一旦使用了更大规模的数据集,原始标准的 ViT 的性能也就已经上来了,对于 ViT large 来说它已经能得到 85.2 的准确度了,已经相当高了
  • 但是 Swin Large 更高,Swin Large 最后能到87.3,这个是在不使用JFT-300M,就是特别大规模数据集上得到的结果,所以还是相当高的

目标检测

  • 表2(a)中测试了在不同的算法框架下,Swin Transformer 到底比卷积神经网络要好多少,主要是想证明 Swin Transformer 是可以当做一个通用的骨干网络来使用的,所以用了 Mask R-CNN、ATSS、RepPointsV2 和SparseR-CNN,这些都是表现非常好的一些算法,在这些算法里,过去的骨干网络选用的都是 ResNet-50,现在替换成了 Swin Tiny
  • Swin Tiny 的参数量和 FLOPs 跟 ResNet-50 是比较一致的,从后面的对比里也可以看出来,所以他们之间的比较是相对比较公平的
  • 可以看到,Swin Tiny 对 ResNet-50 是全方位的碾压,在四个算法上都超过了它,而且超过的幅度也是比较大的
  • 接下来作者又换了一个方式做测试,现在是选定一个算法,选定了Cascade Mask R-CNN 这个算法,然后换更多的不同的骨干网络,比如 DeiT-S、ResNet-50 和 ResNet-101,也分了几组,结果如上图中表2(b)所示
  • 可以看出,在相似的模型参数和相似的 Flops 之下,Swin Transformer 都是比之前的骨干网络要表现好的
  • 接下来作者又做了第三种测试的方式,如上图中的表2(c)所示,就是系统层面的比较,这个层面的比较就比较狂野了,就是现在追求的不是公平比较,什么方法都可以上,可以使用更多的数据,可以使用更多的数据增强,甚至可以在测试的使用 test time augmentation(TTA)的方式
  • 可以看到,之前最好的方法 Copy-paste 在 COCO Validation Set上的结果是55.9,在 Test Set 上的结果是56,而这里如果跟最大的 Swin Transformer–Swin Large 比,它的结果分别能达到58和58.7,这都比之前高了两到三个点

语义分割

  • 上图表3里可以看到之前的方法,一直到 DeepLab V3、ResNet 其实都用的是卷积神经网络,之前的这些方法其实都在44、45左右徘徊
  • 但是紧接着 Vision Transformer 就来了,那首先就是 SETR 这篇论文,他们用了 ViT Large,所以就取得了50.3的这个结果
  • Swin Transformer Large也取得了53.5的结果,就刷的更高了
  • 其实作者这里也有标注,就是有两个“+”号的,意思是说这些模型是在ImageNet-22K 数据集上做预训练,所以结果才这么好

消融实验

实验结果如下图所示

  • 上图中表4主要就是想说一下移动窗口以及相对位置编码到底对 Swin Transformer 有多有用
  • 可以看到,如果光分类任务的话,其实不论是移动窗口,还是相对位置编码,它的提升相对于基线来说,也没有特别明显,当然在ImageNet的这个数据集上提升一个点也算是很显着了
  • 但是他们更大的帮助,主要是出现在下游任务里,就是 COCO 和 ADE20K 这两个数据集上,也就是目标检测和语义分割这两个任务上
  • 可以看到,用了移动窗口和相对位置编码以后,都会比之前大概高了3个点左右,提升是非常显着的,这也是合理的,因为如果现在去做这种密集型预测任务的话,就需要特征对位置信息更敏感,而且更需要周围的上下文关系,所以说通过移动窗口提供的窗口和窗口之间的互相通信,以及在每个 Transformer block都做更准确的相对位置编码,肯定是会对这类型的下游任务大有帮助的

总结

虽然前面已经说了很多 Swin Transformer 的影响力啊已经这么巨大了,但其实他的影响力远远不止于此,论文里这种对卷积神经网络,对 Transformer,还有对 MLP 这几种架构深入的理解和分析是可以给更多的研究者带来思考的,从而不仅可以在视觉领域里激发出更好的工作,而且在多模态领域里,相信它也能激发出更多更好的工作

Mask-RCNN论文

论文:http://cn.arxiv.org/pdf/1703.06870v3

代码:https://github.com/facebookresearch/maskrcnn-benchmark

B站网络详解 FPN

Introduction

我们提出了一个简单、灵活、通用的实例分割框架,称为Mask R-CNN。我们的方法能够有效检测图像中的目标,同时为每个实例生成高质量的分割掩码。Mask R-CNN通过添加一个预测对象掩码的分支,与现有的边框识别分支并行,扩展了之前的Faster R-CNN。Mask R-CNN的训练很简单,只为Faster R-CNN增加了一小部分开销,运行速度为5帧/秒。此外,Mask R-CNN很容易泛化到其他任务,如人体姿态估计。我们展示了Mask R-CNN在COCO挑战赛的实例分割、目标检测和人物关键点检测任务上的最优结果。在不使用花哨技巧的情况下,Mask R-CNN在各项任务上都优于现有的单一模型,包括COCO 2016挑战赛的冠军。我们希望Mask R-CNN能够成为一个坚实的基线,并有助于简化未来实例识别的研究。

Fast/Faster R-CNN和Fully Convolutional Network(FCN)框架极大地推动了计算机视觉领域中目标检测和语义分割等方向的发展。这些方法的概念很直观,具有良好的灵活性和鲁棒性,并且能够快速训练和推理。我们这项工作的目标是为实例分割任务开发一个相对可行的框架。

实例分割具有一定的挑战性,因为它需要正确检测图像中的所有对象,同时还要精确分割每个实例。因此,它结合了目标检测和语义分割等计算机视觉任务中的元素。目标检测旨在对单个物体进行分类,并使用边框对每个物体进行定位。语义分割旨在将每个像素归类到一组固定的类别,而不区分对象实例。鉴于此,人们可能会认为需要一套复杂的方法才能获得良好的结果。然而,我们证明了一个令人惊讶的事实:简单、灵活、快速的系统也可以超越现有的最先进的实例分割模型。

我们的方法称为Mask R-CNN,通过在每个RoI(感兴趣区域,Region of Interest)上添加一个预测分割掩码的分支来扩展Faster R-CNN,并与现有的用于分类和边框回归的分支并行。掩码分支是应用于每个RoI的一个小FCN,以像素到像素的方式预测分割掩码,并且只会增加较小的计算开销。Mask R-CNN是基于Faster R-CNN框架而来的,易于实现和训练,有助于广泛、灵活的架构设计。

原则上,Mask R-CNN是Faster R-CNN的直观扩展,但正确构建掩码分支对于获得好的结果至关重要。最重要的是,Faster R-CNN的设计没有考虑网络输入和输出之间的像素到像素的对齐。这一点在RoIPool(处理实例的核心操作)如何执行粗空间量化来提取特征上表现得最为明显。为了修正错位,我们提出了一个简单的、没有量化的层,称为RoIAlign,它忠实地保留了精确的空间位置。尽管这看起来是一个很小的变化,但是RoIAlign有很大的影响:它将掩码精度提高了10%-50%,在更严格的localization指标下显示出更大的收益。其次,我们发现有必要将掩码和类别预测解耦:我们为每个类别独立预测一个二进制掩码,类别之间没有竞争,并依靠网络的RoI分类分支来预测类别。相比之下,FCN通常执行逐像素的多分类操作,将分割和分类耦合在一起,我们的实验结果表明这种方法的实例分割效果不佳。

在不使用花哨技巧的情况下,Mask R-CNN在COCO实例分割任务上就超越了之前的所有SOTA单模型,包括COCO 2016比赛的冠军。作为副产品,我们的方法在COCO目标检测任务上也表现出色。在消融实验中,我们评估了多个基本实例,这使我们能够证明Mask R-CNN的鲁棒性,并分析其核心因素的影响。

我们的模型可以在GPU上以每帧约200ms的速度运行,在一台8-GPU的机器上进行COCO训练需要1-2天。我们相信,快速的训练和测试,以及框架的灵活性和准确性,将有利于未来实例分割的研究。

最后,我们通过COCO关键点数据集上的人体姿态估计任务展示了Mask R-CNN框架的通用性。通过将每个关键点视为一个独热二进制掩码,只需对Mask R-CNN稍加修改,即可用于检测特定实例的姿态。Mask R-CNN超越了COCO 2016关键点检测比赛的冠军,并且能够以5帧/秒的速度运行。因此,Mask R-CNN可以被更广泛地视为一个实例识别的灵活框架,并且很容易泛化到其他更复杂的任务上。

模型方法

Mask R-CNN方法很简单:Faster R-CNN对每个候选对象有两个输出,一个是类别标签,另一个是边框偏移量。在此基础上,我们添加了第三个分支,用于输出分割掩码。因此,Mask R-CNN是一个自然且直观的想法。但是掩码输出不同于类别和边框输出,需要提取更精细的对象空间布局。接下来,我们介绍了Mask R-CNN的关键元素,包括像素到像素对齐,这是Fast/Faster R-CNN所缺失的部分。

用于实例分割的Mask R-CNN框架

RoIAlign:虚线网格表示特征映射图,实线边框表示RoI(Region of Interest),点表示每个边框中的4个采样点。RoIAlign通过双线性插值从特征映射图上的相邻网格点计算每个采样点的值。

  • Network Architecture: 为了表述清晰,有两种分类方法
  1. 使用了不同的backbone:resnet-50,resnet-101,resnext-50,resnext-101;
  2. 使用了不同的head Architecture:Faster RCNN使用resnet50时,从Block 4导出特征供RPN使用,这种叫做ResNet-50-C4
  3. 作者使用除了使用上述这些结构外,还使用了一种更加高效的backbone:FPN(特征金字塔网络)
Head架构:我们扩展了两个现有的Faster R-CNN Head。
  • Mask R-CNN基本结构:与Faster RCNN采用了相同的two-state结构:首先是通过一阶段网络找出RPN,然后对RPN找到的每个RoI进行分类、定位、并找到binary mask。这与当时其他先找到mask然后在进行分类的网络是不同的。
  • Mask R-CNN的损失函数L = L{_{cls}} + L{_{box}} + L{_{mask}} (当然了,你可以在这里调权以实现更好的效果)
  • Mask的表现形式(Mask Representation):因为没有采用全连接层并且使用了RoIAlign,我们最终是在一个小feature map上做分割。
  • RoIAlign:RoIPool的目的是为了从RPN网络确定的ROI中导出较小的特征图(a small feature map,eg 7×7),ROI的大小各不相同,但是RoIPool后都变成了7×7大小。RPN网络会提出若干RoI的坐标以[x,y,w,h]表示,然后输入RoI Pooling,输出7×7大小的特征图供分类和定位使用。问题就出在RoI Pooling的输出大小是7×7上,如果RON网络输出的RoI大小是8*8的,那么无法保证输入像素和输出像素是一一对应,首先他们包含的信息量不同(有的是1对1,有的是1对2),其次他们的坐标无法和输入对应起来。这对分类没什么影响,但是对分割却影响很大。RoIAlign的输出坐标使用插值算法得到,不再是简单的量化;每个grid中的值也不再使用max,同样使用差值算法。

Implementation Details

使用Fast/Faster相同的超参数,同样适用于Mask RCNN

  • Training:

1、与之前相同,当IoU与Ground Truth的IoU大于0.5时才会被认为有效的RoI,L{_{mask}}只把有效RoI计算进去。

2、采用image-centric training,图像短边resize到800,每个GPU的mini-batch设置为2,每个图像生成N个RoI,在使用ResNet-50-C4 作为backbone时,N=64,在使用FPN作为backbone时,N=512。作者服务器中使用了8块GPU,所以总的minibatch是16, 迭代了160k次,初始lr=0.02,在迭代到120k次时,将lr设定到 lr=0.002,另外学习率的weight_decay=0.0001 momentum = 0.9。如果是resnext,初始lr=0.01,每个GPU的mini-batch是1。

3、RPN的anchors有5种scale,3种ratios。为了方便剥离、如果没有特别指出,则RPN网络是单独训练的且不与Mask R-CNN共享权重。但是在本论文中,RPN和Mask R-CNN使用一个backbone,所以他们的权重是共享的。(Ablation Experiments 为了方便研究整个网络中哪个部分其的作用到底有多大,需要把各部分剥离开)

  • Inference:在测试时,使用ResNet-50-C4作为 backbone情况下proposal number=300,使用FPN作为 backbone时proposal number=1000。然后在这些proposal上运行bbox预测,接着进行非极大值抑制。mask分支只应用在得分最高的100个proposal上。顺序和train是不同的,但这样做可以提高速度和精度。mask 分支对于每个roi可以预测k个类别,但是我们只要背景和前景两种,所以只用k-th mask,k是根据分类分支得到的类型。然后把k-th mask resize成roi大小,同时使用阈值分割(threshold=0.5)二值化

Experiments

Main Results

在下图中可以明显看出,FCIS的分割结果中都会出现一条竖着的线(systematic artifacts),这线主要出现在物体重的部分,作者认为这是FCIS架构的问题,无法解决的。但是在Mask RCNN中没有出现。

Ablation Experiments(剥离实验)

  • Architecture:
    从table 2a中看出,Mask RCNN随着增加网络的深度、采用更先进的网络,都可以提高效果。注意:并不是所有的网络都是这样。
  • Multinomial vs. Independent Masks:(mask分支是否进行类别预测)从table 2b中可以看出,使用sigmoid(二分类)和使用softmax(多类别分类)的AP相差很大,证明了分离类别和mask的预测是很有必要的
  • Class-Specific vs. Class-Agnostic Masks:目前使用的mask rcnn都使用class-specific masks,即每个类别都会预测出一个mxm的mask,然后根据类别选取对应的类别的mask。但是使用Class-Agnostic Masks,即分割网络只输出一个mxm的mask,可以取得相似的成绩29.7vs30.3
  • RoIAlign:tabel 2c证明了RoIAlign的性能
  • Mask Branch:tabel 2e,FCN比MLP性能更好

Bounding Box Detection Results    

  • Mask RCNN精度高于Faster RCNN
  • Faster RCNN使用RoI Align的精度更高
  • Mask RCNN的分割任务得分与定位任务得分相近,说明Mask RCNN已经缩小了这部分差距。

Timing 

  • Inference:195ms一张图片,显卡Nvidia Tesla M40。其实还有速度提升的空间,比如减少proposal的数量等。
  • Training:ResNet-50-FPN on COCO trainval35k takes 32 hours  in our synchronized 8-GPU implementation (0.72s per 16-image mini-batch),and 44 hours with ResNet-101-FPN。

Mask R-CNN for Human Pose Estimation

让Mask R-CNN预测k个masks,每个mask对应一个关键点的类型,比如左肩、右肘,可以理解为one-hot形式。

  • 使用cross entropy loss,可以鼓励网络只检测一个关键点;
  • ResNet-FPN结构
  • 训练了90k次,最开始lr=0.02,在迭代60k次时,lr=0.002,80k次时变为0.0002

PointRend –图像细颗粒分割

title
https://arxiv.org/abs/1912.08193

论文地址: https://arxiv.org/abs/1912.08193

gitlab: https://github.com/zsef123/PointRend-PyTorch

存在的问题

在目前的语义分割网络中存在的问题主要有过采样和现采样。

1.过采样( oversample ):对于图片中低频区域( 属于同一个物体 ),没必要使用 太多的采样点,却使用太多采样点造成过采样;

2.欠采样( undersample ) :对于图片中高频区域( 靠近物体边界 ),如果这些区域的采样过于稀疏,导致分割出的边界过于平滑,不大真实

文章要解决的问题是在实例分割任务中边缘不够精细的问题。以MaskRCNN举例,由于计算量和显存的原因,对于每一个ROIAlign之后的proposal我们一般只会upsample到28*28的分辨率输出mask。这对于绝大多数物体显然是不够的。如果想得到像素级别的精度,我们不得不付出更大的计算和存储代价。那有什么办法可以在低代价下仍然得到精细的分割结果呢?其实很重要的一点是往往这些不准确的部分是在物体的边缘,这些边缘其实只占了整个物体中非常小的一部分。所以基于这样的一个想法,作者提出可以每次在预测出来的mask中只选择Top N最不确定的位置进行细分预测。每个细分点的特征可以通过Bilinear插值得到,每个位置上的classifier通过一个简单的MLP来实现。这其实是等价于用一个1*1的conv来预测,但是对于中心很确定的点并不计算。整体的示意图如下:

PointRend 解决了什么问题?

这篇论文讲了一个很好听的故事,即:把语义分割以及实例分割问题(统称图像分割问题)当做一个渲染问题来解决。故事虽然这么讲,但本质上这篇论文其实是一个新型上采样方法,针对物体边缘的图像分割进行优化,使其在难以分割的物体边缘部分有更好的表现

作为一个小白,那么问题来了:

1、什么是渲染?

2、为什么要把图像分割问题当做渲染问题呢?

要想知道什么是渲染,可以参考:

计算机中所说的「渲染」是什么意思?

简单来说,渲染就是“绘制”,把3D的物体在2D平面上绘制出来。

为什么要把图像分割问题和渲染问题扯在一起呢?因为讲故事好听啊,论文好写嘛….咳咳…不不,是因为二者有类似的问题要解决:即物体边缘难以处理。

具体来说,在图像渲染中,对于多个3D物体,在边缘要判断对于镜头而言谁先谁后,而且还得抗锯齿;而对于图像分割问题,边缘恢复也一直是个麻烦事儿,因为在典型的语义分割网络中(如FCN、DeepLab),在CNN内部一般都会相对输入图像降采样16倍,然后再想办法上采样回去。更细致地说,对于 DeepLabV3+,模型最后直接是一个4倍的双线性插值上采样,这显然对物体边缘的预测十分不利。虽然 DeepLabV3+当时在2017年就达到了秒天秒地的 89%mIoU on VOC2012 test (使用了300M JFT 数据集预训练),至今无人超越(因为JFT 数据集 Google没有公开 \手动滑稽),但显然这个上采样过程仍然存在较大的提升空间。

参考链接:Uno Whoiam:DeepLab 语义分割模型 v1、v2、v3、v3+ 概要(附 Pytorch 实现)

而在实例分割网络中,Mask R-CNN 这货生成的 Mask 才 28×28,要是把这样的 mask 拉伸到 不说多了比如 256×256,还指望它可以很好地预测边缘?我只能说这是在想Peach。

事实上,在图像分割任务上边缘预测不理想这个情况其实在许多前人的工作中都有提及,比如 Not All Pixels Are Equal: Difficulty-Aware Semantic Segmentation via Deep Layer Cascade 中就详细统计了语义分割中,模型最容易误判的 pixel基本上都在物体边缘(如下图右上红色部分标记) 。

而关于上采样其实也有一些前人的工作,如 Decoders Matter for Semantic Segmentation: Data-Dependent Decoding Enables Flexible Feature Aggregation,在实现上有点像超分辨率网络 ESPCN 里使用的 sub-pixel convolutional layer 的操作,不过多加了一个二阶范数约束:

总的来说,图像分割边缘预测是一个未被很好解决的问题,而何恺明团队的 PointRend 是对此问题的一个新的思路和解法,接下来将介绍 PointRend 是如何 work 的。

文主要贡献

1.提出可嵌入主流网络的PointRend模块,提高了图像分割精度。

2.把图像分割问题看作渲染问题,本质上是一个新型上采样方法,为图像分割提供独特视角。

3.降低了训练所需的算力。输出224×224分辨率图像,PointRend只需0.9B FLOPs。

二、总体思路

PointRend 方法要点总结来说是一个迭代上采样的过程:

while 输出的分辨率 < 图片分辨率:

  1. 对输出结果进行2倍双线性插值上采样得到 coarse prediction_i。(粗分辨率预测)
  2. 挑选出 N 个“难点”,即结果很有可能和周围点不一样的点(例如物体边缘)。
  3. 对于每个难点,获取其“表征向量”,“表征向量”由两个部分组成,其一是低层特征(fine-grained features),通过使用点的坐标,在低层的特征图上进行双线性插值获得(类似 RoI Align),其二是高层特征(coarse prediction),由步骤 1 获得。
  4. 使用 MLP 对“表征向量”计算得到新的预测,更新 coarse prediction_i 得到 coarse prediction_i+1。这个 MLP 其实可以看做一个只对“难点”的“表征向量”进行运算的由多个 conv1x1 组成的小网络。

整个过程可以这么理解:

小明同学做题,现在有已知条件(coarse prediction_0,fine-grained features),想求解答案(coarse prediction_k),发现直接求(双线性插值or其它方法)不够准确,那就一步一步来吧(从coarse prediction_1,coarse prediction_2….求到coarse prediction_k)。好的,现在求coarse prediction_1,诶,发现有好多东西不知道,不能从 coarse prediction_0 直接得到怎么办?那就找出不知道的(“难点”),在 fine-grained features 里面找出对应的线索(ROIAlign-like 双线性插值),然后在结合 coarse prediction_0 得到整体线索(“特征向量”)求解(使用MLP计算),嗯,终于得到 coarse prediction_1了。再用同样的思路反复求解,直到 coarse prediction_k。

示意图如下:

对于一个coarse prediction(4×4大小),将其上采样两倍(8×8大小,这里可以理解为检测头的输出)后,取了一些难分割的点(大多是边缘部分),取这些点的特征向量输入到MLP网络中,进行point prediction,得到每一个点的新类别,最后结果输出(8×8大小,边缘更加精确的结果)。

另外,其PointRend 训练为了节省时间,没有使用上述的迭代过程,而是使用多种组合的采样方法,不赘述,详见paper。

  1. 从PointRend的应用思路中可以看到,这里包含了两个阶段的特征处理,分别是fine-grained features和coarse prediction部分,如果主干网络是ResNet,那么fine-grained features就是ResNet的stage2输出,也就是4倍下采样时的精细分割结果,而coarse prediction就是检测头的预测结果(还未上采样还原成原图的结果)。
  2. 从coarse prediction中挑选N个“难点”,也就是结果很有可能和周围点不一样的点(比如物体边缘的点)。对于每一个难点,获取他的“特征向量”,对于点特征向量(point features),主要由两部分组成,分别是fine-grained features的对应点和coarse prediction的对应点的特征向量,将这个两个特征向量拼接成一个向量。
  3. 接着,通过一个MLP网络对这个“特征向量”进行预测,更新coarse prediction。也就相当于对这个难点进行新的预测,对他进行分类。

看完这个,我们就可以这么理解,将预测难的点(边缘点)提取出来,再提取其特征向量,经过MLP网络,将这个点的归属进行分类,然后提升这些点的分类准确率。这就是PointRend的思想。

一个PointRend模块包括三部分

1.point selection strategy:用于inference和traing的点选择

对于点采样过程,需要对模型的Train过程和Inference过程做区分

该方法的核心思想是灵活自适应地选择图像平面上的点来预测分割标签。直观地说,这些点应该更密集地位于高频区域附近,例如物体边界,类似于射线追踪中的反混叠问题。我们产生了推理训练的想法。

  • inference推理

通过仅在与其邻域有显着不同的位置进行计算,该方法可用于有效地渲染高分辨率图像(例如,通过光线跟踪)。对于所有其他位置,通过对已经计算的输出值(从粗网格开始)进行插值来获得值。

对于每个区域,我们以粗到精的方式迭代地“渲染”输出蒙版。在规则网格上的点上进行最粗糙级别的预测(例如,通过使用标准的粗糙分段预测头)。在每次迭代中,PointRend使用双线性插值对其先前预测的分段进行上采样,然后在此较密集的网格上选择N个最不确定的点(例如,对于二进制掩码,概率最接近0.5的那些)。然后,PointRend为这N个点中的每一个点计算特征,并预测它们的标签。重复该过程,直到将分段上采样到所需的分辨率为止。

  • training

对于Train过程的点采样操作,同样可以遵循Inference中的操作。但是作者发现,这样子采样对于梯度的传播不太友好,于是只能被迫选择其他的点采样策略——干脆就用随机采样的方式来进行采样。

在训练过程中,PointRend还需要选择一些点,以在这些点上构建用于训练point head的逐点(point-wise)特征。原则上,点选择策略可以类似于推理inference中使用的细分策略。但是,细分引入了一系列步骤,这些步骤对于通过反向传播训练神经网络不太友好。取而代之的是,为了训练,我们使用基于随机采样的非迭代策略

采样策略在特征图上选择N个点进行训练。它旨在使用三个原理将选择偏向不确定区域,同时还保留一定程度的均匀覆盖。对于训练和推理选择,N的值可以不同。

(i)过度生成:我们通过从均匀分布中随机采样kN个点(k> 1)来过度生成候选点。(ii)重要抽样:通过对所有kN个点的粗略预测值进行插值并计算任务特定的不确定性估计,我们将重点放在具有粗略预测的点上。从kN个候选中选择最不确定的βN个点(β∈[0,1])。(iii)覆盖范围:从均匀分布中采样剩余的(1-β)N点。我们用不同的设置来说明此过程,并将其与常规的网格选择进行比较,如下图所示。

在训练时,预测和损失函数仅在N个采样点上计算(除粗略分割外),这比通过细分步骤进行反向传播更简单,更有效。这种设计类似于在Faster R-CNN系统中对RPN + Fast R-CNN的并行训练,其推理是顺序的。

2. Point-wise Representation:逐点表示

PointRend通过组合(例如,级联)两种特征类型(细粒度和粗略预测特征)在选定点上构造逐点特征,如下所述。

  • 细粒度特征

为了允许PointRend呈现精细的分割细节,我们从CNN特征图中提取每个采样点的特征向量。 因为一个点是“实值2D坐标”,所以我们按照标准做法对特征图执行双线性插值,以计算特征向量。 可以从单个特征图中提取特征(例如,ResNet中的res2);也可以按照Hypercolumn方法,从多个特征图(例如res2到res5)中提取并连接它们。

  • 粗预测特征

细粒度的特征可以解析细节,但在两个方面也有不足:

首先,它们不包含特定区域的信息,因此,两个实例的边界框重叠的相同点将具有相同的细粒度特征。但是,该点只能位于一个实例之中。 因此,对于实例分割的任务,其中不同的区域可能针对同一点预测不同的标签,因此需要其他区域特定的信息。

其次,取决于用于细粒度特征的特征图,这些特征可能只包含相对较低级别的信息(例如,我们将对res2使用DeepLabV3)。 因此,需要有更多具有上下文和语义信息的特征。

基于这两点考虑,第二种特征类型是来自网络的粗分割预测,例如表示k类预测的区域(box)中每个点的k维向量。通过设计,粗分辨率能够提了更加全局的上下文信息,而通道则传递语义类别。这些粗略的预测与现有架构的输出相似,并且在训练过程中以与现有模型相同的方式进行监督。例如,在mask R-CNN中,粗预测可以是一个轻量级的7×7分辨率Mask头的输出。

点特征向量(point features),主要由两部分组成,分别是fine-grained features的对应点和coarse prediction的对应点的特征向量,将这个两个特征向量拼接成一个向量

3. point head

给定每个选定点的逐点特征表示,PointRend使用简单的多层感知器(MLP)进行逐点分割预测。这个MLP在所有点(和所有区域)上共享权重,类似于图卷积或PointNet。由于MLP会预测每个点的分割标签,因此可以通过特定任务的分割loss进行训练。

三、效果如何?

实验结果

  • 网络设计

实验使用ResNet-50+ FPN 的Mask-Rcnn作backbone。 Mask-RCNN中的默认head是region-wise FCN,用“ 4×conv”表示,作为用来与本文网络进行比较的基准网络。

为了计算粗略预测,我们用重量更轻的设计替换4×conv Mask头,该设计类似于Mask R-CNN的box head产生7×7Mask预测。具体来说,对于每个边界框,我们使用双线性插值从FPN的P2层提取14×14特征图。这些特征是在边界框内的规则网格上计算的(此操作可以看作是RoIAlign的简单版本)。接下来,我们使用具有256个输出通道步幅为2的 2×2卷积层,后跟ReLU, 将空间大小减小到7×7。最后,类似于Mask R-CNN的box head,用两个带1024宽的隐藏层的MLP为K类分别产生7×7的Mask预测。ReLU用于MLP的隐藏层,并且Sigmoid激活函数应用于输出。

PointRend:在每个选定点上,使用双线性插值从粗预测头的输出中提取K维特征向量,PointRend还从FPN的P2级别插值256维特征向量,步长为4。这些粗预测和细粒度特征向量是串联在一起的,我们使用具有256个通道的3个隐藏层的MLP在选定点进行K类别预测。在MLP的每个层中,我们用K个粗预测特征补充到256个输出通道中,作为下一层输入向量。在MLP中使用ReLU,并将Sigmoid激活函数应用于输出。

不得不说这个针对物体边缘进行优化的上采样方法的确在感官上和数据上都有很不错的效果:

语义分割结果:

实例分割结果(基于MaskR-CNN):

PointRend的一些代码和实现

摘自: https://chowdera.com/2022/194/202207120607167479.html

代码详解: https://www.361shipin.com/blog/1536592971120508928

  • 作者提出可以在预测出来的mask中只选择Top N最不确定的位置进行细分预测。

具体为先根据粗糙预测出来的mask,将mask按类别预测分数排序,选出分数高的前2 类别的mask,计算出在2个类别mask上均有较高得分的Top K个像素点作为K 个不确定点【1个像素点只能对应1个类别,如果它对应2个类别的分数都很高,说明它很可能是边界点,也是不确定的】

def sampling_points(mask, N, k=3, beta=0.75, training=True):
    """
    主要思想:根据粗糙的预测结果,找出不确定的像素点
    :param mask: 粗糙的预测结果(out)   eg.[2, 19, 48, 48]
    :param N: 不确定点个数(train:N = 图片的尺寸/16, test: N = 8096)    eg. N=48
    :param k: 超参
    :param beta: 超参
    :param training:
    :return: 不确定点的位置坐标  eg.[2, 48, 2]
    """
    assert mask.dim() == 4, "Dim must be N(Batch)CHW"   #this mask is out(coarse)
    device = mask.device
    B, _, H, W = mask.shape   #first: mask[1, 19, 48, 48]
    mask, _ = mask.sort(1, descending=True) #_ : [1, 19, 48, 48],按照每一类的总体得分排序
    if not training:
        H_step, W_step = 1 / H, 1 / W
        N = min(H * W, N)
        uncertainty_map = -1 * (mask[:, 0] - mask[:, 1])
        #mask[:, 0]表示每个像素最有可能的分类,mask[:, 1]表示每个像素次有可能的分类,当一个像素
        #即是最有可能的又是次有可能的,则证明它不好预测,对应的uncertainty_map就相对较大
        _, idx = uncertainty_map.view(B, -1).topk(N, dim=1) #id选出最不好预测的N个点
        points = torch.zeros(B, N, 2, dtype=torch.float, device=device)
        points[:, :, 0] = W_step / 2.0 + (idx  % W).to(torch.float) * W_step    #点的横坐标
        points[:, :, 1] = H_step / 2.0 + (idx // W).to(torch.float) * H_step    #点的纵坐标
        return idx, points  #idx:48 || points:[1, 48, 2]
  • 得到不确定点的位置以后,可以通过Bilinear插值得到对应的特征,对每个不确定点的使用一个MLP来进行单独进行细分预测【训练与预测有所区别】。

具体为:通过刚刚得到的不确定点所在图片的相对位置坐标来找到对应的特征点,将此点对应的特征向量与此点的粗糙预测结果合并,然后通过一个MLP进行细分预测。

##训练阶段
def forward(self, x, res2, out):
        """
        主要思路:
        通过 out(粗糙预测)计算出top N 个不稳定的像素点,针对每个不稳定像素点得到在res2(fine)
        和out(coarse)中对应的特征,组合N个不稳定像素点对应的fine和coarse得到rend,
        再通过mlp得到更准确的预测
        :param x: 表示输入图片的特征     eg.[2, 3, 768, 768]
        :param res2: 表示xception的第一层特征输出     eg.[2, 256, 192, 192]
        :param out: 表示经过级联空洞卷积提取的特征的粗糙预测    eg.[2, 19, 48, 48]
        :return: rend:更准确的预测,points:不确定像素点的位置
        """
        """
        1. Fine-grained features are interpolated from res2 for DeeplabV3
        2. During training we sample as many points as there are on a stride 16 feature map of the input
        3. To measure prediction uncertainty
           we use the same strategy during training and inference: the difference between the most
           confident and second most confident class probabilities.
        """
        if not self.training:
            return self.inference(x, res2, out)
		#获得不确定点的坐标
        points = sampling_points(out, x.shape[-1] // 16, self.k, self.beta) #out:[2, 19, 48, 48] || x:[2, 3, 768, 768] || points:[2, 48, 2]
		#根据不确定点的坐标,得到对应的粗糙预测
        coarse = point_sample(out, points, align_corners=False) #[2, 19, 48]
        #根据不确定点的坐标,得到对应的特征向量
        fine = point_sample(res2, points, align_corners=False)  #[2, 256, 48]
		#将粗糙预测与对应的特征向量合并
        feature_representation = torch.cat([coarse, fine], dim=1)   #[2, 275, 48]
		#使用MLP进行细分预测
        rend = self.mlp(feature_representation) #[2, 19, 48]
        return {"rend": rend, "points": points}
##推理阶段
@torch.no_grad()
    def inference(self, x, res2, out):
        """
        输入:
        x:[1, 3, 768, 768],表示输入图片的特征
        res2:[1, 256, 192, 192],表示xception的第一层特征输出
        out:[1, 19, 48, 48],表示经过级联空洞卷积提取的特征的粗糙预测
        输出:
        out:[1,19,768,768],表示最终图片的预测
        主要思路:
        通过 out计算出top N = 8096 个不稳定的像素点,针对每个不稳定像素点得到在res2(fine)
        和out(coarse)中对应的特征,组合8096个不稳定像素点对应的fine和coarse得到rend,
        再通过mlp得到更准确的预测,迭代至rend的尺寸大小等于输入图片的尺寸大小
        """
        """
        During inference, subdivision uses N=8096
        (i.e., the number of points in the stride 16 map of a 1024×2048 image)
        """
        num_points = 8096
                while out.shape[-1] != x.shape[-1]: #out:[1, 19, 48, 48], x:[1, 3, 768, 768]
        	#每一次预测均会扩大2倍像素,直至与原图像素大小一致
            out = F.interpolate(out, scale_factor=2, mode="bilinear", align_corners=True)   #out[1, 19, 48, 48]
            points_idx, points = sampling_points(out, num_points, training=self.training)   #points_idx:8096 || points:[1, 8096, 2]
            coarse = point_sample(out, points, align_corners=False) #coarse:[1, 19, 8096]   表示8096个不稳定像素点根据高级特征得出的对应的类别
            fine = point_sample(res2, points, align_corners=False)  #fine:[1, 256, 8096]    表示8096个不稳定像素点根据低级特征得出的对应类别
            feature_representation = torch.cat([coarse, fine], dim=1)   #[1, 275, 8096] 表示8096个不稳定像素点合并fine和coarse的特征
            rend = self.mlp(feature_representation) #[1, 19, 8096]
            B, C, H, W = out.shape  #first:[1, 19, 128, 256]
            points_idx = points_idx.unsqueeze(1).expand(-1, C, -1)  #[1, 19, 8096]
            out = (out.reshape(B, C, -1)

超越YOLOv7 | YOLOv6论文

各种Tricks大放异彩!!

论文: https://arxiv.org/abs/2209.02976

github:https://github.com/meituan/YOLOv6

多年来,YOLO 系列一直是高效目标检测的行业标准。YOLO 社区蓬勃发展,丰富了其在众多硬件平台和丰富场景中的使用。在这份技术报告力求将其极限推向新的高度,以坚定不移的行业应用心态向前迈进。

考虑到真实环境中对速度和准确性的不同要求,作者广泛研究了来自工业界或学术界的最新目标检测进展。具体来说,从最近的网络设计、训练策略、测试技术、量化和优化方法中大量吸收了一些想法。最重要的是,整合思想和实践,构建了一套不同规模的部署网络,以适应多样化的用例。

在 YOLO 作者的慷慨许可下,作者将其命名为 YOLOv6。作者也热烈欢迎用户和贡献者进一步增强。YOLOv6-N 在 NVIDIA Tesla T4 GPU 上以 1234 FPS 的吞吐量在 COCO 数据集上达到 35.9% 的 AP。YOLOv6-S 以 495 FPS 的速度达到 43.5% 的 AP,优于同规模的其他主流检测器(YOLOv5-SYOLOX-S 和 PPYOLOE-S)。

YOLOv6-S 量化版本甚至带来了 869 FPS 的最新 43.3% AP。此外,与具有相似推理速度的其他检测器相比,YOLOv6-M/L 还实现了更好的准确度性能(即 49.5%/52.3%)。

1、简介

YOLO 系列因其在速度和准确性之间的出色平衡而成为工业应用中最流行的检测框架。YOLO 系列的开创性作品是YOLOv1-3,随着后期的实质性改进,开创了单阶段检测器的新路。YOLOv4 将检测框架重组为几个独立的部分(backboneneck 和 head),并验证了当时的 bag-of-freebies 和 bag-of-specials,设计了一个适合在单 GPU 上训练的框架。目前,YOLOv5YOLOXPPYOLOE 和 YOLOv7 都是高效检测器部署的竞争候选者。不同大小的模型通常通过缩放技术获得。

在本报告中,作者凭经验观察了几个促使更新 YOLO 框架的重要因素:

  1. RepVGG 的重参化是一种高级技术,尚未在检测中得到很好的利用。作者还注意到 RepVGG Block 的简单模型缩放变得不切实际,为此作者认为小型和大型网络之间的网络设计的优雅一致性是不必要的。普通的单路径架构对于小型网络是更好的选择,但对于较大的模型,参数的指数增长和单路径架构的计算成本使其不可行;
  2. 基于重参化的检测器的量化也需要细致处理,否则由于其在训练和推理过程中的异构配置,将难以处理性能下降。
  3. 以前的工作往往不太关注部署,其延迟通常在 V100 等高成本机器上进行比较。在实际服务环境方面存在硬件差距。通常,像 Tesla T4 这样的低功耗 GPU 成本更低,并且提供相当好的推理性能。
  4. 考虑到架构差异,标签分配和损失函数设计等高级特定领域策略需要进一步验证;
  5. 对于部署,可以容忍训练策略的调整,提高准确度性能但不增加推理成本,例如知识蒸馏。

考虑到上述观察,带来了 YOLOv6 的诞生,它在准确性和速度方面实现了迄今为止最好的权衡。在图 1 中展示了 YOLOv6 与其他类似规模的同行的比较。为了在不大幅降低性能的情况下提高推理速度,研究了包括训练后量化 (PTQ) 和量化感知训练 (QAT) 在内的尖端量化方法,并将它们纳入 YOLOv6 以实现部署就绪网络的目标。

将 YOLOv6 的主要方面总结如下:

  • 针对不同场景中的工业应用重新设计了一系列不同规模的网络。不同规模的架构各不相同,以实现最佳的速度和准确性权衡,其中小型模型具有简单的单路径主干,大型模型建立在高效的多分支块上。
  • 为 YOLOv6 注入了一种self-distillation策略,在分类任务和回归任务上都执行。同时,动态调整来自教师和标签的知识,以帮助学生模型在所有训练阶段更有效地学习知识。
  • 广泛验证标签分配、损失函数和数据增强技术的先进检测技术,并有选择地采用它们以进一步提高性能。
  • 在 RepOptimizer 和通道蒸馏的帮助下改进了检测的量化方案,这带来了具有 43.3% 的 COCO AP 和 869 FPS 的吞吐量的快速准确的检测器,批量大小为 32。

2、YOLOv6方法全解

YOLOv6 的改造设计包括以下组件,网络设计、标签分配、损失函数、数据增强、行业便利改进以及量化和部署:

网络设计:

  • Backbone:与其他主流架构相比, RepVGG 主干在相似的推理速度下在小型网络中具有更强的特征表示能力,但由于参数和计算成本的爆炸式增长,它很难扩展以获得更大的模型。在这方面,将 RepBlock 作为小型网络的构建块。对于大型模型,修改了一个更高效的 CSP Block,名为 CSPStackRep 块。
  • Neck:YOLOv6 的 Neck 采用 YOLOv4 和 YOLOv5 之后的 PAN。使用 RepBlocks 或 CSPStackRep Blocks 增强 Neck 以获得 Rep-PAN
  • Head:简化了 Decoupled Head,使其更高效,称为Efficient Decoupled Head

标签分配:

通过大量实验评估了 YOLOv6 上标签分配策略的最新进展,结果表明 TAL 更有效且对训练更友好。

损失函数:

主流的Anchor-Free检测器的损失函数包括分类损失、框回归损失和目标损失。对于每个损失,用所有可用的技术系统地对其进行试验,最后选择 VariFocal Loss 分类损失,SIoU/GIoU 损失作为回归损失。

行业便利的改进:

引入了额外的常见实践和技巧来提高性能,包括self-distillation和更多的训练时期。对于self-distillation,分类和框回归分别由教师模型监督。多亏了 DFL使得框回归的蒸馏成为可能。此外,来自Soft Label和Hard Label的信息比例通过余弦衰减动态下降,这有助于学生在训练过程中的不同阶段选择性地获取知识。此外,作者遇到了性能受损的问题,而在评估时没有添加额外的灰色边框,为此提供了一些补救措施。

量化和部署:

为了解决量化基于重参化模型的性能下降问题,使用 RepOptimizer 训练 YOLOv6 以获得对 PTQ 友好的权重。进一步采用 QAT 和通道蒸馏和图优化来追求极致性能。量化 YOLOv6-S 达到了最新的技术水平,AP 为 42.3%,吞吐量为 869 FPS(batch size=32)。

2.1、网络设计

单阶段目标检测器一般由以下部分组成:BackboneNeckHeadBackbone主要决定特征表示能力,同时,它的设计对推理效率有至关重要的影响,因为它承载了很大一部分计算成本。Neck用于将低层次的物理特征与高层次的语义特征进行聚合,然后构建各个层次的金字塔特征图。Head由几个卷积层组成,它根据Neck融合的多级特征预测最终检测结果。从结构的角度来看,它可以分为Anchor-BaseAnchor-Free,或者更确切地说是参数耦合Head和参数解耦Head

在 YOLOv6 中,基于硬件友好的网络设计原则,提出了两个可缩放的可重参数BackboneNeck以适应不同大小的模型,以及一个具有混合通道策略的高效解耦HeadYOLOv6 的整体架构如图 2 所示。

1、Backbone

如上所述,Backbone网络的设计对检测模型的有效性和效率有很大的影响。以前,已经表明多分支网络通常可以比单路径网络实现更好的分类性能,但它通常伴随着并行度的降低并导致推理延迟的增加。相反,像 VGG 这样的普通单路径网络具有高并行性和更少内存占用的优势,从而带来更高的推理效率。最近在 RepVGG 中,提出了一种结构重参化方法,将训练时多分支拓扑与推理时普通架构解耦,以实现更好的速度-准确度权衡。

受上述工作的启发,设计了一个高效的可重参化Backbone,表示为 EfficientRep。对于小型模型,Backbone的主要组成部分是训练阶段的 RepBlock,如图 3(a)所示。并且每个 RepBlock 在推理阶段被转换为具有 ReLU 激活函数的 3×3 卷积层(表示为 RepConv)的堆栈,如图 3(b)所示。通常,3×3 卷积在主流 GPU 和 CPU 上进行了高度优化,并且具有更高的计算密度。因此,EfficientRep Backbone 充分利用了硬件的计算能力,在显着降低推理延迟的同时增强了表示能力。

然而,作者注意到随着模型容量的进一步扩大,单路径普通网络中的计算成本和参数数量呈指数增长。为了在计算负担和准确性之间取得更好的平衡,修改了一个 CSPStackRep Block 来构建中型和大型网络的Backbone。如图 3(c) 所示,CSPStackRep Block 由3个 1×1 卷积层和一堆子块组成,该子块由两个 RepVGG Block 或 RepConv(分别在训练或推理时)和一个残差连接组成。此外,采用跨级部分(CSP)连接来提高性能,而不会产生过多的计算成本。与 CSPRepResStage 相比,它的外观更加简洁,并考虑了准确性和速度之间的平衡。

2、Neck

在实践中,多尺度的特征集成已被证明是目标检测的关键和有效部分。采用来自 YOLOv4 和 YOLOv5 的修改后的 PAN 拓扑作为检测Neck的基础。此外,将 YOLOv5 中使用的 CSPBlock 替换为 RepBlock(适用于小型模型)或 CSPStackRep Block(适用于大型模型),并相应调整宽度和深度。YOLOv6 的Neck表示为 Rep-PAN

3、Head

Efficient decoupled head

YOLOv5 的检测头是一个耦合Head,在分类和定位分支之间共享参数,而 FCOS 和 YOLOX 中的检测头将两个分支解耦,并且在每个分支中引入了额外的两个 3×3 卷积层以提高性能。

在 YOLOv6 中采用混合通道策略来构建更高效的解耦Head。具体来说,将中间 3×3 卷积层的数量减少到只有一个。头部的宽度由BackboneNeck的宽度乘数共同缩放。这些修改进一步降低了计算成本,以实现更低的推理延迟。

Anchor-free

Anchor-free检测器因其更好的泛化能力和解码预测结果的简单性而脱颖而出。其后处理的时间成本大大降低。有两种类型的Anchor-free检测器:基于Anchor和基于关键点。在 YOLOv6 中,我们采用了基于Anchor点的范式,其框回归分支实际上预测了Anchor点到边界框4个边的距离。

2.2、Label Assignment

标签分配负责在训练阶段为预定义的Anchor分配标签。以前的工作已经提出了各种标签分配策略,从简单的基于 IoU 的策略和内部真实方法到其他更复杂的方案。

SimOTA OTA 将目标检测中的标签分配视为最佳传输问题。它从全局角度为每个真实对象定义了正/负训练样本。SimOTA 是 OTA 的简化版本,它减少了额外的超参数并保持了性能。在 YOLOv6 的早期版本中使用 SimOTA 作为标签分配方法。然而,在实践中,作者发现引入 SimOTA 会减慢训练过程。而且陷入不稳定训练的情况并不少见。因此,希望更换 SimOTA

任务对齐学习任务对齐学习(Task Alignment Learning,TAL)最早是在TOOD中提出的,其中设计了一个分类分数和预测框质量的统一度量。IoU 被这个指标替换以分配对象标签。在一定程度上缓解了任务错位(分类和框回归)的问题。TOOD 的另一个主要贡献是关于任务对齐的头部(T-head)。T-head 堆叠卷积层以构建交互式特征,在其之上使用任务对齐预测器 (TAP)。PP-YOLOE 改进了 T-head,将 T-head 中的 layer attention 替换为轻量级的 ESE attention,形成 ET-head。然而,我们发现 ET-head 会降低模型中的推理速度,并且没有准确度增益。因此,保留了高效解耦头的设计。

此外,作者观察到 TAL 可以带来比 SimOTA 更多的性能提升并稳定训练。因此,采用 TAL 作为 YOLOv6 中的默认标签分配策略。

2.3、损失函数

1、Classification Loss

提高分类器的性能是优化检测器的关键部分。Focal Loss 修改了传统的交叉熵损失,以解决正负样本之间或难易样本之间的类别不平衡问题。为了解决训练和推理之间质量估计和分类的不一致使用,Quality Focal LossQFL)进一步扩展了Focal Loss,联合表示分类分数和分类监督的定位质量。而 VariFocal Loss (VFL) 源于 Focal Loss,但它不对称地对待正样本和负样本。通过考虑不同重要性的正负样本,它平衡了来自两个样本的学习信号。Poly Loss 将常用的分类损失分解为一系列加权多项式基。它在不同的任务和数据集上调整多项式系数,通过实验证明比交叉熵损失和Focal Loss损失更好。

在 YOLOv6 上评估所有这些高级分类损失,最终采用 VFL

2、Box Regression Loss

框回归损失提供了精确定位边界框的重要学习信号。L1 Loss 是早期作品中的原始框回归损失。逐渐地,各种精心设计的框回归损失如雨后春笋般涌现,例如 IoU-series 损失和概率损失。

IoU-series Loss IoU loss 将预测框的四个边界作为一个整体进行回归。它已被证明是有效的,因为它与评估指标的一致性。IoU的变种有很多,如GIoUDIoUCIoUα-IoUSIoU等,形成了相关的损失函数。我们在这项工作中对 GIoUCIoU 和 SIoU 进行了实验。并且SIoU应用于YOLOv6-NYOLOv6-T,而其他的则使用GIoU

Probability Loss Distribution Focal Loss (DFL) 将框位置的基本连续分布简化为离散化的概率分布。它在不引入任何其他强先验的情况下考虑了数据中的模糊性和不确定性,这有助于提高框定位精度,尤其是在ground-truth框的边界模糊时。在 DFL 上,DFLv2 开发了一个轻量级的子网络,以利用分布统计数据与真实定位质量之间的密切相关性,进一步提高了检测性能。然而,DFL 输出的回归值通常比一般框回归多 17 倍,从而导致大量开销。额外的计算成本阻碍了小型模型的训练。而 DFLv2 由于额外的子网络,进一步增加了计算负担。在实验中,DFLv2 在模型上为 DFL 带来了类似的性能提升。因此,只在 YOLOv6-M/L 中采用 DFL

3、Object Loss

Object loss 最早是在 FCOS 中提出的,用于降低低质量边界框的得分,以便在后处理中将其过滤掉。它还被用于 YOLOX 以加速收敛并提高网络精度。作为像 FCOS 和 YOLOX 这样的Anchor-free框架,在 YOLOv6 中尝试过 object loss。不幸的是,它并没有带来很多积极的影响。

2.4、行业便利的改进

1、More training epochs

经验结果表明,检测器的性能随着训练时间的增加而不断进步。作者将训练持续时间从 300 个 epoch 延长到 400 个 epoch,以达到更好的收敛性。

2、Self-distillation

为了在不引入太多额外计算成本的情况下进一步提高模型精度,应用了经典的知识蒸馏技术,最小化了教师和学生预测之间的 KL-divergence。将老师限制为学生本身,但经过预训练,因此称之为自我蒸馏。

请注意,KL-divergence通常用于衡量数据分布之间的差异。然而,目标检测中有两个子任务,其中只有分类任务可以直接利用基于 KL-divergence的知识蒸馏。由于 DFL 损失,也可以在框回归上执行它。知识蒸馏损失可以表示为:

其中Ldet  是使用预测和标签计算的检测损失。引入超参数α来平衡两个损失。在训练的早期阶段,来自老师的软标签更容易学习。随着训练的继续,学生的表现将与老师相匹配,因此硬标签将更多地帮助学生。在此基础上,将余弦权重衰减应用于 α,以动态调整来自硬标签和来自教师的软标签的信息。

3、Gray border of images

作者注意到在 YOLOv5 和 YOLOv7 的实现中评估模型性能时,每个图像周围都有一个半步长的灰色边框。虽然没有添加有用的信息,但它有助于检测图像边缘附近的对象。这个技巧也适用于 YOLOv6

然而,额外的灰色像素明显降低了推理速度。没有灰色边框,YOLOv6 的性能会变差。假设该问题与马赛克增强中的灰色边框填充有关。进行了在最后一个时期关闭马赛克增强的实验(也称为淡入淡出策略)以进行验证。对此,改变了灰色边框的区域,将带有灰色边框的图像直接调整为目标图像大小。结合这两种策略,模型可以在不降低推理速度的情况下保持甚至提高性能。

2.5、量化与部署

对于工业部署,通常的做法是采用量化来进一步加快运行时间而不会对性能造成太大影响。训练后量化(PTQ)直接量化模型,只需要一个小的校准集。而量化感知训练(QAT)通过访问训练集进一步提高了性能,这通常与蒸馏结合使用。然而,由于在 YOLOv6 中大量使用了重参化块,以前的 PTQ 技术无法产生高性能,而在训练和推理期间在匹配假量化器时很难结合 QAT

1、Reparameterizing Optimizer

RepOptimizer 在每个优化步骤提出梯度重参化。该技术也很好地解决了基于重参化模型的量化问题。因此,以这种方式重建 YOLOv6 的重参化块,并使用 RepOptimizer 对其进行训练以获得对 PTQ 友好的权重。特征图的分布在很大程度上变窄了,这极大地有利于量化过程。

2、Sensitivity Analysis

通过将量化敏感操作部分转换为浮点计算来进一步提高 PTQ 性能。为了获得灵敏度分布,通常使用几个指标,均方误差 (MSE)、信噪比 (SNR) 和余弦相似度。通常为了比较,可以选择输出特征图(在激活某个层之后)来计算这些带有和不带量化的指标。作为替代方案,通过打开和关闭特定层的量化来计算验证 AP 也是可行的。

在使用 RepOptimizer 训练的 YOLOv6-S 模型上计算所有这些指标,并选择前 6 个敏感层以浮动运行。

3、使用 Channel-wise Distillation 进行量化感知训练

如果 PTQ 不足,建议使用量化感知训练 (QAT) 来提高量化性能。为了解决训练和推理过程中假量化器不一致的问题,有必要在 RepOptimizer 上构建 QAT。此外,通道蒸馏(后来称为 CW Distill)适用于 YOLOv6 框架,如图 5 所示。这也是一种自我蒸馏方法,其中教师网络是 FP32 精度的学生本身。

实验

消融实验

1、label assignment

2、损失函数

3、自蒸馏

4、Gray border of images

5、PTQ

6、QAT

SOTA对比

TensorRT部署实践对比

T4 GPU

V100 GPU

区域卷积神经⽹络(R-CNN)系列

来自:动手学深度学习

视频讲解:Faster R-CNN原理 、源码解析

1、R-CNN

R-CNN ⾸先从输⼊图像中选取若⼲(例如2000个)提议区域(如锚框也是⼀种选取方法),并标注它们的类别和边界框(如偏移量)。[Girshick et al., 2014] 然后,⽤卷积神经⽹络对每个提议区域进⾏前向计算以抽取其特征。接下来,我们⽤每个提议区域的特征来预测类别和边界框。

具体来说,R-CNN包括以下四个步骤:

  1. 对输⼊图像使⽤ 选择性搜索来选取多个⾼质量的提议区域 [Uijlings et al., 2013] 。这些提议区域通常是在多个尺度下选取的,并具有不同的形状和⼤小。每个提议区域都将被标注类别和真实边界框。
  2. 选择⼀个预训练的卷积神经⽹络,并将其在输出层之前截断。将每个提议区域变形为⽹络需要的输⼊尺⼨,并通过前向计算输出抽取的提议区域特征。
  3. 将每个提议区域的特征连同其标注的类别作为⼀个样本。训练多个⽀持向量机对⽬标分类,其中每个⽀持向量机⽤来判断样本是否属于某⼀个类别。
  4. 将每个提议区域的特征连同其标注的边界框作为⼀个样本,训练线性回归模型来预测真实边界框。
  • 尽管 R-CNN 模型通过预训练的卷积神经⽹络有效地抽取了图像特征,但它的速度很慢。想象⼀下,我们可能从⼀张图像中选出上千个提议区域,这需要上千次的卷积神经⽹络的前向计算来执⾏⽬标检测。这种庞⼤的计算量使得 R-CNN 在现实世界中难以被⼴泛应⽤。

2、Fast R-CNN

R-CNN 的主要性能瓶颈在于,对每个提议区域,卷积神经⽹络的前向计算是独⽴的,而没有共享计算。由于这些区域通常有重叠,独⽴的特征抽取会导致重复的计算。Fast R-CNN [Girshick, 2015] 对 R-CNN 的主要改进之⼀,是仅在整张图象上执⾏卷积神经⽹络的前向计算。

它的主要计算如下:

  1. 与 R-CNN 相⽐,Fast R-CNN ⽤来提取特征的卷积神经⽹络的输⼊是整个图像,而不是各个提议区域。此外,这个⽹络通常会参与训练。设输⼊为⼀张图像,将卷积神经⽹络的输出的形状记为 1×c×h1×w1。
  2. 假设选择性搜索⽣成了n个提议区域。这些形状各异的提议区域在卷积神经⽹络的输出上分别标出了形状各异的兴趣区域。然后,这些感兴趣的区域需要进⼀步抽取出形状相同的特征(⽐如指定⾼度h2和宽度w2),以便于连结后输出。为了实现这⼀⽬标,Fast R-CNN 引⼊了 兴趣区域 (RoI) 池化层:将卷积神经⽹络的输出和提议区域作为输⼊,输出连结后的各个提议区域抽取的特征,形状为n × c × h2 × w2。
  3. 通过全连接层将输出形状变换为n × d,其中超参数d取决于模型设计。
  4. 预测n个提议区域中每个区域的类别和边界框。更具体地说,在预测类别和边界框时,将全连接层的输出分别转换为形状为 n × q(q 是类别的数量)的输出和形状为 n × 4 的输出。其中预测类别时使⽤softmax 回归。

在Fast R-CNN 中提出的兴趣区域汇聚层与 6.5节 中介绍的汇聚层有所不同。在汇聚层中,我们通过设置池化窗口、填充和步幅的⼤小来间接控制输出形状。而兴趣区域汇聚层对每个区域的输出形状是可以直接指定的。例如,指定每个区域输出的⾼和宽分别为 h2 和 w2。对于任何形状为 h × w 的兴趣区域窗口,该窗口将被划分为 h2 × w2 ⼦窗口⽹格,其中每个⼦窗口的⼤小约为(h/h2) × (w/w2)。在实践中,任何⼦窗口的⾼度和宽度都应向上取整,其中的最⼤元素作为该⼦窗口的输出。因此,兴趣区域汇聚层可从形状各异的兴趣区域中均抽取出形状相同的特征。

3、Faster R-CNN

为了较精确地检测⽬标结果,Fast R-CNN 模型通常需要在选择性搜索中⽣成⼤量的提议区域。Faster R-CNN [Ren et al., 2015] 提出将选择性搜索替换为 区域提议⽹络(region proposal network),从而减少提议区域的⽣成数量,并保证⽬标检测的精度。

与Fast R-CNN 相⽐,Faster R-CNN 只将⽣成提议区域的⽅法从选择性
搜索改为了区域提议⽹络,模型的其余部分保持不变。具体来说,区域提议⽹络的计算步骤如下:

  1. 使⽤填充为1的 3 × 3 的卷积层变换卷积神经⽹络的输出,并将输出通道数记为 c。这样,卷积神经⽹络为图像抽取的特征图中的每个单元均得到⼀个⻓度为 c 的新特征。
  2. 以特征图的每个像素为中⼼,⽣成多个不同⼤小和宽⾼⽐的锚框并标注它们。
  3. 使⽤锚框中⼼单元⻓度为 c 的特征,分别预测该锚框的⼆元类别(含⽬标还是背景)和边界框。
  4. 使⽤⾮极⼤值抑制,从预测类别为⽬标的预测边界框中移除相似的结果。最终输出的预测边界框即是兴趣区域汇聚层所需的提议区域。

值得⼀提的是,区域提议⽹络作为 Faster R-CNN 模型的⼀部分,是和整个模型⼀起训练得到的。换句话说,Faster R-CNN 的⽬标函数不仅包括⽬标检测中的类别和边界框预测,还包括区域提议⽹络中锚框的⼆元类别和边界框预测。作为端到端训练的结果,区域提议⽹络能够学习到如何⽣成⾼质量的提议区域,从而在减少了从数据中学习的提议区域的数量的情况下,仍保持⽬标检测的精度

4、Mask R-CNN

如果在训练集中还标注了每个⽬标在图像上的像素级位置,那么 Mask R-CNN [He et al., 2017] 能够有效地利⽤这些详尽的标注信息进⼀步提升⽬标检测的精度。

如 图13.8.5 所⽰,Mask R-CNN 是基于 Faster R-CNN 修改而来的。具体来说,Mask R-CNN 将兴趣区域汇聚层替换为了 兴趣区域 (RoI) 对⻬层使⽤ 双线性插值(bilinear interpolation)来保留特征图上的空间信息,从而更适于像素级预测。兴趣区域对⻬层的输出包含了所有与兴趣区域的形状相同的特征图。它们不仅被⽤于预测每个兴趣区域的类别和边界框,还通过额外的全卷积⽹络预测⽬标的像素级位置。

补充:ROI Align 和 ROI Pooling

这两个都是用在rpn之后的。具体来说,从feature map上经过RPN得到一系列的proposals,大概2k个,这些bbox大小不等,如何将这些bbox的特征进行统一表示就变成了一个问题。即需要找一个办法从大小不等的框中提取特征使输出结果是等长的。最开始目标检测模型Faster RCNN中用了一个简单粗暴的办法,叫ROI Pooling。该方式在语义分割这种精细程度高的任务中,不够精准,由此发展来了ROI Align。

ROI Pooling:

假如现在有一个8×8的feature map,现在希望得到2×2的输出,有一个bbox坐标为[0,3,7,8]。

这个bbox的w=7,h=5,如果要等分成四块是做不到的,因此在ROI Pooling中会进行取整。就有了上图看到的h被分割为2,3,w被分割成3,4。这样之后在每一块(称为bin)中做max pooling,可以得到下图的结果。

这样就可以将任意大小bbox转成2×2表示的feature。

ROI Pooling需要取整,这样的取整操作进行了两次,一次是得到bbox在feature map上的坐标时。

例如:原图上的bbox大小为665×665,经backbone后,spatial scale=1/32。因此bbox也相应应该缩小为665/32=20.78,但是这并不是一个真实的pixel所在的位置,因此这一步会取为20。0.78的差距反馈到原图就是0.78×32=25个像素的差距。如果是大目标这25的差距可能看不出来,但对于小目标而言差距就比较巨大了。

ROI Align

因此有人提出不需要进行取整操作,如果计算得到小数,也就是没有落到真实的pixel上,那么就用最近的pixel对这一点虚拟pixel进行双线性插值,得到这个“pixel”的值。

  1. 将bbox区域按输出要求的size进行等分,很可能等分后各顶点落不到真实的像素点上
  2. 没关系,在每个bin中再取固定的4个点(作者实验后发现取4效果较好),也就是图二右侧的蓝色点
  3. 针对每一个蓝点,距离它最近的4个真实像素点的值加权(双线性插值),求得这个蓝点的值
  4. 一个bin内会算出4个新值,在这些新值中取max,作为这个bin的输出值
  5. 最后就能得到2×2的输出