您现在的位置是:首页 >学无止境 >OpenMMLab-AI实战营第二期——4-1.目标检测与MMDetection网站首页学无止境

OpenMMLab-AI实战营第二期——4-1.目标检测与MMDetection

吨吨不打野 2024-09-25 00:01:03
简介OpenMMLab-AI实战营第二期——4-1.目标检测与MMDetection

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

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

1. 目标检测的基本范式

1.0-1 目标检测简介

在这里插入图片描述
定位+分类

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

在这里插入图片描述
推理精度


推理速度模型体积

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

1.0-2 基本概念

在这里插入图片描述

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

在这里插入图片描述
都是算重叠区域,和DICE很像,但是不一样,衡量的都是重叠程度,但是计算方式不一样。

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

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 感受野计算

参考:

在这里插入图片描述
感受野就是神经网络中,一个神经元能看到的原图的区域

  • 如右图,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

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

在这里插入图片描述

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

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

在这里插入图片描述

  • 对顶部的这个神经元(假设是 y y y)来说,假设跟下面的神经元层的连接权重都是1(也就是卷积核的值都是1),
  • 则Input中紫色框里最中间的那个(假设是 x 3 x_3 x3)的梯度,就是 ∂ y ∂ x 3 frac{partial y}{partial x_3} x3y=从 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} imes C_{out} Cin×Cout,则对应就可以找一个 1 × 1 1 imes 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} imes 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_channels
                else:
                    in_channels = self.feat_channels
                rpn_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_size
        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)
        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,

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

  • 主干网络是自己设计的19层的DartNet结构, 448 × 448 × 3 448 imes448 imes3 448×448×3 的输入,卷积后产生 7 × 7 × 1024 7 imes7 imes1024 7×7×1024的输出
  • 预测头是一个两层的全连接层,生成 7 × 7 7 imes 7 7×7组预测结果,对应图中 7 × 7 7 imes 7 7×7个空间位置上物体的类别和边界框的位置。
    • 预测头仍然保持特征图的空间结构(与主干网络输出的特征图空间分辨率一致,都是 7 × 7 7 imes 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计算的

参考:

2.2.3 Yolo的损失函数

在这里插入图片描述
这里的损失函数都是示性函数,想打出这个符号需要latex支持,CSDN的katex不怎么支持,详见:How do you get mathbb{1} to work (characteristic function of a set)?

  • 第一部分:表示有物体的位置,才去计算边界框loss(没有物体的位置,只计算分类损失),分别是中心和长宽计算(很少看到平方差了,基本都是cross entropy)
  • 第二部分:置信度回归损失,也是平方差loss

示性函数概念:

2.2.4 YOLO的优点和缺点

在这里插入图片描述

  • 优点就是快,最初版本的yolo已经基本可以达到实时的速度了,如果使用原始的9层DarkNet,速度非常宽,即便是VGG的YOLO,也有21FPS(每秒21帧)
  • 缺点就是因为是对原图做比较粗糙的划分格子,所以对于重叠物体,无法一个cell检测两个类别

2.3 SSD(2016)

在这里插入图片描述

上面介绍的RPN和YOLO都是基于单级特征图进行检测的(都是只使用了主干网络的最后一层的特征图),SSD是第一个尝试使用多级特征图去检测的模型

  • 使用了VGG的Conv4_3(VGG第四阶段的第三个特征图)+一些额外的卷积层产生的特征图
  • 基于每个特征图进行密集预测,不同特征图的锚框数量不同,比如:VGG的那个特征图是4个锚框,Conv7是6个锚框…
  • 最终在300x300的图像上,一共产生了有大有小的8732个锚框

在这里插入图片描述
为了给上面那8000多个锚框生成真值,SSD也采用了基于IOU的匹配规则。

  1. 首先,把每个真值框匹配到与其交并比最大的锚框
  2. 把剩余锚框匹配给与其交并比大于0.5的真值框进行匹配,一个真值框可以匹配给多个锚框。这样做也是为了增加正样本的个数,来抗衡训练中大量的负样本(背景样本)

2.4 Focal Loss与RetinaNet(2017)

在这里插入图片描述

从RetinaNet开始,FPN(特征金字塔)就成为了检测算法的一个主要模块。其主要结构:

  • Resnet主干网络,实际上只用了Resnet的3-5层,每层的特征图是原图的 1 / 8 1/8 1/8 1 / 16 1/16 1/16 1 / 32 1/32 1/32
  • Neck使用FPN进行多尺度检测,除了画出来的这三层,其实上面还有额外进行降采样的两层,分别是 1 / 64 1/64 1/64 1 / 128 1/128 1/128,可以用来检测特别大的物体。
    • 所以一共有5级特征图,对应降采样率是8~128倍。
    • 每级特征图设置3种尺寸x3种长宽比的锚框,覆盖32~813像素尺寸。
  • 5层卷积构成检测头(密集预测头),分成了两支,分别检测类别和边界框,
    • 在类别分支,WxH就是空间分辨率,K是类别数,A是锚框;在box分支,4A(就是为每个锚框预测4个偏移量)
    • 即为每个位置每个锚框产生K个类别预测和4个box偏移量预测
  • 提出了FocalLoss去计算分类损失,主要解决类别不平衡问题

在这里插入图片描述
RetinaNet当时出现也是引起了轰动,在COCO数据集上,单阶段超越当时最先进的两阶段Faster R-CNN。

在这里插入图片描述
当时单阶段算法面临的一个巨大的问题就是:正负样本不均衡问题

以SSD的图为例:

  • 原图的ground truth是非常稀疏的(需要检测的有物体的框很少),比如上图只有两个(猫和狗)
  • 即便可以把正样本匹配到多个锚框上,那也就只有几个锚框(正样本);而我们生成的锚框可能有成千上万个(SSD生成的锚框数量是8000多个),也就是除了那几个之外,都是负样本(背景)

在这里插入图片描述

  • 则在分类时,由于负样本数量过多,导致计算loss时,背景类占据了主导,导致模型偏向背景预测。
  • 即朴素(原始)的分类不损,不能保证检测器在有限的能力下,达到漏检和错检之间的平衡
    • 模型把所有框都预测成背景,正确率依然很高,所以直接错检也没啥
    • 即原始的这种分类损失和目标检测的目标并不匹配,目标检测更想要检测的是样本稀少的正样本,而不是均等的检测每个类

在这里插入图片描述

FocalLoss一开始就分析了一下损失函数的组成部分,

  1. 正样本框的损失
  2. 难负样本的损失(GT是负样本(背景),但是不好分的那种锚框)
  3. 简单负样本(容易分的负样本)

由于负样本占大多数,所以先分析负样本的损失,

  • 上图是交叉熵损失的曲线,横轴是模型把背景预测为负样本的概率,纵轴是损失
  • 理想情况下,希望背景预测为负样本的概率为1,此时负样本的损失就是0,对应上图最右边曲线和x轴的交点
  • 如果模型完全预测错误,即把背景预测成负样本的概率为0,那么此时损失就是无穷大
  • 但是模型不会那么准确,能得到把背景预测成负样本的概率是1,但是即便预测概率在0.6以上,图上可以看到多多少少也都还是有些损失。
  • 但是实际上,预测概率在0.6以上其实就够了,不是非要让模型预测成1才可以,这部分其实已经没必要参与loss的计算了
  • 对那些困难负样本,正确预测为负样本的概率就比较低,损失就会比较大,所以需要优化的这些困难负样本的概率。

但是实际上,简单负样本反倒是负样本中比例非常大的部分,虽然每个都只产生了一点点损失,但是其实它们才是负样本损失贡献最大的部分。

  • 导致模型优化的时候主要去进一步降低简单负样本的损失,而不是困难负样本的损失(以及正样本的损失)
  • 优化方向错误

在这里插入图片描述

FocalLoss就是在这个观察的基础上诞生的,

  • 主要就是在交叉熵损失函数前面加了一个系数,降低了简单负样本的损失,
  • 比如设定参数 γ gamma γ(是一个可调的参数),让预测背景为负样本的概率是0.6以上的基本就没有loss了,大大降低简单负样本在loss中的占比。
  • 让困难负样本和少量正样本的误差成为loss中的主导,纠正模型的优化方向

不同的 γ gamma γ会有不同的结果:

  • γ = 0 gamma=0 γ=0,FocalLoss退化成CE(cross entropy)
  • γ gamma γ越大,对简单负样本的损失的抑制越强
  • 通常 γ = 2 gamma=2 γ=2是一个经验性的比较好的结果

在这里插入图片描述

  • 完整的FocalLoss其实还有个系数,调节的事正负样本的比例。
  • 论文里给出的实验结论是: γ = 2 , α = 0.25 gamma=2, alpha=0.25 γ=2,α=0.25时会产生最好的实验结果。

2.5 YOLO系列选讲

2.5.1 YOLOv3(2018)

在这里插入图片描述
在RetinaNet和FocalLoss出现之后,单阶段有了希望。YOLOv3集百家之所长,出现了

  • 主干网络,DarkNet变成了53层,加入了ResNet模块,还加入了类FPN(特征金字塔网络)结构,CBL+上采样+concat,有特征融合(高层CBL经过上采样与低层RES8融合),这里是3级特征图(13x13, 26x26, 52x52),相对于原图降采样率就是32、16和8。RetinaNet是5级
  • head密集预测头,两层卷积组成(CBL+CONV),每个位置三个锚框,coco数据集那时候是80个类别,所以每级特征图对应位置要产生3x(80+4+1)=255个通道的预测值

在这里插入图片描述
YOLO就是快啊,画条横线,同等精度下,YOLO最快

2.5.2 YOLOv5(2020)

在这里插入图片描述

2.6 无锚框检测算法

2.6.1 重叠问题

在这里插入图片描述

在这里插入图片描述
YoLo就是无锚框的,所以以前无法解决重叠物体,但是FPN可以解决问题。

朴素的想法:同一个位置重合的物体,如果大小有所不同的话,实际可以由不同级别的特征图来进行预测的。

2.6.2 FCOS(2019)

在这里插入图片描述

  • FCOS就是基于:同一个位置重合的物体,如果大小有所不同的话,实际可以由不同级别的特征图来进行预测的。 这一想法来实现的。
  • 模型结构和RetinaNet大致相同,8~128倍降采样率,有多个密集检测头,可以进行多尺度物体的检测
  • 区别在于FCOS是无锚框的,所以预测目标和RetinaNet就会有所不同
    • 分类是差不多的,
    • 主要是边界框,之前RetinaNet是真实位置相对于锚框的偏移量;现在FCOS是物体上下左右边界相对于中心的偏移量,centerness和yolo里框的质量/置信度差不多是一个作用,评判当前这个中心点是不是在物体最中心的位置,在中心,值就是1,在边上,值就是0

在这里插入图片描述
为了适应无锚框的算法,FCOS提出了一种新的匹配规则(生成密集预测真值图的方式)
相对于之前的锚框基于IOU的匹配,FCOS的匹配会生成更多的正样本,正样本多一点,对训练来说,是好事。

在这里插入图片描述

  1. 重叠的物体一般不会一模一样大,一模一样肯定就有个看不见的,看不见的就没必要预测了
  2. 假设有个位置,有1大1小两个预测框,怎么去判断取哪个?
    • 如果这个位置对应的特征图是P3,那么就预测小物体
    • 对应的是高层特征图,就预测大物体。
    • 即:锚框匹配同尺度的真值框(同级别)

在这里插入图片描述

  • 中心度定义:是横竖两个方向中心度的几何平均值, l ∗ , r ∗ l^*, r^* l,r分别表示中心点到左侧边界和右侧边界的距离

在这里插入图片描述

2.6.3 CenterNet(2019)

在这里插入图片描述

2.6.4 YOLOX(2021)

在这里插入图片描述
和同期的YOLOv5在精度和速度上都是差不多的

2.6.5 YOLO v8

在这里插入图片描述
把YOLO系列上了一个新台阶

2.7 总结

在这里插入图片描述

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。