R-CNN目标检测详解及Python代码实现

释放双眼,带上耳机,听听看~!
本文详细解释了R-CNN目标检测算法的原理和实现步骤,并附带Python代码实现。欢迎交流指正,了解更多深度学习相关内容。

我正在参加「掘金·启航计划」

本文是我和好友 @Zee 读论文后整理而来,部分表述可能存在不清晰之处、甚至可能出现错误。欢迎交流指正。
部分图片源于互联网,如有侵权,请联系我删除

R-CNN

R-CNN(Regions with CNN features) 是深度学习领域的经典框架之一,首次将卷积神经网络(CNN)引入了目标检测领域,并取得了优秀的效果。为了实现这个目的,本文解决了两个主要问题:使用深度网络定位目标,以及使用少量有标注的数据集训练出高容量的模型。

本文使用的数据集为 PASCAL VOC ,具体内容可以参考 目标检测数据集PASCAL VOC详解

本文涉及文章:Rich feature hierarchies for accurate object detection and semantic segmentation

结构

此目标检测系包含三个模块

  1. 生成各自独立的区域候选框
  2. 较大的卷积网络,用以从各区域中提取定长的特征向量
  3. 一些用以分类的线性 SVM(支持向量机)

最后实际上还有一个部分,使用回归算法精修候选区域。基本流程如下图:

R-CNN目标检测详解及Python代码实现

确定候选区域

文章的这一部分用的是 Selective Search ,一种传统的目标检测算法。文章里仅是一笔带过,故在此不做赘述。具体算法原理可以参考:理解Selective Search – 知乎 (zhihu.com)

此处使用它生成两千个候选区域。

特征提取

此部分通过五个卷积层、两个全连接层,将传入的 227*227 的 RGB 图片提取为 4096 维的特征向量。卷积网络的结构如下:

R-CNN目标检测详解及Python代码实现

对于任意区域大小的图片,这里最终采用的是直接拉伸的方法转为 227*227。不过在拉伸前会对区域框做一个扩大处理,使得拉伸后的图片周围有原图的 p 像素上下文(此处 p = 16 )

文章的支撑材料里探讨了几种不同的 warp 方法。

SVM+NMS

对于每一个类别,作者使用相应的已训练好的 SVM 对已提取出的特征向量进行打分。当所有候选区域都打完分后,对每个类别使用 NMS(非极大值抑制)算法,扔掉那些 IoU 高出
给定阈值的区域。下面具体介绍这个部分:

IoU

Intersection-over-union,正如其名称所言,计算的是两个矩形框的交集区域面积除以并集区域面积

R-CNN目标检测详解及Python代码实现

该值的取值范围为 [0, 1],0 代表完全不相交,1 代表二者完全等同

NMS

NMS 用于去除重复框,效果如下
R-CNN目标检测详解及Python代码实现

它的算法原理并不复杂,主要是下面这几个步骤:

  1. 给定n个候选框,根据其分数从大到小排序
  2. 循环遍历每一个框,计算它与其他框的 IoU ,如果大于特定的阈值,就丢弃对应的框(因为那俩很可能框的是同一个东西)
  3. 重复,直至所有框处理完成(被保留或丢弃)

对应 Python 代码如下:

import numpy as np

def nms(dets, thresh):
    """ :dets 给定的候选框
        :thresh 阈值
        return 保留的框们
    """
    # 获取dets的各种信息
    x1 = dets[:, 0]
    y1 = dets[:, 1]
    x2 = dets[:, 2]
    y2 = dets[:, 3]
    scores = dets[:, 4]

    areas = (x2 - x1 + 1) * (y2 - y1 + 1)
    order = scores.argsort()[::-1]

    keep = []
    while order.size > 0:
        i = order[0]
        keep.append(i)
        xx1 = np.maximum(x1[i], x1[order[1:]])
        yy1 = np.maximum(y1[i], y1[order[1:]])
        xx2 = np.minimum(x2[i], x2[order[1:]])
        yy2 = np.minimum(y2[i], y2[order[1:]])

        w = np.maximum(0.0, xx2 - xx1 + 1)
        h = np.maximum(0.0, yy2 - yy1 + 1)
        inter = w * h
        # 计算 IoU
        ovr = inter / (areas[i] + areas[order[1:]] - inter)

        # 保留 IoU 低于 thresh 的那些框
        inds = np.where(ovr <= thresh)[0]
        order = order[inds + 1]

    return keep

训练

预训练

CNN 在 ILSVRC 2012 训练集上做了预训练,该数据集具有图像级别的标注(也就是没有候选框)

微调

为了使 CNN 适应于目标检测的新任务,作者使用缩放后的候选区域微调网络。这一过程中,网络的最后一层(分类输出层)从 1000 维更改为了 21 维 (20 种不同的目标 + 背景),其余层结构不变。作者将所有与人工标注的框的IoU大于 0.5 的区域认为是正类(也就是对应分类)、小于 0.3 的是背景(这俩数值是{0, 0.1, …, 0.5}以网格搜索的方式整出来的)。
梯度下降的初始学习率为 0.001 (预训练时的10%),每一轮采样 32 个正样本区域和 96 个背景区域组成 size 为 128 的 batch。鉴于相较于背景来说,正类较为稀少,因此采样时会偏向于正类。

Bounding Box Regression

作者使用由 D. Hoiem, Y. Chodpathumwan, and Q. Dai. Diagnosing error in object detectors. In ECCV. 2012. 提出的目标检测分析工具来探究此方法的错误模式,理解微调是怎么起作用的。一顿操作后,提出了 Bounding Box Regression 用于降低定位误差。通过使用从 Selective Search 选出的区域的 pool5pool_5 特征(CNN 的第五个卷积层后的最大值池化层) ,作者训练了个线性回归模型以预测一个新的候选区域。结果显示,这帮助修复了大量的错误定位,提升 mAP 3到4个百分点。

结果和问题

当时,这一模型在 PASCAL VOC 2012 数据集上得到了 30% 的提升(相较于之前的最好结果),效果可以说是斐然、
但是,R-CNN 也有很多问题:

  1. 。尽管在原论文里,作者一直在强调“我们这个方法很快”;但展示的数据放在今天看却慢的吓人:用(当时的)GPU 检测一张图片需要 13s,而 CPU 甚至达到了 50s+。这样的耗时不难理解:每张图要先用 Selective Search 提取 2000 个候选区域,每个候选区域都需要用 CNN 提取特征,并用 SVM 进行分类,这也是后面改进的重点。
  2. 。由于后面的过程需要前面过程产生的特征,保存这些特征需要占用大量的存储空间。
  3. 图片变形。由于送入 CNN 时图片被强制缩放到 227*227,因此不免变形

正因为有这样的问题,R-CNN 后续有很多改进。下面继续介绍

Fast R-CNN

正如名称所言,Fast R-CNN 是对 R-CNN 的改进,结果不仅能使用较深的卷积神经网络(VGG16),训练上速度也快了后者仅 9 倍,测试上更是快了 200+ 倍,检测单图仅需 0.3s (不包括产生候选区域的时间);它在 VOC 2012 上的 mAP 也达到了 66% ( R-CNN 是 62% )。
不过,在正式介绍它之前,我们先看另一项 R-CNN 的改进:SPPnet

SPPnet

SPPnet,提出于Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition,通过共享部分计算加速了 R-CNN。简单来说,SPPnet 对整张图进行卷积、计算特征(而不是像 R-CNN 一个候选区域来一次),然后使用对应部分的特征对每个物体进行分类。通过最大池化,将候选框内的特征图转化为固定大小的输出(例如6×6)来提取对应候选框的特征。多输出尺寸被池化后连接成空间金字塔池。SPPnet 在测试时将 R-CNN 加速10到100倍。由于更快的候选框特征提取,训练时间也减少了 3 倍。
简而言之,SPP 能够实现 任意尺寸输入、固定尺寸输出(给全连接层)。SPPnet 基于此实现了对不同输入尺寸的适应性。

R-CNN目标检测详解及Python代码实现

但 SPPnet 也有缺点,类似 R-CNN ,它也是多阶段流水线,包含提取特征、微调、训练 SVM 以及最后的 Box Regression,这些特征也得写入到磁盘。所以接下来回到主题,看看 Fast R-CNN 的改进

优点

总的来说,相对 R-CNN 和 SPPnet,它有这几个优点:

  1. 更高的 mAP
  2. 单阶段训练,使用了多任务损失(multi-task loss)
  3. 训练时会更新所有网络层
  4. 无需将特征存储于磁盘

结构

R-CNN目标检测详解及Python代码实现

Fast R-CNN 仍然使用 Selective Search 产生候选区域,并将图片和这些候选区域作为输入。不同于对每个候选都做卷积,Fast R-CNN 对整张图片卷,并产生一个卷积特征图,然后对每个候选 RoI(Region of Interest,感兴趣区域)用 RoI 池化层产生定长的特征向量,丢入全连接层。最后会产生两个同级输出:第一个是分类结果,第二个是精修过的区域位置。
这里能看出比较有趣的另一点是,Fast R-CNN 将 R-CNN 最后的 Box Regression 也巧妙结合到了整个网络中。不过下面我们先看看另一个提到的东西: RoI 池化层

RoI 池化层

此层使用最大值池化,将任意感兴趣的有效区域转成小的、定长的特征图(长度 H*W,比如 7*7,H、W均为超参数)。本文里,RoI 使用一个四元组定义 (r,c,h,w)(r,c,h,w),分别对应左、上、高、宽
具体的操作时,RoI 池化层将 h*w 的 RoI 窗口划分为 H*W 的网格,然后将每个小格对应位置的值最大值池化、放入对应的网格内。对特征图的每个通道来说,这样的操作都是独立的。
其实这一层就是简化版的 SPP ,就是把金字塔池化缩到了一层。把不同尺寸的输入丢进去,最后都能得到固定维度的特征,能够丢给全连接层。
一个 h*w = 5*5 的输入经过 H*W = 2*2 后,结果示意如下

R-CNN目标检测详解及Python代码实现

训练

初始化

本文尝试三个从 ImageNet 训练出来的模型,分别是 R-CNN 用的 CaffeNet(AlexNet 改进版)、VGG_CNN_M_1024 和 VGG16,从小到大分别称为SML。为了适应本文效果,模型做了如下三个变换:

  1. 最后的最大值池化层改为了 RoI 池化层,对每个模型固定了这一层输出的大小 H*W (比如 L模型就是 7*7)
  2. 最后的全连接层和 softmax 换成了上面描述的并行层
  3. 模型的输入被改成了两个部分:图片本身和相应的 RoI 区域

微调

对R-CNN 和 SPPnet 来说,当训练时,它们的某一批候选区域是随机挑的,比如 128 个候选区域可能来自 128 张不同的图片,但 Fast R-CNN 则是从 N 张图片里每张取 R/N 的候选区域出来,比如 N=2,R=128 两张图各取 64 个。更重要的是,由于同一张图的 RoI 可以共享计算,因此相较于之前的两个网络,它能大幅加快计算速度。

多任务损失

由于输出是两个并行的层,Fast R-CNN 的损失函数也就是二者之和。具体定义为

L(p,u,tu,v)=Lcls(p,u)+λ[u≥1]Lloc(tu,v)L(p, u, t^u , v) = L_{cls}(p, u) + λ[u ≥ 1]L_{loc}(t^u , v)

前半部分 Lcls(p,u)=−log⁡puL_{cls}(p, u) = – log p_u 即为预测类别(p)和真实类别(u)的对数损失;后半部分则为区域的损失。其中,λlambda为二者的平衡系数(在本文里均使用 1 ),[u ≥ 1]则表示仅当 u 为真实类别时才计算,u = 0 (背景类)时不计算。最后的部分定义如下:

R-CNN目标检测详解及Python代码实现

同 R-CNN 使用的 L2L_2 损失(x2x^2)不同,此处的损失更平滑。当 x 较大时,前者的梯度(2x2x)较大,容易导致梯度爆炸;而此处的式子会保持梯度恒小于等于 1。

Mini-batch 采样

如上面已经展示的那样,Fast R-CNN 用 2*64 个 RoI 组成 mini-batch,类似于 R-CNN ,里面包含 25% 的目标样本(IoU >= 0.5)和 75% 的背景样本(0.1 <= IoU < 0.5)。在训练时,每个图片有 0.5 的概率水平翻转。

其他训练时的一些细节,如超参数的选择等此处略去,感兴趣的可以参考原文。

检测

检测的基本过程已经在结构里概述过,此处不再赘述。说一句,在得到结果的分类框和各自的分数后,Fast R-CNN 也对使用了 NMS,对每个类别的重复框做了处理。
在做检测时,作者发现,有比较多的时间花在了全连接层上。而我们知道,全连接层其实就是在计算矩阵 WxWx。为了加速这一过程,作者用到了 SVD(奇异值分解)。

SVD

简单来说,对给定的任意形状矩阵 MM,都可以分解成下列形式

R-CNN目标检测详解及Python代码实现

假定原始矩阵大小为 n*m,则分解出的三个矩阵大小分别如下

R-CNN目标检测详解及Python代码实现
矩阵 ΣSigma 仅在对角线上有值,称为奇异值,按重要程度从左上到右下排序。因此,我们可以仅保留前 kk 个奇异值,同时舍去U和VTU和V^T多余的行和列,就能起到压缩的效果。被保留的区域如下图所示:

R-CNN目标检测详解及Python代码实现

压缩后的三个矩阵相乘,大小仍为 m*n,且信息基本保持不变(在k值适当时)。

最终到实现时,相当于把一个全连接层改为两个,第一个不含偏置,第二个才加上偏置。

R-CNN目标检测详解及Python代码实现

一些结果

下表来自原文,分别展示了三者在训练和测试上的速度

R-CNN目标检测详解及Python代码实现

可以看到,对于 L 模型,Fast R-CNN 在训练上比 R-CNN 快到了 8.8 倍,测试上则达到了 213 倍(加上 SVD 的情况);同时,它的 mAP 也高于 R-CNN。

除此之外,作者还做了不少的对比实验,检验了对微调层的选择、多任务训练是否有帮助、scale 的策略、训练数据增加是否有帮助、softmax 和 SVM 对比、更多的候选框是否有效等,感兴趣的同学可以阅读原文,此处不再赘述。

缺点

尽管 Fast R-CNN 有不少优点,但仍然存在一些缺陷。其中,由于它挑选候选框仍然使用 Selective Search 算法,这成了耗时的主要来源。后文的 Faster R-CNN 就针对此做了改进。

Faster R-CNN

此部分由我的好友 @Zee 撰写的 PPT 整理而来,由衷感谢他的支持。
对应文章:Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks

结构

Faster R-CNN 相较于 Fast R-CNN 的最大改进就是,它提出了 RPN 结构来代替 Selective Search 生成候选框,并且与目标检测的卷积层进行共享来缩短训练时间和测试时间,同时提高目标检测的精度。二者结构的对比如下所示:

R-CNN目标检测详解及Python代码实现

观察上图,我们能发现,Faster R-CNN 使用 RPN 结构从特征图中产生候选区域,而其余部分均与 Fast R-CNN 类似。文章里作者使用了两种卷积网络:ZFVGG16,下图是使用 ZF 网络的详细结构图

R-CNN目标检测详解及Python代码实现

所以接下来,咱们就来看看 RPN 是个什么东西?

RPN

对于特征图 𝐻×𝑊×𝑑,我们使用一个 n*n(文章中n=3) 的滑动窗口, 对每个中心点生成个 𝑘 个 (原文为 3×3 共九个, 三种比例分别为 1:1, 1:2, 2:1,三个尺寸分别为 128 , 256, 512) anchors。产生的 anchors 示意如图:

R-CNN目标检测详解及Python代码实现

之后对所有的 anchors 做 bounding box regression 回归,这样如果对应的 anchor 有对应目标,就能找到尺寸最匹配的框。
经过 3×3 卷积层后,每个滑动窗口被映射为一个维度为 d 的低纬特征向量 (对于 ZF 是 256, VGG16 则为 512 ) , 它被送入到两个并行的 1×1 的卷积层,分别生成 𝐻×𝑊×2𝑘 和 𝐻×𝑊×4𝑘 的输出。前者 H*W 代表 anchors 的组数,每组包含 k 个 anchor,相邻的两个数字分别代表预测为目标物体和背景的概率;后者其余类似,4 个数字代表最终框的宽高以及位置偏移的相关量。
最后,将两个输出和 reshape 信息一起结合,生成对应于原始图片尺寸的 proposal, 与 feature map 一起送入 RoI pooling 层。后续结构与 Fast RCNN 一致。

损失函数

类似于 Fast R-CNN ,Faster 的损失函数也由两部分组成:

R-CNN目标检测详解及Python代码实现

  • 分类损失是 log 损失, i 是每一个 minibatch 中每个 anchor 的索引
  • 如果 anchor 为 positive,pi∗p_i^* 为 1, 否则为 0; pip_i 表示 anchor 为 positive 的概率
  • 回归损失函数是 smooth L1L_1
  • tit_i 是表示(𝑡𝑥, 𝑡𝑦, 𝑡𝑤, 𝑡ℎ)四个参数的预测向量,ti∗t_i^* 是表示 ground truth 对应的向量
  • 最后使用 N𝑐𝑙𝑠N_{𝑐𝑙𝑠}𝑁𝑟𝑒𝑔𝑁_{𝑟𝑒𝑔} 来进行标准化,原文中设置 N𝑐𝑙𝑠=256N_{𝑐𝑙𝑠}=256Nreg=2400N_{reg}=2400λ=10lambda =10; 代码中 N𝑐𝑙𝑠=256N_{𝑐𝑙𝑠}=256Nreg=128N_{reg}=128λ=1lambda =1。因为只有正 anchor 才会被计算回归损失, 而 mini-batch 一般是 128 个正样本和 128 个负样本

训练

在训练时,作者采用了四步训练法来实现 RPN 和 Faster R-CNN 的卷积层参数共享。具体步骤如下:

R-CNN目标检测详解及Python代码实现

实验

文章做了大量的实验,以验证各因素对算法效果的影响。

R-CNN目标检测详解及Python代码实现

R-CNN目标检测详解及Python代码实现

R-CNN目标检测详解及Python代码实现

R-CNN目标检测详解及Python代码实现

R-CNN目标检测详解及Python代码实现

R-CNN目标检测详解及Python代码实现

R-CNN目标检测详解及Python代码实现

缺点

当然,Faster R-CNN 也存在一些缺点,我们的归纳如下所示

  • 卷积提取网络:无论是 VGGNet 还是 ResNet,其特征图仅仅是单层的,分辨率通常也较小,这些都不利于小物体及多尺度的物体检测,因此多层融合的特征图、增大特征图的分辨率等都是可以优化的方向。
  • NMS:在 RPN 产生 Proposal 时,为了避免重叠的框,使用了 NMS,并以分类得分为筛选标准。但 NMS 本身的过滤对于遮挡物体不是特别友好,本身属于两个物体的 Proposal 有可能因为 NMS 而过滤为 1 个,造成漏检,因此改进优化 NMS 是可以带来检测性能提升的。
  • RoI Pooling:Faster R-CNN 的原始 RoI Pooling 两次取整带来了精度的损失,因此后续 Mask RCNN 针对此 Pooling 进行了改进,提升了定位的精度。
  • 全连接:原始 Faster R-CNN 最后使用全连接网络,这部分全连接网络占据了网络的大部分参数,并且 RoI Pooling 后每一个 RoI 都要经过一遍全连接网络,没有共享计算。
  • 正负样本:在 RPN 及 RCNN 部分,都是通过超参数来限制正、负样本的数量,以保证正、负样本的均衡。而对于不同任务与数据,这种正、负样本均衡方法是否都是最有效的值得思考,且现实生活中得到均衡的正负样本有时难以实现。
  • 两阶网络:Faster R-CNN 的 RPN 与 R-CNN 两个阶段分工明确,带来了精度的提升,但速度相对较慢,实际实现上还没有达到实时。因此,网络阶数也是一个值得思考的问题。

感谢阅读,如有帮助,欢迎不吝点赞。
本文仅发于掘金和个人平台,作者 FunnySaltyFish。

本网站的内容主要来自互联网上的各种资源,仅供参考和信息分享之用,不代表本网站拥有相关版权或知识产权。如您认为内容侵犯您的权益,请联系我们,我们将尽快采取行动,包括删除或更正。
AI教程

Inception结构及其在神经网络中的应用

2023-12-17 16:45:14

AI教程

OpenAI及ChatGPT概述:从技术到应用

2023-12-17 16:53:14

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索