文章目录

  • 1. 目标检测的基本范式
    • 1.0-1 目标检测简介
    • 1.0-2 基本概念
    • 1.1 滑窗
      • 1.1.1 滑窗基本思想
      • 1.1.2 滑窗效率问题改进
      • 1.1.3 感受野计算
    • 1.2-1 使用卷积实现密集预测
      • 1.2.1 在特征图上进行密集预测
      • 1.2.2 边界框回归
      • 1.2.3 非极大值抑制(Non-Maximum Suppression)
      • 1.2.4 使用密集预测模型进行推理
      • 1.2.5 如何训练
      • 1.2.6 密集预测的基本范式
    • 1.3 密集预测范式的改进:多尺度预测
      • 1.3.1 锚框
      • 1.3.2 图像金字塔
      • 1.3.3 基于层次化特征
      • 1.3.4 特征金字塔(FPN)(2016)
      • 1.3.5 多尺度的密集预测
  • 2. 单阶段&无锚框检测器选讲
    • 2.1 RPN(2015)
      • 2.1.1 基本原理
      • 2.2.2 RPN-head代码
      • 2.2.3 RPN-基于loU的匹配
    • 2.2 YOLO(2005)
      • 2.2.1 Yolo模型
      • 2.2.2 Yolo的匹配和框编码
      • 2.2.3 Yolo的损失函数
      • 2.2.4 YOLO的优点和缺点
    • 2.3 SSD(2016)
    • 2.4 Focal Loss与RetinaNet(2017)
    • 2.5 YOLO系列选讲
      • 2.5.1 YOLOv3(2018)
      • 2.5.2 YOLOv5(2020)
    • 2.6 无锚框检测算法
      • 2.6.1 重叠问题
      • 2.6.2 FCOS(2019)
      • 2.6.3 CenterNet(2019)
      • 2.6.4 YOLOX(2021)
      • 2.6.5 YOLO v8
    • 2.7 总结

如果还想看这个王若晖老师的相关课程,还可以看看AI实战营的第一期课程:合集·OpenMMLab AI 实战营

对应视频链接:目标检测与MMDetection

1. 目标检测的基本范式

1.0-1 目标检测简介


定位+分类

  • DPM:Deformable Part Model,可形变组件模型,

    • 于2008年提出,并发表了一系列的cvpr,NIPS。并且还拿下了2010年,PASCAL VOC的“终身成就奖”
    • DPM用到了HOG的东西,是用传统算法做的。
  • 详见:
    • CSDN博客-DPM(Deformable Part Model)原理详解
    • 博客园博客-关于DPM(Deformable Part Model)算法中模型可视化的解释,算法提出者使用的是matlab写得源码
    • opencv有对应的c++实现:https://docs.opencv.org/3.4/d9/d12/group__dpm.html
    • 目标检测(二)传统目标检测与识别的特征提取(一文讲懂Haar-like特征,HOG特征,DPM特征)
    • github上有个python实现,但是很少:https://github.com/Alarnti/dpm
  • 单阶段方法一开始因为精度问题,没有两阶段的受欢迎,后面改进,在精度和部署上都很优秀,所以现在YoLo系列是主流。2020年之后的Transformer方法因为精度很好,所以也逐渐成为目标检测中受欢迎的方法

b站上有一个课,讲了传统和深度的目标检测:100集深度学习目标检测算法教程


推理精度

  • 早年COCO数据集上的准确度大概在21%~34%,现在最新的采用SwinTrans+HTC的已经可以达到57.7
  • coco leaderboard,点击这里,Evaluate->选择Detection 就可以看到了
  • 不会再有COCO 2021 Challenge比赛了,取而代之的是ICCV2021 LVIS Challenge ,上面介绍的SwinTrans+HTC方案就是这个比赛里出的,详见:腾讯优图斩获ICCV2021 LVIS Challenge Workshop冠军及最佳创新奖
  • 另外,虽然COCO不再举行官方赛事,但是还是有人在刷分,
    • paperwithcodes上:Object Detection on COCO test-dev,目前排名第一的是:OpenGVLab/InternImage
    • 关于OpenGVLab,详见:上海人工智能实验室联合商汤科技发布通用视觉开源平台OpenGVLab

推理速度模型体积

  • 都是YoLo系列最好,所以落地部署基本都是yolo和ssd
  • YoLoX-s推理速度大概10ms,1000ms/10ms=100帧,每秒差不多就是100帧的速度。。

1.0-2 基本概念

  • 一般要求目标检测的边界框是平行于图像边界的,如果不平行的话,会有个专门叫做旋转框的方式去处理


都是算重叠区域,和DICE很像,但是不一样,衡量的都是重叠程度,但是计算方式不一样。

关于这两个指标的异同和使用场景,可以看看:

  • CSDN博客:分割网络评价指标)dice系数和IOU之间的区别和联系
  • 知乎文章:有关IoU和Dice
  • 博客园文章:语义分割评价指标(Dice coefficient, IoU)
  • 影像切割任務常用的指標-IoU和Dice coefficient
  • stackexchange-F1/Dice-Score vs IoU
  • stackoverflow.com-Why Dice Coefficient and not IOU for segmentation tasks?
  • towardsdatascience-Metrics to Evaluate your Semantic Segmentation Model

1.1 滑窗

目标检测的基本思路:从滑窗到密集检测

1.1.1 滑窗基本思想

目标检测是基于分类任务进行的,所以分类是OK的,需要解决的主要就是定位的问题。

  • 是什么,比较好解决
  • 在哪里,定位,受到物体位置、数量、尺度的变化影响。


最简单(朴素思想)的一种思路:

  1. 设定一个固定大小的框
  2. 用这个框去遍历整个图像,每次把框中的部分图像送入分类器去识别,比如:背景、长椅
  1. 同时为了检测不同大小、不同形状的物体,可以使用不同大小、长宽比的窗口,扫描图片

1.1.2 滑窗效率问题改进

滑窗的思想很简单,但是会有一个效率问题:

  • 例如:一张1000x600的图,窗口大小是100x100,每滑动20像素分类一次,

    • 则需要滑动的次数就是(1000-80)/20* [(600-80)/20]=46*26≈1200
    • 若还要使用其他尺寸的不同窗口,则需要进行分类的次数就更多了
    • 滑窗顺序,串行进行分类,也可以考虑多个方向同时进行,利用并行提高速度

改进思路:

  1. 使用启发式算法替换暴力遍历,

    • 用相对低计算量的方式粗筛出可能包含物体的位置,再用卷积网络预测
    • 早期二阶段方法使用,依赖外部算法,系统实现复杂
    • 以前的RCNN和Fast RCNN用的是这个,现在也不怎么用了
  2. 减少冗余计算,使用卷积网络实现密集预测
    • 目前普遍采用的方式,所以下面主要讲这种


对这个滑窗框住的图像,

  • 进行一次前传,前传就是:多层不同卷积核进行计算,第一层卷积核扫截出来的图像,第二层卷积核扫前一步得到的特征图,依次继续。之后特征拼起来,进行分类预测
  • 可以看到,对于两个滑窗,其实有一部分重叠区域会被卷积核卷多次。

改进思路:用卷积一次性计算所有特征,再取出对应位置的特征完成分类。

  • 如果要对上图两个窗进行分类,可以直接计算出全图的特征图;然后再按照位置关系,把窗对应的部分投影到特征图上,把对应的特征裁剪出来,进行后续的分类
  • 因为卷积本身是具有位置不变性的,同一个卷积核在不同的位置,计算时那个系数是一致的。也就是说:先把窗扣出来算,还是算完全图再扣出来,结果是一样的(忽略padding那些带来的影响的话)
    • 第一个卷积层的滑窗可能是100x100,卷积后在特征图上,滑窗大小会发生变化(但是要保证对应的感受野不变,有相应的计算公式),后续也是如此。

相比之前:

  • 之前是在原图上进行滑窗,每次滑窗都要计算卷积核,重叠区域会重复计算卷积(每层都这样),每层都会进行滑窗,再计算卷积。
  • 现在是直接在特征图上滑窗(注意,滑窗和卷积不一样,滑窗的固定框大小一般大于卷积核),只在最后一层特征图上滑
    • 把所有的卷积层都计算完,直接在最后的特征图上,在卷积层不使用滑窗。。。只在最后用滑窗对应的特征图部分拿出来进行分类
    • 重叠区域只计算一次卷积特征,与窗的个数无关(卷积计算次数和窗的个数无关)

1.1.3 感受野计算

参考:

  • 之前TensorFlow有一个脚本专门用来算感受野的

    • https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/receptive_field,这个文件已经不见了
    • https://github.com/timsainb/tensorflow-2-feature-visualization-notebooks
    • https://github.com/google-research/receptive_field,但是也有使用到它的


感受野就是神经网络中,一个神经元能看到的原图的区域

  • 如右图,2层3x3卷积,在layer3的一个神经元,其实看到的是layer1的5x5的区域
  • 可以说layer3的神经元表达了layer1中5x5区域的内容,也就是说layer1中5x5区域的特征是layer3的神经元


感受野的计算一般比较复杂,但是对于常见的卷积核是3x3,pad是1的卷积或池化堆起来的模型(每个卷积层/池化层都是这种设置)

  • 感受野中心坐标 = 神经元在特征图上的坐标 × 感受野步长
  • 感受野步长=降采样率/特征图尺寸的缩减倍数
    • 神经网络某一层上,相邻两个神经元的感受野的距离
    • 步长=这一层之前所有stride的乘积

以右图下面的带数字的示意图为例,计算Conv2上的神经元的感受野中心坐标(layer1是Raw Image)

  • 则Conv2(layer3)上数值为5的神经元在Conv2这个特征图上的坐标就是(0,0),从0开始计数
  • 感受野步长,从Conv2到原图,中间所有卷积层、池化层等包含stride的,把这些stride累乘起来,就是Raw Image→Conv1的stride=2,Conv1→Conv2的stride=1,则感受野步长=2x1=2
  • 上面的示意图有问题。。图画的不好,也没有加padding

关于感受野的计算,可以看看:

  • distill.pub-Computing Receptive Fields of Convolutional Neural Networks
  • Understanding the receptive field of deep convolutional networks
  • 其他中文感受野计算好像很多还有问题,也基本上是从distill那篇翻译过来的。。。有空自己研究研究吧

除了感受野中心,其实还有感受野大小,但是感受野大小不是一个很有用的东西。。

  • 对于一个神经元来说,其激活值更多受到感受野中心附近图像的影响(即:对感受野中心的位置更敏感),而感受野边缘的这些值,通常对神经元激活值计算的影响不大,以下图为例

  • 对顶部的这个神经元(假设是 y y y)来说,假设跟下面的神经元层的连接权重都是1(也就是卷积核的值都是1),
  • 则Input中紫色框里最中间的那个(假设是 x 3 x_3 x3​)的梯度,就是 ∂ y ∂ x 3 \frac{\partial y}{\partial x_3} ∂x3​∂y​=从 y y y到 x 3 x_3 x3​所有可行路径的个数,像个pascal三角形一样扩散开。(其实就是杨辉三角,详见:神奇的帕斯卡三角形
  • 而Input中紫色框里边缘部分的像素,梯度就很小,只有1条路径
  • 即顶部的神经元对边缘像素的导数只有1,对中间就很大

1.2-1 使用卷积实现密集预测

1.2.1 在特征图上进行密集预测


这页ppt动态的,很多内容,所以下面分小图分别说

以特征图的左上角为例,其对应的就是图像的左上角部分。

  • 像红色框对应的感受野,可能会由于padding,所以就在图像外面了
  • 特征图上相邻两个特征(红色和蓝色),对应在图像上红色和蓝色框的偏移量(起点的差值),其实就是stride,就是图像的感受野步长/降采样率/特征图尺寸的缩减倍数,就是上面说的:神经网络某一层上,相邻两个神经元的对应的感受野的距离;( 步长=这一层之前所有stride的乘积)


其实在卷积过程中,特征图上每一个值都对应一个感受野区域。

  • 不同特征对应的感受野区域,自然形成了一系列等距离分布的窗(和滑窗的效果其实一样)
  • 如果按照感受野的步长去设置滑窗,则实际上滑窗算法和一次性计算出全图的特征图的算法,就是一样的计算了

如果把感受野形成的区域看成是窗,那么按照以前的滑窗算法的思路,

  • 经过卷积网络得到特征之后,还需要进行分类,比如说用线性层进行分类
  • 如果物体是 C C C个类别,加上一个背景类,就是 C + 1 C+1 C+1个类别,那么线性层就需要输出 C + 1 C+1 C+1维度的logistics(就是sigmoid函数),输出0和1之间的值,一般会在后面再接一个softmax,输出1个框分别属于 C + 1 C+1 C+1个类别的概率,概率最大的就是当前这个框对应物体所述类别
  • 关于logistics函数,详见:动手学深度学习V2.0(Pytorch)——10.感知机(激活函数)-4.4 sigmoid和logistics部分

考虑有没有可能把这个线性分类也放在特征图上一次做完?

  • 是可以的,因为不同窗用的线性分类参数是一样的(同一个线性层),也就是进行的计算是一样的
  • 所以可以把这个线性分类层变成一个卷积层(1x1的卷积就是全连接层。。。详见:动手学深度学习V2.0(Pytorch)——21. 卷积层里的多输入多输出通道-1.6 1X1卷积层)
  • 比如线性分类层的维度是 C i n × C o u t C_{in}\times C_{out} Cin​×Cout​,则对应就可以找一个 1 × 1 1\times 1 1×1的卷积层,
    • 即每个特征图/通道的大小是1,输入通道数是 C i n C_{in} Cin​,就是线性层的输出,输出通道数是 C o u t C_{out} Cout​,就是一个 C + 1 C+1 C+1维度的向量
    • 这里其实是进行了一个转置,把线性分类的特征维度,变成了卷积层的特征维度


那么经过这个1x1的卷积之后,实际上得到的也是 C + 1 C+1 C+1个logistics,卷积层一般也是用sigmoid做激活函数的。

  • 如果非要得到概率图,那么在卷积层输出的特征图的通道维度上,再做一次softmax就行
  • 这样的话,把特征计算和概率计算这两个步骤合起来,得到了一种更高效的计算方式,但是效果和滑窗算法一模一样


这就是所谓的密集预测,借助卷积高效实现了滑窗算法。

  • 用卷积一次性计算出所有位置包含什么样物体的概率

1.2.2 边界框回归

滑窗算法中固定的窗和密集预测中感受野对应的窗,和真实的物体精准边界通常存在一定差异。

  • 为了实现边界框的精确定位,通常还需要进行一个额外的步骤,即在预测物体类别的同时,还需要预测一下物体的准确位置
  • 一般对卷积网络得到的特征:
    • 一个分支经过全连接层,用sigmoid和softmax求概率,使用分类损失
    • 另一个分支经过全连接层,预测真实边界框相对于之前 窗 的偏移量,4维偏移量,比如左上右下偏移,或者中心点的x、y偏移+长宽的偏移,使用回归损失
  • 由于是使用同一种特征做了两个任务的预测,所以这种也称为多任务学习。

基于锚框(Anchor Based)的方法:

  • 在图像上设置一些大小不同的基准框(不是滑窗,不做卷积计算),在进行边界框回归时,是基于这些基准框进行的。
  • 例如:上图中蓝色框是True Label
    • 人相较于紫色框,有多少偏移量,计算出这个东西(基于锚框的偏移)

无锚框(Anchor Free)的方法

  • 直接回归,物体边界相对于感受野中心的偏移量(无锚框,直接求基于感受野中心的偏移)

但是神经网络进行准确的回归任务要比分类任务困难一些,所以基于锚框的方法会降低回归的难度,

  • 但是锚框方法并不只是纯粹为了边界框回归而设置的,或者说锚框不仅只有降低回归难度这一个作用
  • 基于锚框还可以检测:同一个位置,不同大小甚至有所重叠的一些物体

1.2.3 非极大值抑制(Non-Maximum Suppression)


滑窗和密集预测这类算法通常还会面临一个问题:会在一个物体周围产生很多重叠的框

  • 滑窗/感受野对应窗,窗是重叠的,很可能多个窗都包含了同一个物体的不同部分,导致输出结果的时候,这个物体周围有很多框。
  • 虽然这些框比真实的框有所偏移,不那么精确,但是还是包含了同一个物体。
  • 这些不同偏移的窗在特征图上有不同的位置,都有可能会被预测成同一个类别,对应的前景框就会被保留下来,但是实际上这些前景框在原图上的位置是大幅度重叠的
  • 对于检测算法来说,上面右图猫的位置只希望输出一个检测框就足够了,重叠的框是没有必要的,需要把多余的框去除掉。
  • 去除重叠框的算法,就叫做:非极大值抑制算法,字面意思就是:只保留置信度最大的,不是最大的都删除(非极大值,抑制)
  • 所以在之前的Fast RCNN算法里,会有个NMS的参数,配置最后输出的框的数量,一个物体周围只保存1个或者几个

整体的NMS非极大值抑制算法,是一个贪心算法,主要步骤:

  1. 把所有检测框(全图的,不是针对一个物体的,比如所有框是 B B B集合)按照置信度排名
  2. 找出置信度最高的框 B i B_i Bi​,放到结果集 R R R中去,找到 B B B集合中剩下的与 B i B_i Bi​重合的框(即IoU>0.7的框),把这些框删掉,这些框大概率和 B i B_i Bi​指向的是同一个物体,但是置信度又比 B i B_i Bi​低,因此删除。
  3. 继续进行步骤2,只保留所有重叠框里置信度最大的那个框,放到结果集 R R R,直到 B B B集合为空,说明已经处理完所有的检测框,此时输出 R R R结果集,就是非极大值抑制的结果


置信度(Confidence Score):模型认可自身预测结果的程度

  • 通常会为每个框(滑窗/感受野对应的窗)预测一个置信度

置信度计算的方法一般有两种:

  1. 直接用之前分类预测头得到的概率作为置信度,比如对某个框,之前分类头预测的是长椅的概率是0.9,可能这个框附近重叠的另一个框,分类头预测是长椅的概率是0.8,使用这个概率作为置信度。用分类结果的概率作为置信度的衡量标准,感觉是比较合理的。
  2. 不使用分类的概率,让模型另外预测一个置信度,因为有真实的目标检测框,基于这个信息,可以根据框的偏移作为评判框的质量(置信度)的好坏

1.2.4 使用密集预测模型进行推理


得到一个训练好的密集预测的模型,如何去进行推理:

  1. 把待推理图像送到训练好的模型,得到那个预测图

    • 包含分类和回归结果,分类:每个类别对应的概率;回归:每个边界框的偏移量(基于锚框或者无锚框)
  2. 保留预测类别不是背景的框(分类有一个背景类,删除背景类概率最大的框)
  3. 基于框中心,和边界框回归结果,进行边界框解码
    • 比如对于基于锚框的方法,就是基准框+预测出的偏移量,得到比较精确的边界框结果
    • 提前删除不是背景的框是为了避免不必要的计算开销
  4. 后处理,进行非极大值抑制
    • 在比较精确的边界框结果的基础上,再去删除重叠的框,保留置信度高的框,比较合理

1.2.5 如何训练

重点其实在于,网络前传之后,怎么去把模型得到的一个结果,和真值进行比较,怎么去设计损失函数(返传loss的梯度)


输入图像,模型输出的其实是包含位置类别和边界框回归结果的一个array of prediction results,

  • 注意,每个位置都产生一个预测,这里密集预测结果的大小,并不等于图像的大小,是降采样之后的一个结果。结合上面的对特征图进行密集预测,这里密集预测的结果的大小应该是 C i n × C o u t C_{in}\times C_{out} Cin​×Cout​,对 C + 1 C+1 C+1通道做了softmax,已经知道每个位置的分类了,不需要保留 C + 1 C+1 C+1个通道,1个就够了
  • 那么这里需要建立对应的GT(ground truth),很明显,示意图中密集预测结果的这个表格,肯定不是天然存在的,是需要根据真实的标注框去生成的。
    • 原图像有物体的标注框,怎么把这个标注框的类别对应到密集预测真值上,即基于稀疏的标注框,为密集预测的结果产生真值,这个过程称为匹配(Assignment)


根据感受野的计算公式,不难知道,特征图和原图在位置上存在一个比较简单的对应关系。

  • 之前是特征图上的点求对应的感受野中心,现在是要找到原图上标注框对应在特征图上的点的位置
  • 对应点的类别真值就是物体标注框类别,边界框回归的真值可以是这个框的位置,也可以是框相对于中心位置的偏移量
  • 对于每个标注框,其实在特征图上都可以找到与其最接近的位置(可以不止一个),接近程度由中心位置或者与基准框的IoU判断。
    • 比如上图中汽车对应密集预测真值的蓝框,椅子对应两个黄框
    • 其他部分的类别真值就是背景
    • 这样就可以把原图上稀疏的标注,变成密集预测真值图上密集排列数组标注结果。
  • 将密集预测真值和密集预测结果相减,算loss求和,就可以得到检测器的损失

  • 另外,还有个步骤:采样,

    • 不是所有密集预测图上的结果都参与计算,只会选取一部分正样本和负样本进行计算
    • 很明显,密集预测图上大部分都是负样本(背景),只有少数的是正样本(前景物体),全都算的话,样本数量就很不平衡了,模型可能会更多给出背景的预测,这不是我们想要的。
    • 同时,对于密集预测图上,有物体的真值框的边界位置,由于是前景物体和背景的分界,所以归到哪一类都不太好,还容易给模型带来困扰,这种一般不参与loss计算。

有些匹配和采样的策略,在不同的算法里也有所区别,mmdetection的配置文件里也会有相应的标注。


这是个检测任务

  • 不能单纯像分类任务一样,比较最终类别的差异;
  • 这里输出的内容既有分类,又有回归,而且是基于密集预测图进行的比较,所以要基于标注框先生成密集预测图真值。
  • 就像人体姿态检测,基于标注结果和高斯函数假设,生成真值热力图,与模型生成的预测热力图进行比较,以热力图的差值作为损失函数。

1.2.6 密集预测的基本范式


整体是一个框架性的设计思路,不是针对某个具体的网络进行说明:

  • 图像首先送入backbone(主干网络,在CV里一般是卷积网络),得到特征图,特征图中每个点都是对应的原图中某个感受野区域的一个特征表达
  • 得到特征图之后,用一个1x1的卷积去扫(小卷积网络/检测头),相对于在每个位置上进行线性分类,对于预测图中的每个位置,其包含:
    • 该位置对应的类别和边界框(感受野的范围),以及边框回归的偏移量(如果不使用概率作为置信度的话,可能还会预测一个置信度)
    • C+5+?来自于C+1(C+1个类别)+4(4个偏移量),C是类别种类,?表示可能还会预测别的东西,比如:置信度等
    • 预测图的空间分辨率和特征图一样,

推理:

  • 得到密集预测图,对非背景的框进行非极大值抑制

训练:

  • 匹配生成密集预测图真值,包括类别真值和边界框回归真值
  • 把模型生成的密集预测图和 密集预测图的Ground Truth进行loss计算,梯度回传,参数更新等。

1.3 密集预测范式的改进:多尺度预测


图像中物体大小可能有很大差异,比如可能同时存在100px的椅子和20px的汽车,

  • 如果使用上面说的朴素的密集预测范式,即只检测头(head)部分,基于主干网络(backbone)的最后一层或者倒数第二层的特征图进行预测。

    • 受限于结构(感受野),只擅长中等大小的物体(和卷积网络的设计,stride及卷积层等的层数有关)
    • 深层的特征图经过多次降采样,位置信息逐层丢失,小物体检测能力弱,定位精度低(pooling位置精度变低,/2)
  • 如何让网络可以支持多种尺度物体的检测,锚框是一种可以选择的技术

1.3.1 锚框


可以在原图上设置不同尺寸的基准框(锚框),基于特征分别预测每个锚框中是否包含物体

  • 对于特征图中每个位置,都可以在原图找到对应的感受野(蓝色虚线框),在感受野范围内,生成锚框,可以是多种尺度和多种长宽比的。
  • 但是由于锚框还是基于某个特征图的,特征图确定了,感受野范围和特征抽象程度就确定了,锚框最多能处理感受野内1倍~2倍尺度变化的物体,更大尺度变化的物体还是无法处理的

所以锚框对于处理多尺度的作用还是有限的

1.3.2 图像金字塔


其实还有一种思路,图像金字塔

  • 虽然模型不能变,模型只能比较好的适应某一个尺度的物体(中等尺度)。。。
  • 但是输入图像的大小可以变(可以把要检测的物体变大/变小)来适应模型可以检测的物体尺度,则:
    • 在变大的图像上(小物体变成中等尺度的物体),模型就能更好的检测小物体
    • 在变小的图像上(大物体变成中等尺度的物体),模型就能更好的检测大物体

这种方式

  • 优势:算法不经改动就可以适应不同尺度的物体
  • 劣势:计算成本成倍增加。
  • 图像金字塔有多少级图像,计算成本就增加多少倍;可以用在模型集成等不在意计算成本的情况下

图像金字塔这种技术和模型没有关系,随便哪个模型配上这个技术都可以去检测不同尺度的物体(检测多尺度物体的能力进一步增加)

  • 更多作为一种辅助手段来用,
  • 比如有个训练好的模型,加上图像金字塔,得到多个尺度下的框,然后把这些框收集起来,一起做NMS
  • 有些软件里也会有这种test time augmentation的操作

图像金字塔,只是一种辅助手段,更多还是希望从模型本身出发,来提高模型检测多尺度物体的一种能力


关于test time augmentation(测试时数据增强-TTA),

  • 通常我们说的数据增强是针对训练集的,主要是为了增加训练集样本数量,让模型看的更多
  • 而TTA是为了让模型对每个图像做出更多的预测,得到每个图像预测结果的合集。
  • 详见:使用 测试时数据增强(TTA)提高预测结果

1.3.3 基于层次化特征

卷积神经网络本身就是一种层次化结构

  • 深层的特征图对应的感受野就更大,抽象级别更高
  • 低层特征图对应的感受野小,位置精度就比较高。
  • 可以借助卷积神经网络本身天然的特征图层次结构特征,来检测不同尺度的物体,
  • 相对于图像金字塔,这种的计算成本就会低很多,不需要把输入图像resize好几遍,forward好几遍产生多次预测结果

这种方式:

  • 优势:计算成本低
  • 劣势:低层特征抽象级别不够,可能只能看到边缘,去预测物体类别还是有些困难。虽然定位精度高,但是语义信息比较弱
  • 改进方式:把高层特征融入低层特征,补充低层特征的语义信息,可以帮助进行小物体的预测
  • 这个思路就是特征金字塔的思路

1.3.4 特征金字塔(FPN)(2016)

特征金字塔:feature pyramid network。以右图为例:

  • 一般每次卷积,都是通道数加倍,特征图(空间分辨率)减半,所以卷积时,三个卷积的特征图的大小分别是原图的 1 / 2 1/2 1/2, 1 / 4 1/4 1/4和 1 / 8 1/8 1/8
  • 那么要把高层次特征(如最高层(3层))融入到低层次特征(次高层(2))里,即对于第二层来说:采取的操作
    1. 把上一级的高层次特征,进行2倍上采样,特征图分辨率X2。因为从2层→3层的时候,特征图减半,所以2层的特征图本身大小就是3层的2倍,因此需要把3层的特征图放大两倍,和当前2层特征图分辨率一致
    2. 由于2层→3层的时候,通道数加倍,因此2层的通道数是3层的一半,所以2层自己要通过一个1x1卷积把通道数加倍。
    3. 这样之后,2层的特征和3层的特征维度就一样了,就可以对应相加
  • 这种多尺度特征图融合,和之前人体姿态里讲的:HRNet,思路差不多,详见:OpenMMLab-AI实战营第二期——2-1.人体关键点检测与MMPose-2.2.4 HRNet(2019)

1.3.5 多尺度的密集预测


目前FPN已经是主流目标检测模模型里的一个标配了,把FPN放到密集预测里,整个网络的架构如上图,整体分为三个部分:

  • 主干网络(backbone:进行特征提取),比如:Resnet,5个stage就是5个级别的特征
  • 特征金字塔(neck:对主干网络的特征进行进一步处理),对多层次的特征进行融合(相邻层的高融合到低,没有跨层)
  • 多尺度密集预测头(head:适应具体的任务,分类头或者回归头等),比如:上面朴素密集检测是1x1卷积,实际上可能会是多层卷积
  • 推理:把所有尺度的前景框都拿出来,进行NMS后处理
  • 训练:这里有多个密集预测头,即有多个密集预测的推理结果,那么就需要多组真值(多尺度匹配产生多个密集预测的真值)。可以去看看上面 1.2.5部分的如何训练,就懂了。

2. 单阶段&无锚框检测器选讲

主要选一些实用性比较强的单阶段算法进行讲解


早期性能不行,所以主要是二阶段,后面单阶段性能上来了,就逐渐替代了二阶段,成为主流(17~20我上学的那三年还是二阶段比较火,工作之后单阶段逐渐就兴起了)

2.1 RPN(2015)

2.1.1 基本原理


RPN严格来说不是一个目标检测算法,是半个检测算法,是Faster RCNN的第一阶段,用来初步筛选图像中包含物体的位置,不预测具体的类别。

  • 可以认为是个简化的一类检测器,C=1,只检测前景和背景
  • RPN应该是第一个基于密集预测的模型

只包含两个部分:主干网络和检测头,不包含FPN的特征金字塔neck

  • 主干网络一开始用的是VGG16的前13个卷积层(前4个stage),后来Resnet出现了,就用ResNet的前4个Stage,经过4次降采样,所以降采样率=16(得到的特征图是原图的 1 / 16 1/16 1/16)
  • 检测头是1个3x3卷积和1个1x1卷积,前者进一步压缩特征,后者产生3个锚框的分类(2:前景+背景)和边界框回归预测(4个偏移值)。这里规定只生成3个锚框

  • 上面是VGG的结构,也是把蓝色的部分去掉,只用到14x14这个特征图做预测(224/14=16,下采样率=16)
  • 下面是ResNet的结构,把蓝色框的第五级stage去掉,只用14x14的这个特征图做密集预测

2.2.2 RPN-head代码


上面这个图的代码可能比较老一点,也更清晰一点。主要是forward部分,这个只是rpn网络的head,没有主干部分

# self.cls_out_channels=1或者2取决于使用BCE(Binary CrossEntropy Loss)还是CE loss(Cross Entropy),如果是2分类的CE loss,输出就是2
self.rpn_cls = nn.Conv2d(self.feat_channels,self.num_base_priors * self.cls_out_channels,1)
reg_dim = self.bbox_coder.encode_size # 这里就是4
self.rpn_reg = nn.Conv2d(self.feat_channels,self.num_base_priors * reg_dim, 1)def forward_single(self, x: Tensor) -> Tuple[Tensor, Tensor]:x = self.rpn_conv(x) # 1个3x3卷积,进一步计算特征x = F.relu(x) # 经过relu# 3x(2+4) 这里用了把之前的1x1卷积分成了两个,但是输入都是x,输出不一样,所以用了分开的两个1x1卷积表示# 上面的num_base_priors实际上就是锚框的个数rpn_cls_score = self.rpn_cls(x) # 1x1卷积,锚框的分类rpn_bbox_pred = self.rpn_reg(x) # 1x1卷积,锚框的回归return rpn_cls_score, rpn_bbox_pred

这个代码其实和之前 1.2.2 边界框回归部分的网络结构也很像


链接:mmdetection/mmdet/models/dense_heads/rpn_head.py

def _init_layers(self) -> None:"""Initialize layers of the head."""if self.num_convs > 1:rpn_convs = []for i in range(self.num_convs):if i == 0:in_channels = self.in_channelselse:in_channels = self.feat_channelsrpn_convs.append(ConvModule(in_channels,self.feat_channels,3,padding=1,inplace=False))self.rpn_conv = nn.Sequential(*rpn_convs)else:self.rpn_conv = nn.Conv2d(self.in_channels, self.feat_channels, 3, padding=1)self.rpn_cls = nn.Conv2d(self.feat_channels,self.num_base_priors * self.cls_out_channels,1)reg_dim = self.bbox_coder.encode_sizeself.rpn_reg = nn.Conv2d(self.feat_channels,self.num_base_priors * reg_dim, 1)
def forward_single(self, x: Tensor) -> Tuple[Tensor, Tensor]:x = self.rpn_conv(x)x = F.relu(x)rpn_cls_score = self.rpn_cls(x)rpn_bbox_pred = self.rpn_reg(x)return rpn_cls_score, rpn_bbox_pred

2.2.3 RPN-基于loU的匹配


链接:mmdetection/mmdet/models/task_modules/assigners/approx_max_iou_assigner.py

这里的bbox指的就是每个位置的每个Anchor(锚框),每个锚框生成一个分类结果和边界框回归结果。proposal,候选框,其实指的还是bbox(锚框)

这里主要介绍下大致步骤,详情可以去看代码:

  1. 把所有锚框的类别都设置为背景(初始化,给个默认值,可以认为是类别-1,不是负样本,可以认为一开始是个空值,不属于任何类别)
  2. 把那些和所有GT(真值标记框,有物体的框)的iou都小于neg_iou_thr(一般是个很小的值,比如0.3)的候选框设置成0,即这些框被认为是背景。一个框和所有有物体的真值框的重叠程度(交并比)都很小,比如低于0.3,就认为这个候选框是背景。
  3. 对每个框,如果这个框和离它最近的真值框(GT)的交并比大于某个阈值,那么就把这个GT对应的物体类别分配给这个框。可以在上面的代码链接页面搜索一下pos_iou_thr,就能看到说明了
  4. 经过第3步之后,还有一些GT框没有被使用过,直接把这些GT框的类别分配给离他们最近的锚框(可能会有多个)。要保证每个ground truth,

基于稀疏的标注框,为密集预测的结果产生真值,这个过程称为匹配(Assignment)

  • 但是这里基于IoU的匹配,是给锚框分配分类真值,一个锚框其实对应密集预测图中的一部分
  • 可以结合 1.2.5 如何训练 部分匹配的内容来理解这部分

2.2 YOLO(2005)

2.2.1 Yolo模型

和RPN同年,出现了yolo,

  • 图片出处:https://lilianweng.github.io/lil-log/2018/12/27/object-detection-part-4.html,这好像是个大佬,主页很好看,如果要基于github搭建博客,可以参考这个人的

Yolo也是一个单尺度模型,只有主干网络和检测头,没有多尺度模块

  • 主干网络是自己设计的19层的DartNet结构, 448 × 448 × 3 448\times448\times3 448×448×3 的输入,卷积后产生 7 × 7 × 1024 7\times7\times1024 7×7×1024的输出
  • 预测头是一个两层的全连接层,生成 7 × 7 7\times 7 7×7组预测结果,对应图中 7 × 7 7\times 7 7×7个空间位置上物体的类别和边界框的位置。
    • 预测头仍然保持特征图的空间结构(与主干网络输出的特征图空间分辨率一致,都是 7 × 7 7\times 7 7×7),输出是30个通道
    • 这30个通道包括:类别C=20(最早用于coco数据集,20分类),两组边界框(包括2个中心点的偏移和边界框的大小,1个Score/置信度(模型额外预测的,训练的时候实际上看的是预测框和真实框的IOU))

2.2.2 Yolo的匹配和框编码

YoLo的匹配和之前说的不太一样,这部分才是理解yolo的关键,

  • 整体思路:将输入图像按照模型的输出网格(比如7x7大小)进行划分,划分之后就有很多小cell了。我们再看图片中物体的中心是落在哪个cell里面,落在哪个cell哪个cell就负责预测这个物体

具体而言:

  1. 预测图是7x7,即预测图中一共可以有49个物体/位置的预测结果,则真值图也是7x7。
  2. 那么把原图也对应划分成7x7的格子(一般就是均分),但是注意:原图上1个格子≠1个像素,一般是一个小区域。因为原图的7x7和预测图的7x7肯定还是存在一些位置的对应关系(卷积位置不变性)
  3. 比如上图,狗的中心位于原图的红色格子,则生成密度预测真值时(匹配),
    • 就认为密度预测真值图对应的位置的红色格子的类别就是狗,
    • 同时该物体的标准框也就是对应的真值图上该位置的边界框真值(用偏移量来算, △ X , △ Y \vartriangle X, \vartriangle Y △X,△Y指是狗的中心到红色格子的边缘的偏移量;W和H指的是黄色那个窗相对于整个图像的比例)
    • score就是交并比
  4. 为了可以预测准一点,一般特征图上每一个cell都对应两组(B组)边界框的预测,不过算loss的时候,只算和真值框IoU最大的那个预测框,剩下的是不计算loss的
  5. B组边界框其实是为了预测不同大小的物体而设置的,同时背景的边界框也是不参与loss计算的

参考: