FasterRCNN系列介绍

首页 编程分享 EXPERIENCE 正文

算法之名 转载 编程分享 2022-01-23 22:47:03

简介 RCNN网络 RCNN网络是Ross B. Girshick大神2014年提出的,是第一个基于深度学习的目标检测算法,是基于深度特征的。它跟传统的目标识别不同的就是使用深度学习的特...


RCNN网络

RCNN网络是Ross B. Girshick大神2014年提出的,是第一个基于深度学习的目标检测算法,是基于深度特征的。它跟传统的目标识别不同的就是使用深度学习的特征代替了传统的基于低层次的颜色、纹理的特征提取。

因为RCNN并没有对整个传统目标检测的框架进行改进或者优化,因此RCNN网络依然存在传统目标检测算法所存在的问题,如检测速度或者效率低下,检测精度虽然相对于原先的目标检测算法有了一定的改善,但是依然难以满足实际的需要。这也是后续算法重要的改进方向。

RCNN网络是如何使用深度学习特征的。

  1. RCNN在使用深度学习特征的时候并没有直接使用卷积神经网去进行训练,而是使用了预训练模型AlexNet来训练分类模型,而这个模型是在ImageNet上训练好的模型。
  2. 在RCNN这个模型的分类任务上进行一个模型的fine-tuning(迁移)。在这个迁移的过程中主要将FC层去掉。因为AlexNet是面向1000个分类任务进行分类的,而在目标检测或者VOC数据集上,目标数为20,加上背景是21类。因此需要将FC层去掉,因为FC层的参数不同,只保留了主干网络层,也就是AlexNet卷积层的这些特征,利用这样的特征来进行模型进一步的训练。针对于CNN提取之后的特征,RCNN网络依然采用SVM分类器以及Bounding box回归的方式来得到最终的候选区域的类别和定位的位置信息。
  3. 在提取候选区域的时候采用选择性搜索策略而不是采用滑动窗口的策略。在采用滑动窗口策略一方面要考虑滑动窗口步长以及滑动窗口的大小,此时如果采用滑动窗口的策略,往往意味着要计算非常多的候选区域,而计算候选区域数量的增大,则直接带来了计算性能上的下降。整个目标检测的流程耗时会变得非常大。RCNN采用了选择性搜索的策略来提取候选框。
    1. 生成区域集R,具体参考论文《Efficient Graph-Based Image Segmentation》,采用区域分割的模型,进行区域分割之后就能得到一系列分割好的区域,这些区域往往是图像中某些可能的物体。
    2. 计算区域集R里每个相邻区域的相似度S={s1,s2,...}
    3. 找出相似度最高的两个区域,将其合并为新集,添加进R,减少生成的区域的个数。如何判断两个区域的相似度,通常也是采用颜色、纹理这样一些低层次的计算机视觉中的一些特征。
    4. 从S中移除所有与3中有关的子集
    5. 计算新集与所有子集的相似度
    6. 跳至3,直至S为空,没有相似区域了,就终止整个迭代的过程。最终得到的子集就是我们需要提取的候选框的集合。
  4. 训练SVM分类器:每个类别对应一个SVM,有关SVM的内容可以参考机器学习算法整理(三) 中的支撑向量机SVM
  5. 回归器精修候选框位置:利用线性回归模型判定框的准确度

对于一个候选区域,可能会存在上图中给出的一些例子。线性回归模型将会对dx、dy、dw、dh这四个值进行回归。这四个值,我们可以将其理解为差分的值,差分值表示了当前的候选框的偏移。如果候选框能够完整且非常规整的检测到候选目标,那么此时所有的偏移值都是0。如果候选框往一个方面偏了,那么此时dx就会预测出来相应的偏差的值,比如上图中中间的猫预测出来dx值为0.25。如果候选框大了,检测的目标小,就意味着预测的框的dw或者dh会存在一定的冗余。比如上图中第三幅图中的猫的边框宽度是明显大于候选目标的,则dw预测出来的为-0.125表示宽度要减少0.125,而高度是刚好的,则dh为0。

最终通过RCNN就能够得到一系列的候选框,再通过NMS算法来对候选框进行进一步的筛选和合并,最终得到目标检测的输出。

  • RCNN缺点
  1. 候选框选择算法耗时严重,很难做到实时计算。
  2. 重叠区域特征重复计算
  3. 分步骤进行,过程繁琐。这里我们最少要分别训练三个部分——CNN,SVM,Reg。过程相对于一个端到端的网络模型就会变得非常复杂。整个pipleline在搭建时也会存在一个非常复杂的过程,比如在训练SVM的时候就需要专门去生成SVM的样本,但是我们生成的样本可能同我们利用CNN卷积特征提取出来的样本会存在一定的差异。这个时候就会存在一个分布上的问题。我们在采用分步骤再采用独立模型训练的时候,将整个模型拼到一起的时候,可能就会存在性能上的一些损失。一个比较好的做法就是将整个pipleline搭起来,将CNN的输出作为SVM的输入,将SVM的输出作为Reg回归网络的输入。此时我们就需要训练三种不同的模型。这个过程相对于一个完整的端到端的模型而言就会比较复杂。而且我们需要构造相应的样本,整个过程也会变得相应的繁琐。

SPPNet

针对RCNN的缺点,SPPNet进行了一定程度的改进。这里的改进主要体现在其中的一点上,就是如何对卷积特征进行共享。在SPPNet中提出了一个金字塔池化层,利用这样一个金字塔池化层就能够完成卷积特征的共享。我们来具体看一下这个金字塔池化层是如何实现的。

在上图中表示了原始的RCNN在进行目标检测时候,它所要完成的一个特征提取表示的一个过程。首先我们将输入图像Image理解为候选区域,针对输入图像进行候选区域的提取,这里通常会采用Crop/warp操作。Crop就是对候选区域进行抠图,然后将图片resize到一个固定的尺寸。因为采用了抠图和resize的过程,因此我们获取到的候选区域可能就会存在一些畸变和扭曲。接下来将resize过的固定尺寸的图片输入到卷积神经网中进行特征的提取,最后通过一个FC层得到一个输出的向量。这里采用FC层就意味着卷积层输出图像的feature map必须保持一致,对于不同的候选区域我们需要保证它输出的卷积层的feature map的大小保持一致。因为这里采用了同一个卷积神经网,就意味着卷积神经网的输入的图像的尺寸必须保持一致,因此必须采用resize的操作来保证输入的图像在同一个尺寸上。由于采用了resize,就会导致输入的候选区域进行一定程度的扭曲和拉伸,这样也会在一定程度上影响最终提取出来的特征。

另外将候选区域的提取放在了图像输入的下一步,这里通常针对不同的候选区域,会分别采用一次卷积来完成这样一个特征提取的过程,就意味着会有一些计算量的重复。这也是RCNN网络所面临的一个非常严重的问题,会存在计算量的浪费。

SPPNet针对于上图的流程给出了一个优化。它的优化主要体现在了这样的几点上,一对输入的图像采用不同的尺寸来进行图像的输入。采用不同尺寸图像的输入就会得到不同尺寸的feature map大小的输出。针对不同尺寸feature map大小的输入,如何得到一个同样大小的FC层大小的输入,这里就引入了一个spp层。

具体spp层的结构如下

上图可以看到分别给出了三个不同的尺寸——16*256-d、4*256-d、256-d。对于这三种不同尺寸,我们分别提取不同维度的特征。如何保证不同尺寸输入到FC层之后,它们的大小依然是一致的呢?如果我们将卷积层的结果作为FC层的输入,就意味着输入图像尺寸大小不一样的话,得到的三个不同尺度上的图像依然会有一些区别。这样的一些区别就意味着在输入FC层的时候,参数量就会存在不同,就会产生错误。SPPNet在处理这个问题的时候,实际上将每一个卷积层的输出固定的通过spp层之后固定的得到一个21维的特征,这个21维实际上是针对于每一个feature map而言的,每一个channel通道而言的,具体卷积层输出的特征数为21*channel数量。针对于其中的一个feature map,如上图中的第一个16*256-d的特征图,我们平均的将它分成一个4*4的网格,这样的4*4的网格,每一个网格都得到一个特征点的输出,无论它的尺寸多大,此时它的输出都是16;上图中的第二个4*256-d的特征图,我们为了得到4个特征点的话,就将feature map划分成2*2的网格,每一个网格得到一个特征点,这样的feature map最终能得到4个特征点,如果是256个通道的话,最终能得到4*256个特征向量;上图中的第三个256-d的特征图,它只有一个特征向量,那我们就得到1*256维的特征向量。通过这样的一个spp层,我们就能忽略feature map的大小,得到固定尺寸的输出,这样固定长度的输出就能够作为FC层的输入。通过这样一个spp层就能够对不同候选区域的尺寸来进行整体的处理,得到一个固定的输出。这个输出在输入到FC层之后,就能够进行后续的全连接层的计算。

具体如何通过这样一个区域来得到一个特征值呢?实际上是通过一个池化操作来完成的,我们知道Pooling操作包括Max和Mean这样的一些算子,spp层也是一个特殊的池化层,SPPNet就能够对于不同大小候选框的输入来得到一个固定的输出用于后续FC层的计算。SPPNet有一个非常重要的操作——仅对原图提取一次卷积特征,这个操作为我们节省了非常多的计算量。在RCNN网络中,在卷积特征是存在重复计算的问题,因此在SPPNet中就对这个问题就进行了改进。

从SPPNet的流程图我们也可以看到,对SPPNet的输入并不是原图,而是经过卷积之后的feature map,它们可能是多个通道的feature map,另外这个feature map分别采用了不同尺寸的输入,也就意味着我们会得到多个不同尺寸图像输入经过卷积之后得到的feature map,此时就和SPPNet的结构图对应起来了,为什么通道数是256而不是3,是因为经过卷积之后,它的channel的数量就会非常的高,这里输入的数量为256。为什么会有3个不同的feature map呢,就对应到了CNN的不同尺寸图像的输入。这样的过程SPPNet就能够节省更多的计算量并且解决了原始的RCNN网络中对于候选区域固定尺寸所带来的图形扭曲的问题。因此SPPNet相对RCNN能够带来更加识别的准确度以及更快的检索速度。

Fast RCNN

Fast RCNN是RCNN的作者继RCNN之后提出的一个新的模型。在Fast RCNN中不同于SPPNet,采用了ROI Pooling层来对候选区域进行Crop以及完成固定尺寸输出。这里的ROI Pooling我们可以将其理解成一个单层的SPPNet。在Fast RCNN中采用多任务网络来解决分类和位置回归问题。也就是说在Fast RCNN中将不再采用SVM分类器以及线性回归模型来完成候选框类别的判定和位置的回归。这里同样采用一个神经网络来完成分类和回归的任务。

借鉴SPPNet的优点,Fast RCNN同样采用共享卷积的策略来节省卷积运算的时间,去掉重复的计算。Fast RCNN为Faster RCNN的提出打下了基础,提供了可能。

Fast RCNN网络结构

Fast RCNN网络相对于SPPNet来说依然包括了一个共享的卷积层,同SPPNet不同的地方在于采用了一个ROI Pooling层来得到FC层的固定输入。最终FC层得到的结果分别用于Bounding box的回归以及候选区域类别的判定。相对于SPPNet来说,主要改进的点就在于一个是ROI Pooling层的提出,另外一个就是多任务网络的使用。此时深度学习目标检测一个基本的框架也就有了一个基本的雏形。只是在Fast RCNN中在候选区域提取的过程依然采用了选择性搜索策略,因此也会导致整个网络的过程不是一个完整的端到端的过程,Faster RCNN相对于Fast RCNN的改进就在于RPN网络的提出,通过对候选框推荐算法来进行改进,使得整个目标检测的过程由一个完整的深度学习的网络来完成。

  • ROI Pooling:
  1. SPPNet的一种,也是Pooling层的一种。
  2. 为了将proposal抠出来的过程,通过相对的坐标来抠取feature map上所对应的候选区域的位置,然后将抠取出的feature map resize到统一的大小。对于resize我们通常会采用一些特定的Pooling的操作来完成这样一个过程。
  3. 操作如下
    1. 根据输入的image,将Roi映射到feature map对应的位置。我们通常输入的候选目标它真值的区域通常是相对于原始的image而言的,而Roi Pooling则是作用在经过卷积之后的feature map上的,因此我们需要计算一个相对坐标来得到Roi区域所对应的feature map上相应的位置。然后将所得到的位置抠取出来。
    2. 抠取出来之后,将映射后的区域划分成相同大小的sections(sections数量和输出的维度相同),或者叫block,或者叫块、网格结构。具体包含的网格数和输出维度保持相同。
    3. 对每个section进行max pooling操作得到最终的固定尺寸的输出。

ROI Pooling作用在不同大小的候选区域能够得到一个固定的输出,这个固定的输出就能够用于后续的FC层来完成接下来的网络的位置的回归和类别的判定。

  • Fast RCNN网络性能提升:

在上图中,我们可以看到,Fast RCNN相比于RCNN在训练时间和测试时间均有了非常大的提升。Fast RCNN在整个训练的时间可能需要9.5小时,而RCNN则需要84小时。相对于RCNN,Fast RCNN在训练速度上能够得到一个非常大的提升,大概提升了8倍左右。而在测试时间上,RCNN完成单张图的检测通常需要消耗47秒,而Fast RCNN在处理单张图片的时候,完成一个检测的过程只需要0.32秒,整个检测的过程速度提高了146倍。

Fast RCNN在性能上的提升意味着深度学习目标检测算法能够达到实时处理的可能性,为后续的研究带来了曙光。

  • Fast RCNN网络缺点:
  1. 存在瓶颈:选择性搜索,找出所有的候选框十分耗时。
  2. 那我们能不能找出一个更加高效的方法来求出这些候选框呢?答案就是后续Faster RCNN的Region Proposal Network(RPN)网络的提出。代替在传统的目标检测算法的proposal提取的过程。

Faster RCNN

Faster RCNN同样也是Ross B. Girshick提出的一种目标检测框架

它的整个架构同传统的目标检测的架构是非常相似的,但是它的组织形式发生了非常大的改变。首先在卷积层采用了共享卷积的操作,而proposal的提取采用了一个RPN网络来完成。最终候选区域的类别和位置同样采用神经网络来完成。整个过程是一个端到端的检测过程。也就是说我们将图片作为输入,通过上图中的网络经过计算之后就能够得到一个输出,这个过程无论是它的繁琐程度还是运算效率都会得到一个非常大的改进。因此在Faster RCNN提出之后,也意味着Two-stage算法整个框架的成型,在后续的Two-stage目标检测算法中主要也是沿用了这样一系列的框架,后续的深度学习目标检测主要的改进点就在于对其中各个组件的优化。比如说采用更加优秀的主干网络来提取更加鲁棒的特征;采用更加严谨的ROI Pooling的策略来提升模型的准确度。

Faster RCNN网络结构

Faster RCNN同Fast RCNN最大的不同就在于proposal提取的过程采用了RPN网络,其他的组件依然采用了共享的卷积,ROI Pooling和神经网的分类和回归来完成最终的目标的检测和定位。对于主干网络包括了13个卷积层和13个激活层(relu)以及4个池化层。这里4个池化层意味着图像在进行下采样的时候则下采样了2^4=16倍,意味着feature map大小在RPN网络输入的时候是原始Image的1/16。

在RPN网络中并没有完成最终的目标区域的判定,主要是完成了背景和前景的区分,实际上完成的是一个二分类的问题。同时会完成目标物体的初定位的过程。在后续的过程会通过RPN网络得到的物体粗略的位置进行进一步的细化。在Faster RCNN中同样包括了一个ROI Pooling层,利用ROI Pooling层来针对于RPN网络得到的proposal来进行抠图和固定尺寸的输出。将这个固定尺寸的输出输入到后续的子网络中来进一步对目标类别的判定和位置的精修。通过分类和位置精修就能够得到最终深度学习目标检测算法的输出。

  • RPN网络:

在RPN网络中主要包括了前、背景分离以及框位置的粗回归两个任务,我们可以将这两个任务理解为粗定位粗分类。粗分类是指在RPN网络中仅仅对proposal,提取出来的候选框或者说候选区域它们是否属于背景还是属于前景这样的一个二分类问题来进行判定。在后续的网络中,我们会进一步对候选区域它所属的具体的类别来进行判定。因此在RPN网络中的分类为粗分类。粗定位是指在RPN网络中同样也会对框的位置进行一次回归,这里的回归同样也是一个粗略的回归,是相对于后续的网络需要对框的位置进行进一步的修订。也就是说在后一步的位置中会得到更加精确的框的位置。相对来说在RPN网络中就是对框的位置进行粗略的定位。

RPN网络代替了传统目标检测的proposal候选框位置提取的过程,这个过程实际上就通过Anchor机制来实现。

如上图所示,在RPN网络中会通过一个滑动窗口(sliding window),这是一个3*3的窗口。针对于这样一个滑动窗口的区域,我们通过一个3*3的卷积核来进行卷积,卷积之后就能得到一个固定长度的特征向量。我们采用3*3*256通道数输出的256维的特征向量。针对于每一个3*3的区域,都能得到一个256维的特征向量,接下来分别用2个FC层来对类别和框的位置进行分类和回归。这里的Anchor是指对于每一个滑动窗口,它的中心点都会作为一个Anchor。实际上滑动窗口会从左到右、从上到下依次进行滑动,对所输入到RPN网络中的feature map每一个点都会作为一个滑动窗口的中心点,并且这个点被称为了一个Anchor。针对于这样一个Anchor,来分别从原始的图像中找到不同尺寸的一个窗口,我们认为这个不同尺寸的窗口经过pooling之后就能够映射到这样一个3*3的区域上。换句话说通过Anchor来找到原始的图像以及在当前的feature map上一个3*3的窗口,它们之间的对应关系。假设我们在原始的图像中找到9个区域,就认为这9个区域就是我们所需要提取的proposal。这9个不同尺寸的区域,经过pooling之后都会投影到这样一个3*3的区域上。此时就能够根据不同的Anchor来找到多个不同的proposal。比如说当前的feature map大小为w*h,我们就可以找到w*h*9个proposal(候选区域)。然后针对这些候选区域来估计每一个候选区域的类别和框的坐标。

实际上,在Faster RCNN的Anchor,我们会考虑3个面积尺寸(128、256、512),然后分别在这三个不同尺寸下分别取三种不同长宽比的区域(1:1、1:2、2:1)。这样每一个Anchor都能找到3*3=9个区域,再乘上Anchor的数量(feature map的像素点数量),即此时proposal的个数为9*w*h。因为每一个像素点都会成为Anchor的中心。我们针对9*w*h个proposal进行类别的判定和候选框位置的回归,并得到后续的ROI的位置作为ROI Pooling的输入,完成后续的位置精修和类别精细判定的一个网络。

现在我们来实现Faster RCNN各个组织结构的代码,先是主干网络的实现,这里我们遵照原定义,使用13个卷积层的VGG16模型进行实现。当然这里可以改成更加复杂的ResNet或者InceptionNet网络,这里神经网络的层次深度越深,可以提取的图像特征越好,可以从简单的颜色、纹理特征到更高层次的语义特征。

import tensorflow as tf
from tensorflow.keras import layers, models

class Vgg16:
    def __init__(self):
        self.first_fc_unit = 4096

    def image_to_head(self, inputs):
        x = layers.Conv2D(64, (3, 3), padding='SAME', activation='relu', kernel_regularizer='l2')(inputs)
        x = layers.Conv2D(64, (3, 3), padding='SAME', activation='relu', kernel_regularizer='l2')(x)
        x = layers.MaxPool2D((2, 2), padding='SAME')(x)
        x = layers.Conv2D(128, (3, 3), padding='SAME', activation='relu', kernel_regularizer='l2')(x)
        x = layers.Conv2D(128, (3, 3), padding='SAME', activation='relu', kernel_regularizer='l2')(x)
        x = layers.MaxPool2D((2, 2), padding='SAME')(x)
        x = layers.Conv2D(256, (3, 3), padding='SAME', activation='relu', kernel_regularizer='l2')(x)
        x = layers.Conv2D(256, (3, 3), padding='SAME', activation='relu', kernel_regularizer='l2')(x)
        x = layers.Conv2D(256, (3, 3), padding='SAME', activation='relu', kernel_regularizer='l2')(x)
        x = layers.MaxPool2D((2, 2), padding='SAME')(x)
        x = layers.Conv2D(512, (3, 3), padding='SAME', activation='relu', kernel_regularizer='l2')(x)
        x = layers.Conv2D(512, (3, 3), padding='SAME', activation='relu', kernel_regularizer='l2')(x)
        x = layers.Conv2D(512, (3, 3), padding='SAME', activation='relu', kernel_regularizer='l2')(x)
        x = layers.MaxPool2D((2, 2), padding='SAME')(x)
        x = layers.Conv2D(512, (3, 3), padding='SAME', activation='relu', kernel_regularizer='l2')(x)
        x = layers.Conv2D(512, (3, 3), padding='SAME', activation='relu', kernel_regularizer='l2')(x)
        x = layers.Conv2D(512, (3, 3), padding='SAME', activation='relu', kernel_regularizer='l2')(x)
        return x

    def head_to_tail(self, inputs):
        x = layers.Flatten()(inputs)
        x = layers.Dense(4096, activation='relu', kernel_regularizer='l2')(x)
        x = layers.Dropout(0.1)(x)
        x = layers.Dense(4096, activation='relu', kernel_regularizer='l2')(x)
        x = layers.Dropout(0.1)(x)
        return x

    def build_graph(self, input_shape, class_num):
        inputs = layers.Input(shape=input_shape)
        x = self.image_to_head(inputs=inputs)
        x = layers.Dense(class_num)(x)
        outputs = models.Model(inputs=inputs, outputs=x)
        return outputs


if __name__ == "__main__":
    vgg16 = Vgg16()
    vgg16_model = vgg16.build_graph((500, 500, 3), 10)
    vgg16_model.summary(line_length=100)

运行结果

Model: "model"
____________________________________________________________________________________________________
Layer (type)                                 Output Shape                            Param Id        
====================================================================================================
input_1 (InputLayer)                         [(None, 500, 500, 3)]                   0              
____________________________________________________________________________________________________
conv2d (Conv2D)                              (None, 500, 500, 64)                    1792           
____________________________________________________________________________________________________
conv2d_1 (Conv2D)                            (None, 500, 500, 64)                    36928          
____________________________________________________________________________________________________
max_pooling2d (MaxPooling2D)                 (None, 250, 250, 64)                    0              
____________________________________________________________________________________________________
conv2d_2 (Conv2D)                            (None, 250, 250, 128)                   73856          
____________________________________________________________________________________________________
conv2d_3 (Conv2D)                            (None, 250, 250, 128)                   147584         
____________________________________________________________________________________________________
max_pooling2d_1 (MaxPooling2D)               (None, 125, 125, 128)                   0              
____________________________________________________________________________________________________
conv2d_4 (Conv2D)                            (None, 125, 125, 256)                   295168         
____________________________________________________________________________________________________
conv2d_5 (Conv2D)                            (None, 125, 125, 256)                   590080         
____________________________________________________________________________________________________
conv2d_6 (Conv2D)                            (None, 125, 125, 256)                   590080         
____________________________________________________________________________________________________
max_pooling2d_2 (MaxPooling2D)               (None, 63, 63, 256)                     0              
____________________________________________________________________________________________________
conv2d_7 (Conv2D)                            (None, 63, 63, 512)                     1180160        
____________________________________________________________________________________________________
conv2d_8 (Conv2D)                            (None, 63, 63, 512)                     2359808        
____________________________________________________________________________________________________
conv2d_9 (Conv2D)                            (None, 63, 63, 512)                     2359808        
____________________________________________________________________________________________________
max_pooling2d_3 (MaxPooling2D)               (None, 32, 32, 512)                     0              
____________________________________________________________________________________________________
conv2d_10 (Conv2D)                           (None, 32, 32, 512)                     2359808        
____________________________________________________________________________________________________
conv2d_11 (Conv2D)                           (None, 32, 32, 512)                     2359808        
____________________________________________________________________________________________________
conv2d_12 (Conv2D)                           (None, 32, 32, 512)                     2359808        
____________________________________________________________________________________________________
dense (Dense)                                (None, 32, 32, 10)                      5130           
====================================================================================================
Total params: 14,719,818
Trainable params: 14,719,818
Non-trainable params: 0

第二步当然是从主干网络出来的feature map进入RPN网络。

转载链接:https://my.oschina.net/u/3768341/blog/5407678


Tags:


本篇评论 —— 揽流光,涤眉霜,清露烈酒一口话苍茫。


    声明:参照站内规则,不文明言论将会删除,谢谢合作。


      最新评论




ABOUT ME

Blogger:袅袅牧童 | Arkin

Ido:PHP攻城狮

WeChat:nnmutong

Email:nnmutong@icloud.com

标签云