(十)RefineDet----2018CVPR论文解读

Single-Shot Refinement Neural Network for Object Detection

用于目标检测的单发细化神经网络

Abstract

对于物体检测,两阶段方法(例如,更快的R-CNN)已经实现了最高的准确度,而一阶段方法(例如,SSD)具有高效率的优点。为了继承两者的优点同时克服它们的缺点,在本文中,我们提出了一种新颖的基于单发的检测器,称为RefineDet,该检测器比两阶段方法具有更高的准确性,并且可以保持与一阶段方法相当的效率RefineDet由两个相互连接的模块组成,即锚点优化模块和对象检测模块。具体而言,前者旨在(1)过滤出负锚以减少分类器的搜索空间,(2)粗略调整锚的位置和大小以为后续回归器提供更好的初始化。后者模块将精炼的锚作为前者的输入可进一步改善回归并预测多类别标签。同时,我们设计了一个传递连接块来传递锚点细化模块中的特征,以预测对象检测模块中对象的位置,大小和类别标签。多任务丢失功能使我们能够以端到端的方式训练整个网络。在PASCAL VOC 2007,PASCAL VOC 2012和MS COCO上进行的大量实验表明,RefineDet可以高效实现最先进的检测精度。可以从https://github.com/sfzhang15/RefineDet获得代码。

1 Introduction

近年来,在深度神经网络(DNN)的框架下,对象检测取得了重大进展。当前的DNN检测器可分为两类:(1)两步法,包括[3、15、36、41],以及(2)一步法,包括[30,35]。在两阶段方法中,首先会生成一组稀疏的候选对象框,然后对其进行进一步分类和回归。两阶段方法在包括PASCAL VOC [8]和MS COCO [29]在内的几个具有挑战性的基准上均取得了最佳性能。

一阶段方法通过在位置,比例和纵横比上进行定期且密集的采样来检测对象。这种方法的主要优点是计算效率高。但是,其检测精度通常落后于两阶段方法,主要原因之一是类不平衡问题[28]。

一阶段方法中的一些最新方法旨在解决类不平衡问题,以提高检测精度。 Kong等 [24]使用卷积特征图上的客观先验约束来显着减少物体的搜索空间。 Lin等[28]通过重塑标准交叉熵损失,将训练重心放在稀疏的困难样本集上,并权衡分配给分类良好的样本的损失,从而解决了类别不平衡问题。 [53]设计了一个最大输出标记机制,以减少由于类不平衡而导致的误报。我们认为,当前最先进的两阶段方法,例如Faster R-CNN [36],R-FCN [5]和FPN [27]与一阶段方法相比具有以下三个优点:(1)使用两阶段结构和采样启发法来处理类不平衡问题; (2)使用两步级联来回归对象框参数; (3)使用两阶段特征来描述对象1。

在这项工作中,我们设计了一个名为RefineDet的新颖的对象检测框架,以继承两种方法(即一阶段和两阶段方法)的优点并克服它们的缺点。通过使用两个相互连接的模块(参见图1),即锚细化模块(ARM)和对象检测模块(ODM),它改进了一级方法的体系结构。具体而言,ARM旨在(1)识别和删除负锚以减少分类器的搜索空间,以及(2)粗略调整锚的位置和大小,以为后续回归器提供更好的初始化。 ODM将改进的锚点作为前者的输入,以进一步改善回归并预测多类标签。如图1所示,这两个相互连接的模块模仿了两级结构,因此继承了上述三个优点,可以高效地产生准确的检测结果。此外,我们设计了一个传输连接块(TCB),以传输ARM中的功能3来预测ODM中对象的位置,大小和类标签。多任务丢失功能使我们能够以端到端的方式训练整个网络。

在PASCAL VOC 2007,PASCAL VOC 2012和MS COCO基准测试中进行的大量实验表明,RefineDet的性能优于最新方法。具体而言,它通过VGG-16网络在VOC 2007和2012上实现了85.8%和86.8%的mAP。 。同时,它通过使用ResNet-101在MS COCO测试开发中达到41.8%的AP 4,优于一阶段和两阶段方法先前发布的最佳结果。此外,RefineDet具有时间效率,即在NVIDIA Titan X GPU上以40.2 FPS和24.1 FPS的速度运行,推断输入大小为320×320和512×512。

这项工作的主要贡献概述如下。 (1)我们介绍了一种新颖的一阶段对象检测框架,该框架由两个相互连接的模块,即ARM和ODM组成,这比两阶段方法具有更好的性能,同时保持了一个阶段的高效率,阶段方法。 (2)为确保有效性,我们设计了TCB,以转移ARM中的功能来处理更具挑战性的任务,即在ODM中预测准确的对象位置,大小和类标签。(3)RefineDet实现了以下最新状态:通用对象检测的最新结果(即PASCAL VOC 2007 [10],PASCAL VOC 2012 [11]和MS COCO [29])。

近年来,在深度神经网络(DNN)的框架下,对象检测取得了重大进展。当前的DNN检测器可分为两类:(1)两步法,包括[3、15、36、41],以及(2)一步法,包括[30,35]。在两阶段方法中,首先会生成一组稀疏的候选对象框,然后对其进行进一步分类和回归。两阶段方法在包括PASCAL VOC [8]和MS COCO [29]在内的几个具有挑战性的基准上均取得了最佳性能。

**一阶段方法通过在位置,比例和纵横比上进行定期且密集的采样来检测对象。这种方法的主要优点是计算效率高。但是,其检测精度通常落后于两阶段方法,主要原因之一是类不平衡问题[28]。
在这里插入图片描述

2 Related Work

早期物体探测器。早期的对象检测方法基于滑动窗口范式,该方法将手工制作的特征和分类器应用于密集的图像网格以查找对象。作为最成功的方法之一,Viola和Jones [47]使用Haar功能和AdaBoost来训练一系列用于人脸检测的级联分类器,以高效率实现令人满意的准确性。 DPM [12]是另一种流行的方法,它使用多尺度可变形零件模型的混合物来表示高度可变的对象类,并保持了PASCAL VOC [8]的多年经验。但是,随着深度卷积网络的到来,物体检测任务很快被基于CNN的检测器所支配,大致可以分为两类,即两阶段方法和一阶段方法。

两阶段方法。两阶段方法由两部分组成,其中第一部分(例如,选择性搜索[46],EdgeBoxes [55],DeepMask [32,33],RPN [36])生成一组稀疏的候选对象提议,以及第二个使用卷积网络确定准确的对象区域和相应的类别标签。值得注意的是,两阶段方法(例如R-CNN [16],SPPnet [18],Fast RCNN [15]到Faster R-CNN [36])在几个具有挑战性的数据集(例如PASCAL VOC 2012 [ 11]和MS COCO [29])。之后,提出了许多有效的技术来进一步提高性能,例如体系结构图[5,26,54],训练策略[41,48],上下文推理[1、14、40、50]和多层利用[ 3,25,27,42]。
在这里插入图片描述

一阶段方法。考虑到高效率,近一步法引起了越来越多的关注。 [38]提出了基于深度ConvNets的分类,定位和检测的OverFeat方法,该方法是从原始像素到最终类别的端到端训练。 Redmon等 [34]使用单个前馈卷积网络直接预测对象类别和位置,称为YOLO,这非常快。之后,提出了YOLOv2 [35]在多个方面进行改进,例如,在所有卷积层上添加批处理归一化,使用高分辨率分类器,将卷积层与锚定框一起使用以预测边界框,而不是完全连接的层,等等。刘等[30]提出了SSD方法,该方法将不同比例的锚点扩展到ConvNet中的多个层,并强制每一层集中于预测特定比例的对象。 DSSD [13]通过反卷积将额外的上下文引入SSD,以提高准确性。 DSOD [39]设计了一个有效的框架和一套原理,以遵循SSD的网络结构从零开始学习对象检测器。为了提高准确性,一些单阶段方法[24、28、53]旨在通过重新设计损失函数或分类策略来解决极端阶级失衡问题。尽管一级检测器取得了良好的进展,但其准确度仍落后于二级检测器。

3 Network Architecture

请参考图1所示的整体网络架构。与SSD [30]相似,RefineDet基于前馈卷积网络,该网络产生固定数量的边界框,其得分指示这些框中存在不同类别的对象,然后进行非最大抑制以产生最终结果。 RefineDet由两个相互连接的模块即ARM和ODM组成。 **ARM的目标是消除负面的锚点,以减少分类器的搜索空间,并粗略调整锚点的位置和大小,以便为后续回归器提供更好的初始化,而ODM旨在回归准确的对象位置并预测基于在精致的锚点上。**通过删除分类层并添加两个基本网络(即在ImageNet [37]上预训练的VGG-16 [43]和ResNet-101 [19])的一些辅助结构来满足我们的需求,即可构建ARM。 **ODM由TCB的输出和预测层(即具有3×3内核大小的卷积层)组成,这些预测层生成对象类别的得分以及相对于精化锚框坐标的形状偏移。**下面说明RefineDet中的三个核心组件,即(1)传输连接块(TCB),将功能从ARM转换为ODM以进行检测; (2)两步级联回归,准确地回归物体的位置和大小; (3)负面锚过滤,尽早拒绝分类良好的负面锚,并减轻不平衡问题。

传输连接块。为了在ARM和ODM之间建立链接,我们引入了TCB,以将来自ARM的不同层的功能转换为ODM所需的形式,以便ODM可以共享来自ARM的功能。值得注意的是,在ARM中,我们仅使用与锚点关联的要素地图上的TCB。 TCB的另一个功能是通过将高级功能添加到传输的功能中来集成大规模上下文[13,27],以提高检测精度。为了匹配它们之间的尺寸,我们使用了去卷积操作来放大高级特征图,并以逐元素的方式对其进行求和。然后,在求和后添加卷积层,以确保检测特征的可分辨性。 TCB的架构如图2所示。

两步级联回归。当前的一阶段方法[13,24,30]依赖于基于具有不同比例的各种特征层的一步回归来预测对象的位置和大小,在某些挑战性场景中,尤其是对于小型对象而言,这是准确的。为此,我们提出了一种两步级联的回归策略来回归对象的位置和大小。也就是说,我们使用ARM首先调整锚点的位置和大小,以便为ODM中的回归提供更好的初始化。具体来说,我们将n个锚点框与特征图上每个规则划分的像元相关联。每个锚点框相对于其对应单元格的初始位置是固定的。在每个特征图单元格上,我们预测精制锚点框相对于原始平铺锚点的四个偏移量和两个置信度得分,指示这些框中存在前景对象。因此,我们可以在每个特征图单元处生成n个精致的锚框。

在获得精炼的锚框之后,我们将它们传递给ODM中的相应特征图,以进一步生成对象类别以及准确的对象位置和大小,如图1所示。ARM和ODM中的相应特征图具有相同的维度我们计算c类得分和对象相对于精化锚框的四个准确偏移量,为每个精化锚框产生c + 4个输出以完成检测任务。此过程类似于SSD [30]中使用的默认框。但是,与SSD [30]直接使用规则平铺的默认框进行检测相反,RefineDet使用两步策略,即ARM生成精化的锚框,ODM将精化的锚框作为输入进行进一步检测,导致更准确的检测结果,尤其是对于小物体。

负锚过滤。为了及早拒绝分类良好的负锚并减轻不平衡问题,我们设计了一个负锚过滤机制。具体来说,在训练阶段,对于精化的锚框,如果其负置信度大于预设阈值θ(即,根据经验设置θ= 0.99),我们将在训练ODM时将其丢弃。硬负锚盒和精制正锚盒来训练ODM。同时,在推断阶段,如果为精炼的锚框分配了大于θ的负置信度,则将其丢弃在ODM中进行检测。

4 Training and Inference

数据扩充。我们使用[30]中提出的几种数据增强策略来构建一个健壮的模型以适应对象的变化。也就是说,我们随机扩展和裁剪原始训练图像,并附加其他随机光度失真[20]并翻转以生成训练样本。有关更多详细信息,请参见[30]。

骨干网。我们在我们的RefineDet中使用VGG-16 [43]和ResNet-101 [19]作为骨干网,这些网络在ILSVRC CLS-LOC数据集上进行了预训练[37]。值得注意的是,RefineDet还可以在其他经过预训练的网络上工作,例如Inception V2 [22],Inception ResNet [44]和ResNeXt-101 [49]。类似于DeepLab-LargeFOV [4],我们通过子采样参数将VGG-16的fc6和fc7转换为卷积层conv fc6和conv fc7。由于conv4 3和conv5 3与其他层相比具有不同的特征尺度,因此我们使用L2归一化[31]将conv4 3和conv5 3中的特征范数缩放为10和8,然后在反向传播期间学习尺度。同时,为了捕获高级信息并以多个尺度驱动对象检测,我们还向截断的VGG-16的末尾添加了两个额外的卷积层(即conv6 1和conv6 2)和一个额外的残差块(即res6) )分别到截断的ResNet-101的末尾。

锚设计和匹配。要处理不同比例的对象,我们为VGG-16和ResNet-101 5选择四个总步幅分别为8、16、32和64像素的要素图层,并与几种不同比例的锚点关联以进行预测。每个要素图层都与一个特定比例的锚关联(即比例是对应图层总步幅大小的4倍)和三个纵横比(即0.5、1.0和2.0)。我们在[53]中遵循不同层上的锚缩放比例设计,以确保不同比例的锚在图像上具有相同的拼贴密度[51,52]。同时,在训练阶段,我们根据jaccard重叠[7]确定锚点和地面真值框之间的对应关系,并相应地端对端训练整个网络。具体来说,我们首先将每个地面真值与锚点匹配。具有最佳重叠分数的方框,然后将锚点框与重叠度高于0.5的任何地面真实情况进行匹配。

硬负开采。匹配步骤之后,大多数锚框都是负数,即使对于ODM,对于某些简单的负锚也被ARM拒绝。类似于SSD [30],我们使用硬负挖矿来缓解极端的前景-背景类别失衡,即,我们选择一些具有最高损失值的负锚框,以使正负之比低于3:1,而不是使用所有负面锚点或在训练中随机选择负面锚点。

损失函数。 RefineDet的损失函数由两部分组成,即ARM中的损失和ODM中的损失。对于ARM,我们为每个锚点分配一个二进制类标签(是否为对象),并回归其位置和同时调整大小以获取精致的锚点。之后,我们将负置信度小于阈值的精化锚传递给ODM,以进一步预测对象类别以及准确的对象位置和大小。通过这些定义,我们将损失函数定义为:
L ( { p i } , { x i } , { c i } , { t i } ) = 1 N i m ( i L b ( p i , [ l i 1 ] ) + i [ l i 1 ] L r ( x i , g i ) ) + f N c l s ( i L m ( c i , l i ) + i [ l i 1 ] L r ( t i , g i ) ) \begin{aligned} &\mathcal{L}\left(\left\{p_{i}\right\},\left\{x_{i}\right\},\left\{c_{i}\right\},\left\{t_{i}\right\}\right)=\frac{1}{N_{i} m}\left(\sum_{i} \mathcal{L}_{b}\left(p_{i},\left[l_{i}^{*} \geq 1\right]\right)\right.\\ &\left.+\sum_{i}\left[l_{i}^{*} \geq 1\right] \mathcal{L}_{\mathrm{r}}\left(x_{i}, g_{i}^{*}\right)\right)+\frac{\mathrm{f}^{*}}{N_{\mathrm{cls}}}\left(\sum_{i} \mathcal{L}_{\mathrm{m}}\left(c_{i}, l_{i}^{*}\right)\right.\\ &\left.+\sum_{i}\left[l_{i}^{*} \geq 1\right] \mathcal{L}_{r}\left(t_{i}, g_{i}^{*}\right)\right) \end{aligned}
其中,i是微型批次中锚点的索引,l ∗ i是锚点i的地面真实等级标签,g ∗ i是锚点i的地面真实位置和大小。 p i和x i是作为对象的锚点i的预测置信度以及ARM中锚点i的精确坐标。 c i和t i是ODM中的预测对象类和边界框的坐标。 N arm和N odm分别是ARM和ODM中正锚的数目。二进制分类损失L b是两类(对象与非对象)的交叉熵/对数损失,多分类损失L m是多类置信度的softmax损失。与Fast R-CNN [15]类似,我们使用平滑L1损失作为回归损失L r。当条件为真时,艾弗森括号指示符功能[l ∗ i≥1]输出1,即l ∗ i≥1(锚点不是负数),否则输出0。因此,[l * i≥1] L r表示负锚点的回归损失被忽略。值得注意的是,如果N arm = 0,则将L b(p i,[l ∗i≥1])设为0,将L r(x i,g ∗ i)设为0。如果N odm = 0,则将L m(c i,l ∗ i)设置为0,并将L r(t i,g ∗ i)设置为0。

优化。如上所述,我们的RefineDet方法中的骨干网(例如VGG-16和ResNet-101)已在ILSVRC CLS-LOC数据集上进行了预训练[37]。我们使用“ xavier”方法[17]随机初始化基于VGG-16的RefineDet的两个额外添加的卷积层(即conv6 1和conv6 2)中的参数,并从零均值高斯分布中绘制参数,其中基于ResNet-101的RefineDet的额外残差块(即res6)的标准偏差0.01。我们在训练中将默认批次大小设置为32。然后,使用具有0.9动量和0.0005权重衰减的SGD对整个网络进行微调。我们将初始学习速率设置为10 -3,并对不同的数据集使用稍微不同的学习速率衰减策略,这将在后面详细介绍。

推理。在推理阶段,ARM首先过滤出具有大于阈值θ的负置信度得分的规则平铺锚,然后细化其余锚的位置和大小。之后,ODM会接管这些精致的锚点,并输出每个图像的前400个高置信度检测结果。最后,我们应用非最大抑制,每类的jaccard重叠为0.45,并保留每个图像的前200个高置信度检测,以产生最终的检测结果。

5 Experiments

在三个数据集上进行了实验:PASCAL VOC 2007,PASCAL VOC 2012和MS COCO。 PASCAL VOC和MS COCO数据集分别包含20和80个对象类。 PASCAL VOC中的类是MS COCO中的类的子集。 我们在Caffe中实现RefineDet [23]。 所有培训和测试代码以及受过培训的模型都可以在https://github.com/sfzhang15/RefineDet上获得。

5.1. PASCAL VOC 2007

所有模型都在VOC 2007和VOC 2012火车valset上进行训练,并在VOC2007 testset上进行了测试。对于前80k迭代,我们将学习速率设置为10 -3,然后分别将其衰减为10 -4和10 -5,以分别训练另外20k和20k迭代。我们在训练中使用默认的批次大小32,并且仅对PASCAL VOC数据集(包括VOC 2007和VOC 2012)上的所有实验使用VGG-16作为骨干网。

我们将RefineDet 6与表1中的最新检测器进行了比较。在低尺寸输入(即320×320)的情况下,RefineDet产生了80.0%的mAP,没有钟声,这是第一种实现80%以上的mAP的方法这样小的输入图像,比几个现代反对者要好得多。通过使用更大的输入尺寸512×512,RefineDet达到了81.8%的mAP,超过了RON384 [24],SSD513 [13],DSSD513 [13]等所有一阶段方法。与两阶段方法RefineDet512相比除了CoupleNet [54]是基于ResNet-101的,并且比我们的RefineDet512使用更大的输入大小(即,约1000×600),其性能要优于大多数。如[21]中指出的,输入大小会显着影响检测精度。原因是高分辨率输入使检测器清楚地“看到”小物体,从而增加了成功的检测率。为了减少输入大小的影响以进行公平的比较,我们使用多尺度测试策略来评估RefineDet,达到83.1%(RefineDet320 +)和83.8%(RefineDet512 +)的mAP,这比当前的状态要好得多。艺术方法。

5.1.1 Run Time Performance

在这里插入图片描述

我们在表1的第五栏中介绍了RefineDet的推理速度和最新技术。在配备NVIDIA Titan X,CUDA 8.0和cuDNN v6的计算机上,以批处理大小1评估了该速度。如表1所示,我们发现RefineDet在输入大小分别为320×320和512×512的情况下以24.8ms(40.3 FPS)和41.5ms(24.1 FPS)处理图像。据我们所知,RefineDet是第一种在PASCAL VOC 2007上达到80%mAP以上的检测精度的实时方法。与SSD,RON,DSSD和DSOD相比,RefineDet在特征图上关联的锚框更少(例如, SSD512 ∗ [30]中有24564个锚定框,而RefineDet512中是16320个锚定框)。但是,主要由于两个相互连接的模块(例如,两步回归)的设计,RefineDet仍然可以高效地获得最高的精度,这使RefineDet能够适应不同的对象比例和纵横比。同时,只有YOLO和SSD300 *比我们的RefineDet320快一点,但它们的准确度却比我们的RefineDet320低16.6%和2.5%。总而言之,RefineDet在准确性和速度之间取得了最佳的平衡。

5.1.2 Ablation Study

为了证明RefineDet中不同组件的有效性,我们构造了四个变体并在VOC 2007上对其进行了评估,如表3所示。具体地说,为了公平比较,我们在评估中使用相同的参数设置和输入大小(320×320)。所有模型都在VOC 2007和VOC 2012训练集上进行了训练,并在VOC 2007测试集上进行了测试。

负锚过滤。为了证明负锚滤波的有效性,我们在训练和测试中都将锚的置信度阈值θ设置为负1.0。在这种情况下,所有改进的锚点都将发送到ODM进行检测。 RefineDet的其他部分保持不变。移除负面的锚点过滤功能会导致mAP下降0.5%(即80.0%对79.5%)。原因是这些分类良好的负面锚大多数将在训练期间被滤除,从而在一定程度上解决了班级失衡的问题。

两步级联回归。为了验证两步级联回归的有效性,我们通过直接使用规则铺砌的锚点而不是从ARM提取的锚点来重新设计网络结构(请参阅表3的第四列)。如表3所示,我们发现mAP从79.5%降低到77.3%。这种急剧下降(即2.2%)表明,两步锚定级联回归显着有助于提升性能。
在这里插入图片描述
在这里插入图片描述
传输连接块。我们通过在RefineDet中剪切TCB并重新定义ARM中的损失函数来构建网络,以直接检测多类对象(就像SSD一样),以展示TCB的效果。该模型的检测精度列于表3的第五列。我们比较了表3的第四列和第五列的结果(分别为77.3%和76.2%),发现TCB将mAP提高了1.1%。主要原因是该模型可以继承ARM的判别功能,并且可以使用TCB集成大规模上下文信息以提高检测精度。

5.2. PASCAL VOC 2012

根据VOC 2012的协议,我们将RefineDet的检测结果提交给公共测试服务器进行评估。我们使用VOC 2007训练集和测试集加上VOC 2012训练集(21,503张图像)进行训练,并在VOC 2012测试集上进行测试(10,991张图像)。我们在训练中使用默认的批次大小32。同时,我们在前160k次迭代中将学习速率设置为10 -3,并在另外40k次和40k次迭代中将学习速率衰减为10 -4和10 -5。

表1显示了所提出的RefineDet算法的准确性以及最新方法。在输入大小为320×320的方法中,RefineDet320获得最高的78.1%mAP,甚至比使用大约1000×600输入大小的大多数两阶段方法(例如,更快的R-CNN [70.4%mAP] [ 36]和77.6%的m-AP R-FCN [5]。使用512×512的输入大小,RefineDet可将mAP提升至80.1%,超越了所有的单阶段方法,仅略低于CoupleNet [54](即80.4%)。 CoupleNet使用ResNet-101作为具有1000×600输入大小的基础网络。为了公平地比较,减少输入大小的影响,我们还使用多尺度测试来评估RefineDet,并获得最新的mAP分别为82.7%(RefineDet320 +)和83.5%(RefineDet512 +)。
在这里插入图片描述

5.3. MS COCO

除了PASCAL VOC,我们还在MS COCO上评估RefineDet [29]。与PASCAL VOC不同,使用ResNet-101的检测方法始终比使用MS COCO上的VGG-16的检测方法具有更好的性能。因此,我们还报告了基于ResNet-101的RefineDet的结果。遵循MS COCO中的协议,我们使用trainval35k集[1]进行训练并评估来自test-dev评估服务器的结果。我们在训练7中将批处理大小设置为32,并在前280k迭代中以10 -3的学习率训练模型,然后在另外80k和40k迭代中分别以10 -4和10 -5训练模型。

表7显示了MS COCO测试开发集上的结果。带有VGG-16的RefineDet320产生29.4%的AP,比基于VGG-16的所有其他方法(例如SSD512 * [30]和OHEM ++ [41])要好。通过使用较大的输入大小(即512×512),可以将RefineDet的精度提高到33.0%,这比一些现代的对象检测器(例如,更快的R-CNN [36]和SSD512 * [30])要好得多。 ,使用ResNet-101可以进一步提高RefineDet的性能,即带有ResNet-101的RefineDet320达到32.0%的AP和RefineDet512达到36.4%的AP,除快速R-CNN w TDM [42],可变形的R-FCN以外,大多数检测方法都超过了[6],RetinaNet800 [28],umd det [2]和G-RMI [21]。与我们的RefineDet(即320×320和512×512)相比,所有这些方法在训练和测试时都使用更大的输入图像(即1000×600或800×800)。与PASCAL VOC相似,我们还在表7中报告了RefineDet的多尺度测试AP结果以进行公平比较,即35.2%(带有VGG-16的RefineDet320 +),37.6%(带有VGG-16的RefineDet512 +),38.6%(重新-fineDet320 +(带ResNet-101)和41.8%(RefineDet512 +,带ResNet-101)。 RefineDet的最佳性能是41.8%,这是最新技术,超过了所有已发布的两阶段和一阶段方法。尽管第二好的检测器G-RMI [21]集成了五个Faster R-CNN模型,但与使用单个模型的RefineDet相比,它的AP仍然低0.2%。与排名第三和第四的最佳检测器(即umd det [2]和RetinaNet800 [28])相比,RefineDet产生的AP分别高出1.0%和2.7%。另外,主要的贡献是:RetinaNet 800的焦点损失是我们方法的补充。我们相信可以在RefineNet中使用它来进一步提高性能。

5.4. From MS COCO to PASCAL VOC

我们研究了MS COCO数据集如何帮助提高PASCAL VOC的检测精度。由于PASCAL VOC中的对象类别是MS COCO的子集,因此我们通过对参数进行二次采样来直接微调在MS COCO上预训练的检测模型,从而在VOC2007测试集上达到84.0%的mAP(RefineDet320)和85.2%的mAP(RefineDet512),表4所示为mAP(RefineDet 320)和82.7%mAP(RefineDet512),如表4所示。使用多尺度测试后,检测精度分别提高到85.6%,85.8%,86.0%和86.8。 %,分别。如表4所示,使用MS COCO和PASCAL VOC中的训练数据,我们的RefineDet在VOC 2007和VOC 2012上均获得最高的mAP得分。最重要的是,我们基于VGG-16的单一模型RefineNet512 +在该模型上排名前5 VOC 2012排行榜(请参阅[9]),这是所有一阶段方法中最高的准确性。其他取得更好结果的两阶段方法是基于更深层的网络(例如ResNet-101 [19]和ResNeXt-101 [49])或使用集成机制。

6 Conclusions

在本文中,**我们提出了一种基于单次细化神经网络的检测器,该检测器由两个相互连接的模块即ARM和ODM组成。 ARM的目的是过滤出负锚,以减少分类器的搜索空间,并粗略调整锚的位置和大小,以为后续回归器提供更好的初始化,而ODM则将经过精炼的锚作为前ARM的输入进行回归准确的对象位置和大小,并预测相应的多类标签。整个网络以端到端的方式进行多任务丢失训练。**我们在PASCAL VOC 2007,PASCAL VOC 2012和MS COCO数据集上进行了一些实验,以证明RefineDet可以高效实现最先进的检测精度。将来,我们计划使用RefineDet来检测其他特定种类的对象,例如行人,车辆和面部,并在RefineDet中引入注意机制以进一步提高性能。

RefineDet_vgg主干网代码:
(转自https://zhuanlan.zhihu.com/p/50917804)

import os
import torch
import torch.nn as nn
import torch.nn.functional as F

from layers import *

# vgg主干网结构
vgg_base = {
    '320': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'C', 512, 512, 512, 'M', 512, 512, 512],
}

# This function is derived from torchvision VGG make_layers()
# https://github.com/pytorch/vision/blob/master/torchvision/models/vgg.py
# vgg的backbone加载,未灌入参数
def vgg(cfg, i, batch_norm=False):
    layers = []
    in_channels = i   # 初始化的输入通道数,image dim = 3
    for v in cfg:     # cfg就是base中的取值
        if v == 'M':
            layers += [nn.MaxPool2d(kernel_size=2, stride=2)]                   # 'M'对应pooling
        elif v == 'C':
            layers += [nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True)]   # 'C'也对应pooling,做了feature map尺度规整
        else:
            conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
            if batch_norm:
                layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)]
            else:
                layers += [conv2d, nn.ReLU(inplace=True)]
            in_channels = v

    # 后接vgg其它层操作
    pool5 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
    conv6 = nn.Conv2d(512, 1024, kernel_size=3, padding=6, dilation=6)   # 这个就类似global pooling
    conv7 = nn.Conv2d(1024, 1024, kernel_size=1)
    layers += [pool5, conv6, nn.ReLU(inplace=True), conv7, nn.ReLU(inplace=True)]

    return layers

# refinedet网络结构定义
class RefineSSD(nn.Module):
    """Single Shot Multibox Architecture
    The network is composed of a base VGG network followed by the
    added multibox conv layers.  Each multibox layer branches into
        1) conv2d for class conf scores
        2) conv2d for localization predictions
        3) associated priorbox layer to produce default bounding
           boxes specific to the layer's feature map size.
    See: https://arxiv.org/pdf/1512.02325.pdf for more details.

    Args:
        phase: (string) Can be "test" or "train"
        base: VGG16 layers for input, size of either 300 or 500
        extras: extra layers that feed to multibox loc and conf layers
        head: "multibox head" consists of loc and conf conv layers
    """

    def __init__(self, size, num_classes, use_refine=False):
        super(RefineSSD, self).__init__()
        self.num_classes = num_classes
        self.size = size
        self.use_refine = use_refine

        # SSD network,backbone
        self.base = nn.ModuleList(vgg(vgg_base['320'], 3))

        # Layer learns to scale the l2 normalized features from conv4_3
        self.L2Norm_4_3 = L2Norm(512, 10)    # 参照parsenet,与论文稍微有点出入,论文中是在conv3_3、conv4_3上设置
        self.L2Norm_5_3 = L2Norm(512, 8)     # 参照parsenet

        # 接在vgg base后的新增层
        self.extras = nn.Sequential(nn.Conv2d(1024, 256, kernel_size=1, stride=1, padding=0), nn.ReLU(inplace=True), \
                                    nn.Conv2d(256, 512, kernel_size=3, stride=2, padding=1), nn.ReLU(inplace=True))

        # arm, oup_channel为何是12? 3 x 4, 对应3个anchor
        if use_refine:
            self.arm_loc = nn.ModuleList([nn.Conv2d(512, 12, kernel_size=3, stride=1, padding=1), \
                                          nn.Conv2d(512, 12, kernel_size=3, stride=1, padding=1), \
                                          nn.Conv2d(1024, 12, kernel_size=3, stride=1, padding=1), \
                                          nn.Conv2d(512, 12, kernel_size=3, stride=1, padding=1), \
                                          ])
            self.arm_conf = nn.ModuleList([nn.Conv2d(512, 6, kernel_size=3, stride=1, padding=1), \
                                           nn.Conv2d(512, 6, kernel_size=3, stride=1, padding=1), \
                                           nn.Conv2d(1024, 6, kernel_size=3, stride=1, padding=1), \
                                           nn.Conv2d(512, 6, kernel_size=3, stride=1, padding=1), \
                                           ])   # 类别,objectness,3 x 2

        # odm
        self.odm_loc = nn.ModuleList([nn.Conv2d(256, 12, kernel_size=3, stride=1, padding=1), \
                                      nn.Conv2d(256, 12, kernel_size=3, stride=1, padding=1), \
                                      nn.Conv2d(256, 12, kernel_size=3, stride=1, padding=1), \
                                      nn.Conv2d(256, 12, kernel_size=3, stride=1, padding=1), \
                                      ])
        self.odm_conf = nn.ModuleList([nn.Conv2d(256, 3*num_classes, kernel_size=3, stride=1, padding=1), \
                                       nn.Conv2d(256, 3*num_classes, kernel_size=3, stride=1, padding=1), \
                                       nn.Conv2d(256, 3*num_classes, kernel_size=3, stride=1, padding=1), \
                                       nn.Conv2d(256, 3*num_classes, kernel_size=3, stride=1, padding=1), \
                                       ])       # 类别,objectness,3 x num_classes

        # arm操作的最高层feature map,无需结合更高层feature map
        self.last_layer_trans = nn.Sequential(nn.Conv2d(512, 256, kernel_size=3, stride=1, padding=1),
                                              nn.ReLU(inplace=True),
                                              nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1),
                                              nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1))

        # 对应fig 2中eltw-sum上半部分
        self.trans_layers = nn.ModuleList([nn.Sequential(nn.Conv2d(512, 256, kernel_size=3, stride=1, padding=1),
                                                         nn.ReLU(inplace=True),
                                                         nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1)), \
                                           nn.Sequential(nn.Conv2d(512, 256, kernel_size=3, stride=1, padding=1),
                                                         nn.ReLU(inplace=True),
                                                         nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1)), \
                                           nn.Sequential(nn.Conv2d(1024, 256, kernel_size=3, stride=1, padding=1),
                                                         nn.ReLU(inplace=True),
                                                         nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1)), \
                                           ])

        # 对应fig 2中eltw-sum右半部分,相当于feature map上采样操作,结合fig 1、2,只需做三次上采样
        self.up_layers = nn.ModuleList([nn.ConvTranspose2d(256, 256, kernel_size=2, stride=2, padding=0),
                                        nn.ConvTranspose2d(256, 256, kernel_size=2, stride=2, padding=0),
                                        nn.ConvTranspose2d(256, 256, kernel_size=2, stride=2, padding=0), ])

        # 对应fig 2中eltw-sum下半部分
        self.latent_layrs = nn.ModuleList([nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1),
                                           nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1),
                                           nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1),
                                           ])

        self.softmax = nn.Softmax()

    def forward(self, x, test=False):
        """Applies network layers and ops on input image(s) x.

        Args:
            x: input image or batch of images. Shape: [batch,3*batch,300,300].

        Return:
            Depending on phase:
            test:
                Variable(tensor) of output class label predictions,
                confidence score, and corresponding location predictions for
                each object detected. Shape: [batch,topk,7]

            train:
                list of concat outputs from:
                    1: confidence layers, Shape: [batch*num_priors,num_classes]
                    2: localization layers, Shape: [batch,num_priors*4]
                    3: priorbox layers, Shape: [2,num_priors*4]
        """
        arm_sources = list()
        arm_loc_list = list()
        arm_conf_list = list()
        obm_loc_list = list()
        obm_conf_list = list()
        obm_sources = list()

        # apply vgg up to conv4_3 relu
        for k in range(23):
            x = self.base[k](x)
        s = self.L2Norm_4_3(x)    # conv4_3上牵出一个arm检测分支
        arm_sources.append(s)

        # apply vgg up to conv5_3
        for k in range(23, 30):
            x = self.base[k](x)
        s = self.L2Norm_5_3(x)    # conv5_3上牵出一个arm检测分支
        arm_sources.append(s)

        # apply vgg up to fc7
        for k in range(30, len(self.base)):
            x = self.base[k](x)
        arm_sources.append(x)     # vgg-reduced版本下,全卷积的fc7上牵出一个arm检测分支

        # conv6_2,接在vgg base后的新增层
        x = self.extras(x)
        arm_sources.append(x)     # vgg base后新增的extras后,全卷积的conv6_2上牵出一个arm检测分支,相当于是最后一个conv layer

        # apply multibox head to arm branch, arm分支的cls + loc预测,刚好对应arm_sources新增的4个分支
        if self.use_refine:
            for (x, l, c) in zip(arm_sources, self.arm_loc, self.arm_conf):
                arm_loc_list.append(l(x).permute(0, 2, 3, 1).contiguous())
                arm_conf_list.append(c(x).permute(0, 2, 3, 1).contiguous())
            arm_loc = torch.cat([o.view(o.size(0), -1) for o in arm_loc_list], 1)      # 所有分支上的loc结果,做一个concate聚合
            arm_conf = torch.cat([o.view(o.size(0), -1) for o in arm_conf_list], 1)    # 所有分支上的cls结果,做一个concate聚合

        # refinedet最高层,也即全卷积的conv6_2上牵出来的tcb模块,对应论文fig 1中最后一个tcb,无需高层feature map,直接类似fig 2的上半部分操作即可
        x = self.last_layer_trans(x)
        obm_sources.append(x)              # odm预测分支

        # get transformed layers,特征层转换,对应fig 2中eltw-sum的上半部分,也是从arm_sources分支上牵出来的
        trans_layer_list = list()
        for (x_t, t) in zip(arm_sources, self.trans_layers):
            trans_layer_list.append(t(x_t))

        # fpn module
        trans_layer_list.reverse()    # 因为是fpn-style的top-down结构,trans_layer_list掉换个顺序先
        arm_sources.reverse()         # arm_sources同样调换顺序

        # 整个操作其实很简单,就是tcb模块,[u(x) + t]对应fig 2上、右半部分,[l]对应fig 2下半部分,再加了两个relu操作,
        for (t, u, l) in zip(trans_layer_list, self.up_layers, self.latent_layrs):
            x = F.relu(l(F.relu(u(x) + t, inplace=True)), inplace=True)
            obm_sources.append(x)

        obm_sources.reverse()   # odm构建好了,从top-down调换成down-top结构,方便预测
        for (x, l, c) in zip(obm_sources, self.odm_loc, self.odm_conf):    # odm分支的cls + loc预测,与arm分支刚好对应
            obm_loc_list.append(l(x).permute(0, 2, 3, 1).contiguous())
            obm_conf_list.append(c(x).permute(0, 2, 3, 1).contiguous())
        obm_loc = torch.cat([o.view(o.size(0), -1) for o in obm_loc_list], 1)       # 所有分支上的loc结果,做一个concate聚合
        obm_conf = torch.cat([o.view(o.size(0), -1) for o in obm_conf_list], 1)     # 所有分支上的cls结果,做一个concate聚合

        # apply multibox head to source layers
        if test:
            if self.use_refine:
                output = (
                    arm_loc.view(arm_loc.size(0), -1, 4),               # arm, loc preds
                    self.softmax(arm_conf.view(-1, 2)),                 # arm, conf preds, softmax
                    obm_loc.view(obm_loc.size(0), -1, 4),               # odm, loc preds
                    self.softmax(obm_conf.view(-1, self.num_classes)),  # odm, conf preds, softmax
                )
            else:
                output = (
                    obm_loc.view(obm_loc.size(0), -1, 4),               # odm, loc preds
                    self.softmax(obm_conf.view(-1, self.num_classes)),  # odm, conf preds
                )
        else:
            if self.use_refine:
                output = (
                    arm_loc.view(arm_loc.size(0), -1, 4),                   # arm, loc preds
                    arm_conf.view(arm_conf.size(0), -1, 2),                 # arm, conf preds
                    obm_loc.view(obm_loc.size(0), -1, 4),                   # odm, loc preds
                    obm_conf.view(obm_conf.size(0), -1, self.num_classes),  # odm, conf preds
                )
            else:
                output = (
                    obm_loc.view(obm_loc.size(0), -1, 4),                   # odm, loc preds
                    obm_conf.view(obm_conf.size(0), -1, self.num_classes),  # odm, conf preds
                )

        return output

    # refinedet有两种训练方式:直接使用vgg模型参数、基于vgg继续训练,还有一种就是在已有refinedet上继续finetune
    # 这里对应着模型加载和参数赋值操作
    def load_weights(self, base_file):
        other, ext = os.path.splitext(base_file)
        if ext == '.pkl' or '.pth':
            print('Loading weights into state dict...')
            self.load_state_dict(torch.load(base_file, map_location=lambda storage, loc: storage))
            print('Finished!')
        else:
            print('Sorry only .pth and .pkl files supported.')


# Refinedet网络构建
def build_net(size=320, num_classes=21, use_refine=False):
    if size != 320:
        print("Error: Sorry only SSD300 and SSD512 is supported currently!")
        return

    return RefineSSD(size, num_classes=num_classes, use_refine=use_refine)
发布了66 篇原创文章 · 获赞 23 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_18315295/article/details/104162699