Object Detection

重新开始细致学习Object Detection的问题,包括之前遗漏的方法(只大概懂皮毛)。回归到初始,不要着急呀。

建议参考知乎专栏,机器学习算法工程师,比一些机翻的好很多。

整体架构

核心组件:Backbone(提取特征)+neck+多尺度head+正负样本定义+正负样本平衡采样+loss

Backbone

在mmdetection中通常都会采用frozen_stages参数固定前n个stage的权重。因为研究表明前几层特征都是基础通用特征,可以不用重头训练,不仅可以省点内存也可以加速收敛。

AleNet

  • 采样用Relu【训练速度更快,sigmoid有梯度消失问题】
  • 5个卷积层+3个全连接层
  • 每个全连接层后加入了dropout,减少了过拟合

VGG

采用连续的几个3x3的卷积核代替AlexNet中的较大卷积核;因为多层非线性层可以增加网络深度来保证学习更复杂的模式,而且代价还比较小(参数更少)。

GoogLeNet

使用密集结构来近似一个稀疏的CNN,激活值之间是稀疏连接的;使用了一中瓶颈层(实际上就是1x1卷积)来降低计算量;使用全局均值池化层替换了全连接层,大大减少了模型的总参数量。

DarkNet

darknet是一个类似于TensorFlow、PyTorch的框架;也是一个类似于AlexNet、VGG的模型(只不过两个名字相同而已)

DenseNet

Densenet密集跨层跳层连接思想

CSPNet

部分局部跨层融合

ResNext

group convolution

ResNeSTt

融合了attention的思想

HRNet

EfficientNet

Deformable Convolutional Networks

不是一个Backbone,但是是一个卷积的改进版本,可以更改在各种Backbone网络之上。可以看作是空洞卷积的进一步扩展。在此基础上还提出了新的ROI Pooling方法——Deformable ROI pooling。

特点

  • 一般用在提取到一定语义特征之后使用。即网络靠后的几层中
  • 对物体的形变和尺度建模的能力较强
  • 感受野比一般卷积大很多(因为有偏移)

解决的问题

  • 常规的卷积得到的都是矩形框。对于不规则的目标建模有非常大的局限性。而CNN不具有旋转不变性和尺度不变性,因此对于CNN来说一般都是通过大量的训练样本来让训练器强行记忆的

算法

通常卷积是

就是说用卷积中的一点和输入中在中心点同样增加方位点的乘积之和。

而可变形卷积就是增加了一个偏移量

由于偏移量往往是小数,所以采用了双线性差值(接近的点才对线性差值有贡献)。

网络学习的是变量,来控制偏移量的大小

Neck

这里介绍的都是网络,针对于多尺度的融合问题;其中的一个部分作为Neck,为了连贯性,因此将一些多尺度的融合方法也放入其中。

FPN——2016

解决的问题

  • 目标的尺寸不同,因此对于小目标的结果并不好;由此提出同时利用低层特征高分辨率高层特征的高语义信息,通过融合这些不同层的特征达到预测的效果,并且预测是在每个融合后的特征层上单独进行的。

缺点

  1. 不同尺度之间存在语义gap,即某个gt bbox只会分配到某一个特定层,而其余层级对应区域会认为是背景(但是其余层学习出来的语义特征其实也是连续相似的,并不是完全不能用的);如果图像中包含大小对象,则不同级别的特征之间的冲突往往会占据要素金字塔的主要部分,这种不一致会干扰训练期间的梯度计算,并降低特征金字塔的有效性。

  2. 梯度计算如下

    由于不同层之间是一个尺度变换关系,因此可以认为$\frac{\partial \mathbf{x}_{i j}^{1 \rightarrow l}}{\partial \mathbf{x}_{i j}^{1}}$是一个常数;简单我们用1表示;而$\frac{\partial \mathbf{y}_{i j}^{l}}{\partial \mathbf{x}_{i j}^{1 \rightarrow l}}$是一个activation的操作的梯度值,也是一个常数,因此可以简化为

    在不同层特征图(i,j)位置上的目标很可能同时包含正负样本,这样这个不连续性会对梯度结果产生影响,降低训练的效率

结构

  • 自下而上:低层次特征通过下采样,得到高层特征的高语义信息。【C1~C5】
  • 自上而下:高层次特征通过2x上采样,通过最近邻插值【将高层次小特征图放大】,改变h, w【P5~P3】
  • 横向连接:【由于C1的特征图尺寸较大,且语义信息不足,因此C1没有用横向连接】
    • 将低层次特征用1x1卷积层降维,改变channel个数【C2~C5】
    • 这样低层次和高层次特征维度一致,直接逐元素相加
    • 将融合特征通过3x3卷积以消除上采样带来的不利影响【P2~P4】

算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import torch.nn as nn
import torch.nn.functional as F
import math
class Bottleneck(nn.Module):
expansion = 4
def __init__(self, in_planes, planes, stride=1, downsample=None):
super(Bottleneck, self).__init__()
self.bottleneck = nn.Sequential(
nn.Conv2d(in_planes, planes, 1, bias=False),
nn.BatchNorm2d(planes),
nn.ReLU(inplace=True),
nn.Conv2d(planes, planes, 3, stride, 1, bias=False),
nn.BatchNorm2d(planes),
nn.ReLU(inplace=True),
nn.Conv2d(planes, self.expansion * planes, 1, bias=False),
nn.BatchNorm2d(self.expansion * planes),
)
self.relu = nn.ReLU(inplace=True)
self.downsample = downsample
def forward(self, x):
identity = x
out = self.bottleneck(x)
if self.downsample is not None:
identity = self.downsample(x)
out += identity
out = self.relu(out)
return out
class FPN(nn.Module):
def __init__(self, layers):
super(FPN, self).__init__()
self.inplanes = 64
self.conv1 = nn.Conv2d(3, 64, 7, 2, 3, bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(3, 2, 1)
self.layer1 = self._make_layer(64, layers[0])
self.layer2 = self._make_layer(128, layers[1], 2)
self.layer3 = self._make_layer(256, layers[2], 2)
self.layer4 = self._make_layer(512, layers[3], 2)
self.toplayer = nn.Conv2d(2048, 256, 1, 1, 0)
self.smooth1 = nn.Conv2d(256, 256, 3, 1, 1)
self.smooth2 = nn.Conv2d(256, 256, 3, 1, 1)
self.smooth3 = nn.Conv2d(256, 256, 3, 1, 1)
self.latlayer1 = nn.Conv2d(1024, 256, 1, 1, 0)
self.latlayer2 = nn.Conv2d( 512, 256, 1, 1, 0)
self.latlayer3 = nn.Conv2d( 256, 256, 1, 1, 0)
def _make_layer(self, planes, blocks, stride=1):
downsample = None
if stride != 1 or self.inplanes != Bottleneck.expansion * planes:
downsample = nn.Sequential(
nn.Conv2d(self.inplanes, Bottleneck.expansion * planes, 1, stride, bias=False),
nn.BatchNorm2d(Bottleneck.expansion * planes)
)
layers = []
layers.append(Bottleneck(self.inplanes, planes, stride, downsample))
self.inplanes = planes * Bottleneck.expansion
for i in range(1, blocks):
layers.append(Bottleneck(self.inplanes, planes))
return nn.Sequential(*layers)
def _upsample_add(self, x, y):
_,_,H,W = y.shape
return F.upsample(x, size=(H,W), mode='bilinear') + y
def forward(self, x):
c1 = self.maxpool(self.relu(self.bn1(self.conv1(x))))
c2 = self.layer1(c1)
c3 = self.layer2(c2)
c4 = self.layer3(c3)
c5 = self.layer4(c4)
p5 = self.toplayer(c5)
p4 = self._upsample_add(p5, self.latlayer1(c4))
p3 = self._upsample_add(p4, self.latlayer2(c3))
p2 = self._upsample_add(p3, self.latlayer3(c2))
p4 = self.smooth1(p4)
p3 = self.smooth2(p3)
p2 = self.smooth3(p2)
return p2, p3, p4, p5

PANet——2018

解决的问题

  • FPN自底向上的过程,使得特征信息在上传时丢失的较为严重;而低层次的信息包含大量边缘形状等特征,这对于像素级别的分类任务(如实例分割)是起到至关重要的作用的。
  • ROI经过ROI Pooling或者ROI Align提取ROI特征,每个ROI所基于的特征都是单层特征

结构

  • 缩短信息路径和用低层级的准确定位信息来增强特征金字塔,创建了自下而上的路径增强
    • 相当于FPN是在向下采样的卷积后增加了自上而下的P模块以及横向连接
    • 而PAN是在FPN之后增加了自下而上的N模块以及横向连接
    • 这里的P2到N2没有经过变换
    • N3~N5是和P3~P5的融合结果
  • 为了恢复每个建议区域和所有特征层级之间被破坏的信息,作者开发了适应性特征池化(adaptive feature pooling)技术,可以将所有特征层级中的特征整合到每个建议区域中,避免了任意分配的结果。
    • 每个ROI和多层特征做ROI Align操作,将不同层的ROI特征融合在一起。这样得到的ROI特征是多层的。
  • 全连接融合层:使用一个小型fc层用于补充mask预测
    • 增加了一个前景二分类全连接支路

M2Det——2019

解决的问题

  • 原本 backbone 是用于目标分类的网络,导致用于目标检测的语义特征不足;
    • 分类子网络来说更深更高的层更容易区分,对定位的回归任务来说使用更低更浅的层比较好
    • 底层特征更适合描述具有简单外观的目标,而高层特征更适合描述具有复杂外观的目标
  • 每个用于目标检测的特征层主要或者仅仅是由单级特征层(single-level layers)构成,也就是仅仅包含了单级信息

结构

  • 主干网络提取特征
  • MLFPN
    • 特征融合模块FFM将浅层和深层特征进行融合【使用两种不同scale的feature map作为输入,所以在拼接操作之前加入了上采样操作来调整大小】,得到base feature
    • 堆叠多个细化U型模块TUM和FFM(和上一条的FFM是两个独立但是相同的模块)提取更有代表性的Multi-level&Mutli-scale特征
      • 每个TUM可以产生多个不同scale的feature map,在上采样和元素相加操作之后加上1x1卷积加强学习能力和保持特征平滑度
      • 每个FFMv2融合base feature上一个TUM的输出,并给到下一个TUM作为输入(更高level)。
    • 尺度特征聚合模块SFAM融合多级特征,通过scale-wise拼接和channel-wise attention来聚合multi-level&multi-scale的特征,得到多级特征金字塔用于最终阶段的预测
      • 沿着channel维度将拥有相同scale的feature map进行拼接,这样得到的每个scale的特征都包含了多个level的信息
      • 借鉴SENet的思想,加入channel-wise attention,以更好地捕捉有用的特征。
  • 采用类似SSD的方式预测密集的包围框和类别得分
  • soft-NMS得到最后的检测结果

ASFF:Adaptively Spatial Feature Fusion——2019

解决的问题

  1. 不同尺寸特征用concat或者add的融合方式并不够科学;于是本文提出自适应融合,自动找出最合适的融合特征。
  2. FPN的梯度是可能会同时包含正负样本的,所以会对训练有影响;而ASFF的梯度是由权重来控制的,因此对于正负样本同时在梯度计算的时候,可以设置对应的权重为0,这样负梯度就不会对结果产生干扰。

结构

  • add基础上增加了一个自动学习的参数,实现自适应融合效果,类似于全连接参数

  • identically rescaling:为了融合不同层级之间的特征,首先要进行上采样或者下采样,得到同等空间大小的特征图。

    • 上采样:1x1卷积进行通道压缩,然后双线性插值
    • 下采样:用1/2特征图进行3x3 conv with stride of 2;用1/4特征图进行max pooling with stride of 2
  • adaptively fusing:

    用level为1,2,3的特征图转换到level l,前面超参数表示一个重要度权重,他们的加和为1.并且满足:

算法

  1. 首先对于第l级特征图输出c*h*w,对其余特征图进行上下采样操作,得到同样大小和channel的特征图,方便后续融合
  2. 对处理后的3个层级特征图输出,输入到1*1*n的卷积中(n是预先设定的),得到3个空间权重向量,每个大小是n*h*w
  3. 然后通道方向拼接得到3n*h*w权重融合图 (只是权重连接)
  4. 通道方向softmax操作,进行归一化,将3个向量乘加到3个特征图上面,得到融合后的c*h*w特征图 (特征图带权连接)
  5. 采用3*3卷积得到输出通道为256的预测输出层

EfficientDet

特点

  • 可学习参数的自适应加权融合

结构

  • 双向FPN
    • Top-down
    • down-top

NAS-FPN

Anchor

参数

  • octave_base_scale: 每层特征图的base anchor scale;增大,则整体anchor都会放大
  • scales_per_octave: 每层anchor尺度的个数,为2**0, 2**(1/n), ..., 2**(n-1/n)
  • ratios:每层anchor的长宽比
  • Strides:每个特征图层输出stride,故anchor范围为[octave_base_scale* 2**0 * strides[0], octave_base_scale* 2**(n-1/n) * strides[-1]

生成方式

sliding window
  • 定义K个特定尺度和长宽比
  • 在图上以一定的步长滑动

RPN

  • 分类:二分类,区分前景和背景
  • 回归:对前景样本进行基于anchor的变换回归

ROI

两次量化

  • 映射:在框【就是ROI框对应到网格化的框中】进行量化的时候,会丢失一部分信息和增加一部分噪声。(考虑不同位置的四舍五入情况)
  • Pooling的Kernel大小可能不能被featmap整除。在量化的过程中可能会丢失(右/下)信息。
Deformable ROI Pooling

n表示在这个bin中pixels的个数。

学习的是变量,来控制偏移量的大小,防止超出ROI

ROI Wraping

RCNN

  • 分类:类别数+1(背景)
  • 回归:对前景样本不考虑分类类别进行ROI Refine

Loss

不同场景下我们会使用合适的loss,比如:

  • 线性回归,MSE Loss【可以处理分类「但是01标签难收敛」/回归问题】

    实际中分布是不均衡,不是正态分布,应该是满足二元交叉熵分布的。但是在回归问题基本不会考虑不均衡的问题,因为标签是连续的。但是会存在标签分布诡异的现象(比如奢侈品消费)。在处理回归问题时,会发现用对数变换效果变好。如果标签存在负数,则标签加上min_data+1,再log变换即可。

  • 逻辑回归二分类,二元交叉熵

使用特定损失函数的前提是对标签的分布进行了某种假设,在这种假设的前提下通过极大似然法推出所有样本构成的极大似然公式,再使用凸优化的方法,比如梯度下降法求解。

分类

BCE Loss

二分类交叉熵损失函数(一般在此之前会经过sigmoid层,得到0或者1的标签)

可以简写为

但是由于负样本过多,刚开始的loss会很大,而且不稳定,容易出现梯度爆炸的现象;即使降低分类的权重来避免梯度爆炸,但是模型会倾向于预测负样本。

但是论文“Is Sampling Heuristics Necessary in Training Deep Object Detectors?”指出,适当调整模型的初始化和loss权重比例,是可以达到FL 类似的效果的。

二分类问题与伯努利分布

二分类问题,常见的假设就是标签服从伯努利分布

伯努利分布/两点分布是一个离散型分布。其表示,如果成功,随机变量取值为1;如果失败,随机变量取值为0。

成功几率为p,那么相应失败的几率为1-p。可以表示为:

可以统一表示为

N次试验之后,成功期望为N*p,方差为N*p*(1-p)

假设观察到的数据是,那么极大似然的目标:

同时在这里,引入一个假设——独立同分布(i.i.d.),目标公式即变为:

由于乘法,我们可以用log,不改变取值点的同时,又可以将运算变为加法。

这样就是二元交叉熵公式了。

Focal Loss——2018

将大量易学习样本的loss权重降低,但是不丢弃样本,突出难学习样本的loss权重,但是因为大部分易学习样本都是负样本,所以顺便解决了正负样本不平衡问题。

实验说明gamma取2的时候,结果最好。

当y=0,即为负样本时,

  • 它的置信度很低p【表示确定为正例的概率很低,即确定为负样例的概率很高】(接近1),那么调节因子就是接近0,损失函数权重很低;即易学习的不太关注;
  • 它的置信度很高,即p接近0.5时,那么调节因子很高是要关注的

当y=1,即为正样本时,

  • 它的置信度很低p,表示难学习的正样本,这时调节因子很高
  • 它的置信度很高,表示易学习的正样本,调节因子很低
1
2
3
4
p = torch.sigmoid(inputs)
ce_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduction="none")
p_t = p * targets + (1 - p) * (1 - targets)
loss = ce_loss * ((1 - p_t) ** gamma)

回归

Smooth L1

Balanced L1 Loss

平衡回归loss的目的是既不希望放大外点对梯度的影响,又要突出内点中难负样本的梯度,从而实现对外点容忍,对内点区分难负样本的作用。

in which the parameters $\gamma, \alpha,$ and $b$ are constrained by

MSE Loss

线性回归问题与正态分布

在使用线性回归的时候,基本假设是噪声服从正态分布,根据正态分布的概率密度函数可以得出,因变量y则服从正态分布

正态分布

同样的我们的目标是使得这个正态分布在数据集D下(在假设独立同分布下,即连乘的极大似然估计)最大。

同样取对数来简化为加法

两个变量,用求偏导的等于0来求得参数的取值:

由于服从正态分布,那么希望均值u和方差趋近于0。看表达式也可以知道,当两者趋向于0时,对数似然函数越大。于是就等价于均方误差了。

IOU Loss

  • 具有尺度不变性,对尺度不敏感
  • 如果两个框没有相交,根据定义,IoU=0,不能反映两者的距离大小(重合度)。同时因为loss=0,没有梯度回传,无法进行学习训练。
  • IoU无法精确的反映两者的重合度大小。比如对于A和B有同样的IOU,那么只能说明两者的重叠面积一样,这就有无数多种,只需要让框旋转一下,但是这个回归的效果是不同的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def Iou(box1, box2, wh=False):
xmin1, ymin1, xmax1, ymax1 = box1
xmin2, ymin2, xmax2, ymax2 = box2
# 获取矩形框交集对应的左上角和右下角的坐标(intersection)
xx1 = np.max([xmin1, xmin2])
yy1 = np.max([ymin1, ymin2])
xx2 = np.min([xmax1, xmax2])
yy2 = np.min([ymax1, ymax2])
# 计算两个矩形框面积
area1 = (xmax1-xmin1) * (ymax1-ymin1)
area2 = (xmax2-xmin2) * (ymax2-ymin2)
inter_area = (np.max([0, xx2-xx1])) * (np.max([0, yy2-yy1])) #计算交集面积
iou = inter_area / (area1+area2-inter_area+1e-6)  #计算交并比
return iou

GIOU Loss——2019

先计算两个框的最小闭包区域面积(通俗理解:同时包含了预测框和真实框的最小框的面积),再计算出IoU,再计算闭包区域中不属于两个框的区域占闭包区域的比重,最后用IoU减去这个比重得到GIoU。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def Giou(box1, box2):
xmin1, ymin1, xmax1, ymax1 = box1
xmin2, ymin2, xmax2, ymax2 = box2
iou = Iou(box1,box2)
area_C = (max(xmin1,xmin2,xmax1,xmax2)-min(xmin1,xmin2,xmax1,xmax2))*(max(ymin1,ymin2,ymax1,ymax2)-min(ymin1,ymin2,ymax1,ymax2))
area_1 = (xmax1-xmin1)*(ymax1-ymin1)
area_2 = (xmax2-xmin2)*(ymax2-ymin2)
sum_area = area_1 + area_2
w1 = x2 - x1 #第一个矩形的宽
w2 = x4 - x3 #第二个矩形的宽
h1 = y1 - y2
h2 = y3 - y4
W = min(xmin1,xmin2,xmax1,xmax2)+w1+w2-max(xmin1,xmin2,xmax1,xmax2) #交叉部分的宽
H = min(ymin1,ymin2,ymax1,ymax2)+h1+h2-max(ymin1,ymin2,ymax1,ymax2) #交叉部分的高
Area = W*H #交叉的面积 就是IOU的inter_area
add_area = sum_area - Area #两矩形并集的面积
end_area = (area_C - add_area)/area_C #闭包区域中不属于两个框的区域占闭包区域的比重
giou = iou - end_area
return giou

DIOU Loss

将目标与anchor之间的距离,重叠率以及尺度都考虑进去,不会像IoU和GIoU一样出现训练过程中发散等问题。

第二项是:中心点坐标的欧式距离平方与闭包的对角线长度平方的比值

收敛更快、回归快。

CIOU Loss

考虑了长宽比

长宽在[0, 1]下,会造成梯度爆炸,实现时暂时当作1

综合

GHM loss——2018

本质上和FL一样,是通过对易学习样本降低权重;但是它是从梯度范数角度出发的;不仅实现了FL效果,还具备克服外点样本影响

  • 外点数据【标注错误的数据】,norm loss接近1,这时候对于CE、FL它的梯度非常大,但是实际上应该要忽略;所以GHM对loss两端的梯度降低了权重

  • 但是由于其内部需要定义梯度范数函数不通用,不同的loss函数可能需要重新定义;

    梯度密度函数

    意义:设定梯度值分布间隔(论文中bin=30个,30个区间),对g的纵坐标进行均匀切割;然后统计每个区间内的样本个数,除以单位取值区域内分布的样本个数。【一定范围置信度p的样本数量,这个范围就可以把离群点给除外了】

    把梯度的倒数作为样本权重

    • 分类:BCE Loss

      定义梯度范数

    • 回归:Smooth L1

      由于当差值大于delta后是一个固定值,无法反映难易程度。因此采用了等效公式ASL:

      mu设置为0.02;因为回归分支的输入全部是正样本,因此易学习的样本也不能把权重降的很低。

  • 由于某些样本对梯度的波动,引入了Exponential Moving average(EMA)操作

OHEM

通过对loss排序,选出loss最大的example来进行训练,这样就能保证训练的区域都是hard example。

这个方法的缺陷,是把所有的easy example(包括easy positive和easy negitive)都去除掉了,造成easy positive example无法进一步提升训练的精度(表现的可能现象是预测出来了,但是bbox不是特别准确),而且复杂度高影响检测效率。

GFL——2020

QFL:将分类分支与bbox的质量评估分支联合训练

y是[0,1]范围的质量标签,来自预测和真实的bbox的IOU值。如果是负样本,则y=0。是分类分支经过sigmoid后的预测值。

QFL的全局最小解是,这样后面部分就是交叉熵。前面是调节因子(用距离绝对值的幂次函数),当最优。


DFL,希望网络能够快速地聚焦到标注位置附近的数值,使得这个位置的概率仅可能的大。

其中,是浮点值y的左右整数值,S是分布。

通过计算可以得到全局最优解为:

也就是说我希望学出来的分布(全局最优解)应该是以线性插值的模式得到距离左右整数坐标的权重。

代码实现里是

Optimizer

Adam

SGD

正负样本

定义

Intersection over Union

将pred与gt计算IOU值,如果超过统一的阈值,则为正样本;否则为负样本。

MaxIoUAssigner
  1. 首先初始化时候假设每个anchor的mask都是-1,表示都是忽略anchor
  2. 计算每个anchor和所有gt的iou值;对于每一个anchor将最大IoU进行比较:小于neg_iou_thr的anchor的mask设置为0,表示是负样本;大于等于pos_iou_thr,则设置该anchor的mask设置为1
  3. 对于每个gt找出和它最大iou的anchor,如果iou大于min_pos_iou,将该anchor的mask设置为1,表示该anchor负责预测对应的gt。最大程度保证每个gt都有anchor负责预测。【引入低质量anchor,可能会带来噪声】

假如有两个gt,一个anchor,anchor与gt1的iou为0.75,与gt2为0.5,按照第2条,anchor会匹配给gt1,但在第3条anchor与gt2的iou大于min_pos_iou会将其分配给gt2;这种情况下,会重新分配anchor,gt1对应的anchor就不是原来那个了,而是分配给了gt2。这也就是mmdet v2版本里面新增的一个控制参数,通过该参数可以防止出现这种问题

每个gt可能和多个anchor进行匹配,每个anchor不可能存在和多个gt匹配的场景。

1
overlaps = bbox_overlaps(gt_bboxes, bboxes)
ApproxMaxIoUAssigner
  1. 利用每个位置的x个anchor设定,计算这x个anchor和gt的IOU值
  2. 在这x个anchor中max,选出IOU最大的那个
  3. 然后用这个max IOU值计算MaxIOUAssigner。
1
2
3
4
5
approxs = torch.transpose(approxs.view(num_squares, approxs_per_octave, 4), 0, 1).contiguous().view(-1, 4)
all_overlaps = bbox_overlaps(approxs, gt_bboxes)
overlaps, _ = all_overlaps.view(approxs_per_octave, num_squares, num_gts).max(dim=0)
overlaps = torch.transpose(overlaps, 0, 1)
PointAssigner
  1. 计算gt bbox宽高落在哪个尺度
  2. 遍历每个gt bbox,找到其所属的特征图层;
  3. 先计算特征图上任何一点距离gt bbox中心点坐标的距离;
  4. 用topk算法选择出前pos_num个距离gt bbox最近的特征图点,这pos_num个都算正样本
  5. 如果有两个gt bbox的中心点重合且映射到同一个输出层,那么会出现后遍历的gt bbox覆盖前面gt bbox;【不用额外处理】
  6. 如果topk取得比较大,可能会出现fcos里面描述的模糊样本,处理办法就是距离哪个gt bbox中心最近就负责预测谁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
gt_bboxes_lvl = ((torch.log2(gt_bboxes_wh[:, 0] / scale) + torch.log2(gt_bboxes_wh[:, 1] / scale)) / 2).int()
points_lvl = torch.log2(points_stride).int()
lvl_min, lvl_max = points_lvl.min(), points_lvl.max()
gt_bboxes_lvl = torch.clamp(gt_bboxes_lvl, min=lvl_min, max=lvl_max)
points_gt_dist = ((lvl_points - gt_point) / gt_wh).norm(dim=1)
min_dist, min_dist_index = torch.topk(
points_gt_dist, self.pos_num, largest=False)
min_dist_points_index = points_index[min_dist_index]
less_than_recorded_index = min_dist < assigned_gt_dist[min_dist_points_index]
min_dist_points_index = min_dist_points_index[less_than_recorded_index]
assigned_gt_inds[min_dist_points_index] = idx + 1
assigned_gt_dist[min_dist_points_index] = min_dist[less_than_recorded_index]

Spatial and Scale Constraint

先利用scale ratio来确定gt分配到哪一层,然后利用center sampling策略来确定哪些位置是正样本。由于其会产生更多的正样本区域,所以性能比IoU更好

采样

1
2
3
pos_iou_thr: 0.5~0.7
neg_iou_thr:
min_pos_iou:

Hard sampling,选择难负例样本

OHEM:Online Hard exampling mining
RandomSampler

由于正负样本不一致,因此需要采样;正负样本用不同阈值进行采样

IOUBalancedNegSampler

由于负样本本身IoU就不平衡,因此随机采样得到的大部分样本都是易学习的负样本。

而用loss排序来采样的方法——OHEM,会对噪声数据敏感,而且参数比较难调。

核心操作是对负样本按照iou划分k个区间,每个区间再进行随机采样,保证易学习负样本和难负样本比例尽量平衡。

Soft sampling,赋予样本不同权重值

Focal Loss

平衡loss

见loss部分

Train

知识蒸馏

EMA

数据增强

Test

NMS

Soft NMS

Adaptive NMS

Head

Anchor free

特点

  1. 去掉了anchor,免去了设置anchor超参数的问题;实际上可以看作是anchor个数为1且为正方形的anchor-based类算法

缺点

  1. 理论上anchor设置越密集,召回率会越高;因此anchor free方法会专注于recall的值【即它的recall很低】

Anchor based

缺点

  1. 超参太多,其中anchor的设置对结果影响很大;不同任务的超参都需要根据经验来确定,比如:不同尺度、不同长宽比
  2. 正负样本不平衡问题(但有Focal Loss的方法),需要计算anchor和gt的IOU值来判断正负样本
  3. 框与对应feature的misalignment(但有FSAF、Guided Anchoring),提供的是粗略的定位吗,并不完全拟合目标的形状和姿态,框会包含无关或噪声的信息
  4. scale invariance(但有分组选择、空洞卷积)
  5. 优化目标与inference不一致(但有Consistent Optimization for Single-shot object detection)
  6. 两次roi pooling量化取整(但有ROI Align)
  7. 预训练模型数据集与实际数据集分布不一致,domain-shift(但有SNIP)
  8. IOU mismatch(但有Cascade RCNN)
  9. nMS暴力删除候选框问题(但有soft-nms)

One-Stage

使用固定网格, anchor -> detection bbox

Anchor free

Yolo v1

DenseBox——2015

特点

  • 直接在图像位置上预测出目标的边界框(不需要anchor、proposal)
  • 利用FCN,结合关键点【heatmaps】检测(增加分支),提高了目标检测的精度
  • 整张图片是没必要的,裁剪包含一个人脸【人脸bbox的中心半径0.3倍的圆形确定】和部分背景,然后缩放为统一的尺寸,更加关注尺寸小和严重遮挡的目标
  • 为避免负样本过多导致模型倾斜于负样本,用二值mask图来决定像素是否为训练样本【没有脸的图像在上一点就保证了不会送入】
  • 忽略灰度区域(正负模糊)

缺点

  • 要求让training image patches to be resized to a fixed scale;即image pyramids(图片压缩为不同尺寸送入网络)影响效率

结构

  • 生成特征金字塔
  • 送入多层conv、pooling、upsampling【上采样使得计算的大小降低了许多】后得到输出
  • NMS得到结果

UnitBox——2016

特点

  • 运用于人脸检测
  • 提出了IOU loss
    • 有尺度不变性
    • l2的缺点:
      • 四个预测变量l,r,f,b是独立的
      • 距离接近,但是可能是不可接受的
      • 大bbox有更大的影响(惩罚)

CornerNet

CenterNet

使用更大分辨率的输出特征图(缩放了4倍),本质上是因为其采用关键点检测思路做法,而关键点检测精度要高,通常是需要输出高分辨率特征图,同时不需要多尺度预测。其输出预测头包含3个分支。

  • 分类
  • offset:由于输入和输出相差x倍,而在特征图上的坐标会量化为整数,相当于在输出在实际图上会有差距。如果下采样x越大,那么量化误差越大。因此用offset分支来学习量化误差,提高预测精度。
  • wh

对正样本附近增加惩罚,基于2d高斯分布来降低这部分权重,相当于起到了类似于忽略区域的作用。

$L_{k}=\frac{-1}{N} \sum_{x y c}\left\{\begin{array}{cc}\left(1-\hat{Y}_{x y c}\right)^{\alpha} \log \left(\hat{Y}_{x y c}\right) & \text { if } Y_{x y c}=1 \\ \left(1-Y_{x y c}\right)^{\beta}\left(\hat{Y}_{x y c}\right)^{\alpha} & \\ \log \left(1-\hat{Y}_{x y c}\right) & \text { otherwise }\end{array}\right.$
其中

  • 当Y=1时候,也就是正样本位置,就是标准的focal loss设定;
  • 附近区域【在2d高斯分布内部】采用了自适应宽高标准差的做法;附近区域中越靠近中心点的惩罚Y越大,Loss权重越小,表示该位置对于正还是负的区分越模糊
  • 外围区域,标准的focal loss定义

ExtremeNet

Point-based

特征图上每一个点都进行分类和回归预测.

FCOS:A simple and strong anchor-free object detector——2019

特点
  • 增加了centerness分支,对框回归的结果进行了质量评估,对于后续NMS排序作用很大;对正样本区域按照距离gt bbox中心来设置权重
结构
  • ResNet输出四个特征图
  • 对这三层进行1x1改变通道,输出256个通道
  • 最近邻上采样add操作进行特征融合
  • 3x3卷积得到p3p4p5特征图
  • C5 3x3卷积,stride=2下采样得到p6
  • P6 3x3卷积,stride=2下采样得到p7
算法
  1. 多尺度预测输出,需要首先考虑gt由哪一个输出层负责

  2. 对于特征图上任何一点都回归其距离bbox的4条边距离。

  3. 定义正负样本

    • 设置center_sampling_ratio=1.5,确定对于任意一个输出层距离bbox中心多远的区域属于正样本,(即中心点向外多少是属于正样本的,扩展后的区域当作正样本,若扩展大于原区域,则截断)【那么对于不同输出层,由于缩放s不同,因此可以处理多尺度的问题】

      • 减少模糊样本数目
      • 减少标注噪声干扰

      b: FCos, c: centernet d: Training-Time-Friendly Network for Real-Time Object Detection

    • 设置regress_ranges=((-1, 64), (64, 128), (128, 256), (256, 512),(512, INF);对于每个输出层的正样本,变量每个point位置,计算其到四个边距离的最大值是否在该层的指定范围内,若不在,则认为是背景

      • 用于将不同大小的bbox分配到不同的FPN层进行预测
  4. loss:Focal loss(GIoU loss)

  5. centernetness分支(1,h,w),和bbox回归分支共享权重,仅仅在bbox head最后并行一个centerness分支,其target的设置是离gt bbox中心点越近,该值越大,范围是0-1,仅对正样本。【这样子可以使得正样本区域的权重不同,对loss有帮助】

与RetinaNet区别
  • 数据归一化:

    1
    2
    3
    4
    # pytorch
    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True
    # caffe
    mean=[103.530, 116.280, 123.675], std=[1.0, 1.0, 1.0], to_rgb=False
  • Backbone:style=’caffe’(FCOS)和style=’pytorch’(retinanet)

    • 对于caffe模式来说,stride参数放置在第一个1x1卷积上,对于pytorch模式来说,stride放在第二个3x3卷积上

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      256-d--------
      | |
      1x1, 64 |
      |relu |
      3x3, 64 |
      |relu |
      1x1, 256 |
      | |
      + <----------
      |relu
      output
    • 早期mmdetection采用了detectron权重(不想重新训练imagenet),采用了caffe2来构建模型,后续detectron2才切换到pytorch中,属于历史遗留问题。

    • 在caffe模式下,requires_grad=False,也就是说resnet的所有BN层参数都不更新并且全局均值和方差也不再改变,而在pytorch模式下,除了frozen_stages的BN参数不更新外,其余层BN参数还是会更新的

  • Neck: FPN

    • retinanet在得到p6,p7的时候是采用c5层特征进行maxpool得到的(对应参数是add_extra_convs='on_input',),而fcos是从p5层抽取得到的(对应参数是extra_convs_on_inputs=False),而且其还有relu_before_extra_convs=True参数,也就是p6和p7进行卷积前,还会经过relu操作,retinanet的FPN没有这个算子(C5不需要是因为resnet输出最后就是relu)。从实验结果来看,从p5抽取的效果是好于c5的
  • Head

    • fcos的head结构多了一个centerness分支

Soft Anchor-Point Object Detection——2019

针对的问题
  1. Attention bias

    靠近物体中心的四周会依然会产生大量confidence很高的输出,即没有清晰的边界,在训练过程中可能会抑制掉周围小的物体,导致小物体检测不出来或者检测效果很差。

  2. Feature selection

    对于任何一个gt bbox,到底采用何种策略分配到不同的层级进行预测?

    目前目标检测的做法是基于启发式人工准则将实例分配到金字塔层次(retinanet),或将每个实例严格限制为单个层次(fcos),从而可能会导致特征能力的不充分利用。

    通过训练发现:虽然每个GT只在特定层进行回归,但是其学出来的特征图是相似的,如上图所示,也就是说:一个以上金字塔等级的特征可以共同为特定实例的检测做出贡献,但是来自不同等级的贡献程度应该有所不同。这就是本文的出发点,不再强行判断哪一层进行回归了,而是每一层都进行回归,但是让网络自行学习到金字塔层级的权重。

Generalized Focal Loss——2020

针对的问题
  1. 分类预测分值和框的质量评估分值(如iou和centerness)的训练阶段和测试阶段不一致;

    • 训练时,分类和质量估计分支各自独立训练;测试时,相乘作为NMS score的排序依据。
    • 训练时,质量估计分支和bbox回归分支只针对正样本训练,而分类分支会对所有样本训练(包括大量的负样本);这样就导致分类分数较低的“负样本”的质量预测是没有监督信号的;测试时,有可能会出现分类score低但是有一个极高的质量score,导致NMS时排在真的正样本之前。
  2. bbox回归采用的表示不够灵活,没有办法建模复杂场景下不确定性

    • 之前的边界框具有很强的不确定性;框回归建模用了非常单一的狄拉克分布(即脉冲函数);对于有遮挡、模糊的边界非常不好
    • 作者则直接回归一个任意分布来建模框的表示。由于连续域上不可能回归,因此用离散化的方式,通过softmax来实现
结构

针对上述两个问题,分别设计了GFL和DFL两个分支来解决:

  1. 将bbox预测质量和分类分支loss联合表示,解决两分支独立训练,联合测试不一致问题

  2. 采用自发学习的灵活分布建模形式来表示bbox不确定性,具体是采用softmax+积分形式,相当于把回归问题转换为分类问题

算法

分类分支的改进:(分类分支加入了质量标签y;将bbox分支联合到此)

y是[0,1]范围的质量标签,来自预测和真实的bbox的IOU值。如果是负样本,则y=0。是分类分支经过sigmoid后的预测值。

QFL的全局最小解是,这样后面部分就是交叉熵。前面是调节因子(用距离绝对值的幂次函数),当最优。


将回归问题转化为分类问题,每个分布长度的概率。

回归的值是,点相对于bbox四条边的offset的值。【需要数据集的先验知识】

待回归值为x;假设分布长度为n,要使得分类满足。作用时要先用预测的label求的预测的回归值,再计算loss。

作者统计了所有coco数据集中正样本的回归范围,发现最大值大概可以设置为16,即分布长度为16+1.


由于这个label是无穷的,优化方向太多,但是考虑到真实分布通常不会距离标注太远,所以额外增加了一个loss——DFL,希望网络能够快速地聚焦到标注位置附近的数值,使得这个位置的概率仅可能的大。

其中,是浮点值y的左右整数值,S是分布。

通过计算可以得到全局最优解为:

也就是说我希望学出来的分布(全局最优解)应该是以线性插值的模式得到距离左右整数坐标的权重。

例如假设浮点数4.6,那么左右label=4,5,并且对应分布索引处的预测值理论上是0.4,0.6,此时就可以4x0.4+5x0.6=4.6

【也就是说,我希望学出来的分布应该是个在label两边整数的双峰分布】

同时,这里也为了联合分布,计算loss_bbox的时候,将分类分值作为这个loss的权重,进一步加强一致性。

Code

QFL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def quality_focal_loss(pred, target, beta=2.0):
"""
Args:
pred (torch.Tensor): 预测的分类和质量评估联合结果(N, C)
target (tuple([torch.Tensor])): 真实的的分类(N, )和质量评估(N, )
beta (float): 调节因子参数,默认为最优值2.0.
Returns:
损失值:torch.Tensor(N,).
"""
assert len(target) == 2, """分类标签和质量分数"""
label, score = target # 类别label(N), 预测bbox和gt bbox的iou score(N)
pred_sigmoid = pred.sigmoid() # (N, class_num)
scale_factor = pred_sigmoid
# 负样本的质量分数设置为0
zerolabel = scale_factor.new_zeros(pred.shape)
# 先假设所有label都是负样本,计算bce loss,乘上sigmoid^beta次方,达到focal效应,返回每一个样本的loss, with_logit会加上sigmoid,就和公式一样了
loss = F.binary_cross_entropy_with_logits(
pred, zerolabel, reduction='none') * scale_factor.pow(beta)
# 前景类标签id: [0, num_classes -1], 背景类标签id: num_classes
bg_class_ind = pred.size(1)
# 找出正样本索引, nonzero找出非零元素的位置
pos = ((label >= 0) & (label < bg_class_ind)).nonzero().squeeze(1)
pos_label = label[pos].long()
# 正样本应该被框质量IoU分数所监督
scale_factor = score[pos] - pred_sigmoid[pos, pos_label] # 正样本focal值,调节因子
# 计算正样本处的bce loss,将正样本部分的值更改为正确的loss;注意label=score,而不是1
loss[pos, pos_label] = F.binary_cross_entropy_with_logits(
pred[pos, pos_label], score[pos],
reduction='none') * scale_factor.abs().pow(beta)
loss = loss.sum(dim=1, keepdim=False) # (N,class_num)在1维度直接求和,变成(N,)
return loss

分布映射到浮点值:pred预测的是分布的label,然后计算出回归值后再计算loss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Integral(nn.Module):
"""
`sum{P(y_i) * y_i}`,
P(y_i) denotes the softmax vector that represents the discrete distribution
y_i denotes the discrete set, usually {0, 1, 2, ..., reg_max}
Args:
reg_max (int): The maximal value of the discrete set. Default: 16. You may want to reset it according to your new dataset or related settings.
"""
def __init__(self, reg_max=16):
super(Integral, self).__init__()
self.reg_max = reg_max
self.register_buffer('project',
torch.linspace(0, self.reg_max, self.reg_max + 1)) # 从(0, self.reg_max)线性分成self.reg_max+1份
def forward(self, x):
"""Forward feature from the regression head to get integral result of
bounding box location.
Args:
x (Tensor): Features of the regression head, shape (N, 4*(n+1)),
n is self.reg_max.
Returns:
x (Tensor): Integral result of box locations, i.e., distance
offsets from the box center in four directions, shape (N, 4).
"""
x = F.softmax(x.reshape(-1, self.reg_max + 1), dim=1) # 每一行的和为1,就是一张图片的概率label值 (N*4, reg_max+1) -> (N*4, reg_max+1)
x = F.linear(x, self.project.type_as(x)).reshape(-1, 4) # type_as(tesnor)将张量转换为给定类型的张量, 将两者对应相乘求和,(project又个broadcast),将(N*4, reg_max+1) -> (N*4, 1) -> (N, 4),积分操作得到最终回归值,到四个边的数值
return x

DFL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def distribution_focal_loss(pred, label):
"""
Args:
pred (torch.Tensor): Predicted general distribution of bounding boxes
(before softmax) with shape (N, n+1), n is the max value of the
integral set `{0, ..., n}` in paper.
label (torch.Tensor): Target distance label for bounding boxes with
shape (N,).
Returns:
torch.Tensor: Loss tensor with shape (N,).
"""
dis_left = label.long() # 坐标整数值
dis_right = dis_left + 1 # 右边整数值
# 线性权重
weight_left = dis_right.float() - label
weight_right = label - dis_left.float()
# 两个bce loss,并且加权,促使学到的分布是双峰分布,提高优化效率
loss = F.cross_entropy(pred, dis_left, reduction='none') * weight_left \
+ F.cross_entropy(pred, dis_right, reduction='none') * weight_right
return loss

一致性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
weight_targets = cls_score.detach().sigmoid()
# 预测类别所对应的输出分值
weight_targets = weight_targets.max(dim=1)[0][pos_inds]
# regression loss
loss_bbox = self.loss_bbox(
pos_decode_bbox_pred,
pos_decode_bbox_targets,
weight=weight_targets, # 将分类分值作为权重,进一步加强一致性
avg_factor=1.0)
# dfl loss
loss_dfl = self.loss_dfl(
pred_corners,
target_corners,
weight=weight_targets[:, None].expand(-1, 4).reshape(-1), # 将分类分值作为权重,进一步加强一致性
avg_factor=4.0)
# cls (qfl) loss
loss_cls = self.loss_cls(
cls_score, (labels, score), # 将bbox回归的score加入分类分支
weight=label_weights,
avg_factor=num_total_samples)

所有loss在特征图上计算的,因此在test的时候,需要乘以stride,得到原图尺寸。

Sparse R-CNN: End-to-End Object Detection with Learnable Proposals

特点

  • 目前目标检测的主流方法

    • dense detector一阶段:直接由密集的anchor得到
    • dense-to-sparse两阶段:由密集的anchor得到稀疏的proposal

    本文是为了解决dense属性造成的问题

    • NMS
    • many-to-one正负样本分配
    • Prior candidates的设计(anchor size、aspect ratio)

    设计的一种彻底sparse的设计方案。

    • sparse candidates——类似于提出ROI
    • sparse feature interaction——proposal的强连接
  • Sparse、没有NMS、网络简洁

结构

  • Backbone: ResNet+FPN
  • Head: iterative【上一层的output_feature, output_boxes作为下一层的proposal features, proposal boxes;不可靠的proposal通过多轮refine‘;之前使用过饱和的proposal来保证预测的质量】Dynamic Instance Interactive Head

    • 图像层面的候选——Proposal boxes: N*4代表候选目标的个数【100~300,可学习,不用枚举】和四个边界。【代替RPN】

      • RPN输出的roi主要目的是提供丰富的候选框,保证召回率即可,roi不需要很准确
      • 采用一个合理的和数据集相关的统计信息就可以提供足够的候选框了,从而采用可学习的proposal boxes代替RPN是完全合理的
      • 实验说明,这个可学习的proposal boxes初始化为什么,对结果的影响很小
    • 特征层面的候选——Proposal Features: N*d将稀疏的ROI features精确化,d一般为256。并进行self-attention

      • proposal boxes太过粗糙,无法表征物体姿态和形状。为了提高精度,引入了额外的高维度的proposal feature
    • 将proposal features和proposal boxes得到的ROI feature做一对一交互,使得ROI特征更有利于定位和分类。

      (300,7x7,256)的roi特征和(300,256,1)的proposal feature进行矩阵乘法,输出是(300,7x7,1)

      其表示将256维度的proposal feature向量和空间7x7的每个roi特征256维度向量计算相似性,得到相似性权重,该权重可以表征空间7x7个位置中哪些位置才是应该关心的

      如果将该权重作用到原始的(300,7x7,256)上,那不就是CV领域常说的空间注意力机制吗?

      并且由于roi特征和proposal feature是一对一计算,从而论文中称为实例级可交互模块,即N个实例roi特征和N个实例proposal feature一一对应计算,而没有交叉计算。

    • 统一大小的特征框经过全连接层得到固定大小的特征向量,输出N个无序集合,每个集合元素包括分类和bbox坐标信息

    • 采用casecase rcnn极联思想,迭代运行n个stage,每个stage都是一个rcnn模块参数是不共享的下一个stage接受的是上一个stage输出的refine后的roi,对输出的bbox进行refine,得到refine后的bbox坐标

  • Loss:匈牙利双边匹配后再计算loss

Anchor based

Yolo v2

通过在所有训练图像的所有边界框上进行k-means聚类来选择先验框。【在准确度和模型复杂性折衷下,取k=5】

Yolo V3

类别预测是不考虑背景的,所以才多引入了一个confidence的概念,该分支用于区分前景和背景

保证每个bbox一定有一个唯一的anchor进行对应

对gt bbox没有限制只能在某个层预测

对anchor没有限制,可以多个anchor预测同一个物体

YOLOv3+ASFF: Learning Spatial Fusion for Single-Shot Object Detection——2019

特点

  1. 训练技巧改进,只影响训练过程,对于推理没有增加复杂程度

结构

  • ASFF(Adaptively Spatial Feature Fusion):多个特征图带权的融合,融合的权重参数是通过学习得到的
  • 强baseline——YOLOv3
  • 训练策略
    • Mixup
    • Cosine learning rate schedule
    • Syncchronized batch normalization
    • IOU loss:优化bbox
    • Guided anchoring:由于在YOLO中有confidence分支来进行前后景提取,因此只需要shape预测的分支

Yolo V5

应用类似EfficientNet的channel和layer控制因子来灵活配置不同复杂度的模型,并且在正负样本定义阶段采用了跨邻域网格的匹配策略,从而得到更多的正样本anchor,加速收敛。

SSD

SSD vs YOLO v2/v3

  • SSD:使用数学公式来计算先验框尺寸,与数据集无关;YOLO通过K-means得到先验框
  • SSD先验框专注于长宽比;YOLO专注于先验框的大小
  • SSD先验框还包括x,y位置信息;而YOLO默认先验框位置位于网格的中心
  • SSD使用降序采样(网格从细到粗,从更general的尺度上获得更精确的预测);YOLO使用升序采样
  • SSD没有置信度,通过增加一个背景类来区分前后景
  • SSD的一个gt选择最大IOU的检测器;YOLO的一个gt随机选择检测器
  • SSD对于回归采用MSE;YOLO采用SSE平方和误差(因为图片中包含的物体数量不同)
  • SSD用难例挖掘来弱化正负样本不平衡的问题;YOLO通过超参数no_object_scale来调节loss的大小

FPN

RetinaNet:Focal Loss for Dense Object Detection——2018

特点
  • 提出了Focal Loss
结构
  • ResNet输出4个特征图c2 c3 c4 c5,stride=4,8,16,32

  • FPN:对这三层进行1x1改变通道,全部输出256个通道;然后经过从高层到底层的最近邻上采样add操作进行特征融合,最后对每个层进行3x3的卷积,得到p3,p4,p5特征图。对c5进行3x3卷积且stride=2进行下采样得到P6,然后对P6进行同样的3x3卷积且stride=2,得到P7

  • 输出head:

    • 分类:初始化bias设置为

      默认$\pi$为0.01【取值为最优, N: samples, : positive samples; C: classes】;anchor太多了,且没有faster rcnn里面的sample操作,故负样本远远大于正样本,也就是说分类分支,假设负样本:正样本数=100:1;简单来说就是对于一个分类任务,一个batch内部几乎全部是负样本,如果预测的时候没有偏向,那么Loss肯定会非常大,因为大部分输出都是错误的,现在强制设置预测为负类,这样开始训练时候loss会比较小。这个操作会影响初始训练过程。

      分类是sigmod输出,其输出的负数表示负样本label;

算法
  • Focal Loss

    FL本质上解决的是将大量易学习样本的loss权重降低,但是不丢弃样本,突出难学习样本的loss权重,但是因为大部分易学习样本都是负样本,所以还有一个附加功能即解决了正负样本不平衡问题。

    根据交叉熵改进而来,本质是dynamically scaled cross entropy loss,直接按照loss decay掉那些easy example的权重。

    但是label必须是one-hot形式。

    p代表gt类别的概率,负样本p很大,给予的权重就很小

    alpha属于正负样本的加权参数,值越大,正样本的权重越大。再看beta,其有focal效应,可以控制难易样本权重,值越大,对分类错误样本梯度越大(难样本权重大),focal效应越大,这个参数非常关键。

Region Proposal by Guided Anchoring——2019

特点

  • anchor的设置对于结果的影响是很大的,尤其对于one-stage(两阶段的可以通过RCNN进行回归);因此提出通过图像特征来指导anchor的生成。通过预测anchor的位置和形状,来生成稀疏而且形状任意的anchor。

    • anchor应该是alignment,以feature map上的点作为中心点;否则提取的特征和anchor的对应不是很好

    • anchor应该是consistency的;由于形状不同,通过feature adaption来修正。

      feature adaption:将anchor和特征融合后,通过3*3 deformable convolution修正原始的特征图。实际是对anchor随意性可能会造成网络难以学习的问题,给网络了一些anhcor的信息。

  • 可以直接插入anchor-base网络中进行anchor动态调整的做法,而不是替换掉原始网络结构。

  • 由于proposal质量很高了,所以可以减少proposal数量,增大训练时正样本的IOU阈值

结构

  • 预测cls_scores

  • 预测bbox_preds: xywh

  • 预测loc(batch, anchor_num, h, w),目标是预测哪些区域应该作为中心点来生成anchor,是一个二分类问题。

    1. 对每个gt,利用FPN中的ROI重投影规则,将gt映射到不同的特征图层上
    2. 定义中心区域和忽略区域比例(中心区域占据比例center_ratio和忽略区域占比ignore_ratio=0.5两个超参数),将gt落在中心区域的位置认为是正样本,忽略区域是模糊样本,其余区域是背景负样本。
  • 预测shape(batch, anchor_num*2, h, w),用于预测(正样本)anchor的形状【最佳的长和宽】,是一个回归问题。

    对于某个 anchor,应该优化和哪个 ground truth 的 IoU,也就是说应该把这个 anchor 分配给哪个 gt。对于以前常规的 anchor,我们可以直接计算它和所有 ground truth 的 IoU,然后将它分配给 IoU 最大的那个 gt。但是现在的anchor 的 w 和 h 是不确定的,作者回归的shape预测值实际上是在某个缩放系数下的值,sample了常见的9组w和h

算法

  1. 使用阈值将loc预测值划分出前景区域
  2. 提取前景区域的shape_preds
  3. 结合特征图位置,conccat得到4维的guided_anchors(x, y, w, h)
  4. 然后根据guided_anchors和cls_scores, bbox_preds分支得到最终的bbox预测值

ATSS:Bridging the Gap Between Anchor-based and Anchor-free Detection via Adaptive Training Sample Selection——2020

特点

  • 分析了anchor-based和anchor-free的差异,发现正负样本定义规则的不同是导致两者性能差异的原因

    以RetinaNet和FCOS进行分析。

    首先将RetinaNet的anchor个数设置为1,且比例设置为正方形,然后采用和FCOS相同的trick包括:GroupNorm、GIoU Loss、In GT Box、Centerness、Scalar后,mAP仍然相差了一个点。

    现在两个算法的区别:

    • 正负样本定义
      • Intersection over Union
      • Spatial and Scale Constraint【更好,因为其会产生更多的正样本区域】
    • 回归分支是从point【每个点预测距离4条边的距离】还是anchor【anchor框到gt框的

    然后实验得出用基于空间Scale约束之后,两者算法的mAP相近且比IOU高。

  • 使得anchor-based和ancchor-free都不需要调核心参数

    • 消除anchor-free的超参数,变为真正的自适应算法:如FCOS,希望消除回归范围regress_ranges和中心扩展比例参数center_sample_radius这两个核心超参
    • 对于anchor-base希望借鉴anchor-free的正负样本分配策略,弥补性能差异,同时也能自适应,使得不必设置正负样本阈值

算法

  1. compute iou between all bbox (bbox of all pyramid levels) and gt
  2. compute center distance between all bbox and gt
  3. on each pyramid level, for each gt, select k bbox whose center
    are closest to the gt center, so we total select k*l bbox as
    candidates for each gt
  4. get corresponding iou for the these candidates, and compute the
    mean and std, set mean + std as the iou threshold
  5. select these candidates whose iou are greater than or equal to
    the threshold as postive
  6. limit the positive sample’s center in gt

VarifocalNet: An IoU-aware Dense Object Detector——2020

针对的问题

  • 分类和回归分支不一致性问题
    • 作者通过将真实的centerness、IoU、bbox带入,发现并没有过大的提升;由此得出:影响mAP的主要因素不是bbox预测不准确。
    • 因此作者将cls设置为真实值(即排序仅依靠centerness),发现提升了10%,说明在类别预测完全正确的情况下,centerness可以在一定程度上区分边界框的准确性。
    • 目前的目标检测bbox分支输出的密集bbox中其实存在非常精确的预测框,关键是没有好的预测分值来选择出来,也就是Generalized Focal Loss中提到的如何加强分类分支和bbox分支一致性的问题。采用centerness分类分值的做法提升非常有限,最合适的做法就是将分类分支对应类别预测值中能够同时反映出物体类别和物体预测准确度,此时理论上限最高

特点

  1. 提出了正负样本不对称加权的 Varifocal Loss,突出正样本的主样本

    q是label

    • 正样本时候q为预测bbox和gt bbox的iou;普通的bce loss,多了一个自适应iou加权
    • 负样本时候q=0;标准的focal loss
  2. 借助dcn思想,提出星型bbox特征提取refine网络,对atss的输出初始bbox进行refine

    每个点的特征感受野就可以和初始时刻预测值重合,即特征对齐操作,方便refine,此时就得到了refine后的bbox预测输出值,需要特别注意的是refine分支输出值是代表scale值,将该refine输出值和初始预测值相乘即可得到refine后的真实bbox值

Two-Stage

基于候选框,anchor -> proposal -> detection bbox

Anchor based

Faster RCNN

Dynamic R-CNN: Towards High Quality Object Detection via Dynamic Training——2020

特点

  • 对faster rcnn的rcnn部分的训练过程进行动态分析,提出了the fixed label assignment strategy动态regression loss function

    • 随着训练进行,不断自适应增加rcnn正样本阈值【一般都是0.5】,并且针对回归分支预测bbox的方差减少特点自适应修改SmoothL1 Loss参数,达到类似focal效果。

      随着训练过程中,预测的bbox质量是会变的,正样本数也是不断增加的,此时采用固定的iou阈值肯定是不完美的,同时随着训练进行,回归误差的方差是不断变小的,采用smooth l1的固定beta也是不完美的,这两个核心参数应该动态变化。

    • 非常像cascade rcnn训练过程,只不过cascade rcnn是通过多个head,而本文压缩为一个head

    • 仅仅影响训练过程,对测试过程没有任何影响

结构

  • Dynamic Label Assignment

    在训练前期,高质量正样本很少,所以应该采用相对低的iou阈值来补偿,随着训练进行,高质量正样本不断增加,故可以慢慢增加iou阈值

    计算每个ROI和所有gt的最大iou值,在每张图片上选取第K个最大值,遍历所有图片,然后求均值作为T_now,并且每隔C个迭代更新一次该参数。

  • Dynamic Smooth L1 Loss

    先计算预测值和Gt的回归误差,然后选择第M个最小值组成list,在达到迭代次数之后,用中位数设置【平均值有外点的影响】。

    随着训练进行,高质量样本增多,loss的梯度非常小,导致无法进一步提高。因此通过动态减少beta,来增加高质量部分样本的梯度。

Anchor free

RepPoints

DCN v1 and DCN v2

DCN v1:学习偏移量【x,y两个方向】来改变标准卷积的采样位置。

DCN v2:增加了调制因子【范围在[0, 1]之间】

RepPoint相当于DCN v3,相比于前两者用分类和定位来监督偏移量,使得偏移量有解释性。

解决的问题

  • 边界框
    • 虽然易于计算,但是仅提供目标的粗略定位,并不完全拟合目标的形状和姿态。因此,从边界框的规则单元格中提取的特征可能会受到背景内容【无关】或前景区域的无效信息【噪声】的严重影响。可能导致特征质量降低,从而降低了目标检测的分类性能。
    • anchor需要设计不同尺度、不同长宽比
    • anchor的正负样本对比需要计算和gt的IOU值
  • cornernet这种点集表示法,需要将左上角点和右下角点进行分组的操作,尤其在密集或者重叠的场景中,分组的表现并不好

特点

  • 提供了更细粒度的定位和更方便的分类
  • 用n个自适应样本点进行建模,n默认为9;无anchor
    • 9个点是因为学习到的bbox更精准、更稳定
  • 仍然使用bbox标注,无需额外的样本点标注
  • DCN v3,有更好的语义对齐和可解释性

结构

  • head分支
    • 分类分支
      • 中心点回归热力图
    • 初始表征点回归
      • 18个通道,学习特征图上任意一点的9个自适应样本点的offset
      • 用可微转换函数将点转换为伪框
      • 计算转换后伪框与ground truth边界框之间的差异
    • refine表征点回归分支
      • 将预测输出的offset作为DCN offset的输入,进行特征重采样捕获几何位置,得到新的特征图
      • 对特征图进行分类和offset精细refine(相对第一阶段的偏移值)

算法

Head
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 将FPN输出的某个特征图,分成分类和回归特征图两条分支
cls_feat = x
pts_feat = x
for cls_conv in self.cls_convs:
cls_feat = cls_conv(cls_feat)
for reg_conv in self.reg_convs:
pts_feat = reg_conv(pts_feat)
# 通过3x3+1x1卷积,输出18个offset
pts_out_init = self.reppoints_pts_init_out(
self.relu(self.reppoints_pts_init_conv(pts_feat)))
# 乘上系数,降低该分支的梯度权重
pts_out_init_grad_mul = (1 - self.gradient_mul) * pts_out_init.detach() + self.gradient_mul * pts_out_init
# 减去kernel所对应的9个base点坐标([-1,-1], [0,-1], [1, -1], [-1, 0]....,[1,1]),作为DCN的offset输入值
dcn_offset = pts_out_init_grad_mul - dcn_base_offset
# 用DCN对分类分支和refine回归分支进行特征自适应,得到新的特征图,然后经过两个1x1卷积得到最终输出
# 分类输出
cls_out = self.reppoints_cls_out(
self.relu(self.reppoints_cls_conv(cls_feat, dcn_offset)))
# refine点offset输出
pts_out_refine = self.reppoints_pts_refine_out(
self.relu(self.reppoints_pts_refine_conv(pts_feat, dcn_offset)))
# refine加上初始预测就可以得到refine后的输出9个点坐标
pts_out_refine = pts_out_refine + pts_out_init.detach()
正负样本

对于offset回归问题,仅对正样本训练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 第一阶段
init=dict(
assigner=dict(type='PointAssigner', scale=4, pos_num=1),
allowed_border=-1,
pos_weight=-1,
debug=False)
# 第二阶段,用第一个阶段预测的offset解码还原后的初始bbox作为输入
refine=dict(
assigner=dict(
type='MaxIoUAssigner',
pos_iou_thr=0.5,
neg_iou_thr=0.4,
min_pos_iou=0,
ignore_iof_thr=-1))

对于分类分支采用MaxIoUAssigner

RepPoints转换为bbox

作者提出了三种做法【性能相似,相对来说3>1>2】:

  • minmax:取9个点的最大最小值,就可以构成bbox
  • Partical_minmax:选择前四个点进行minmax操作?
  • moment中心矩:先求9个点在xy方向的均值得到中心点坐标;再求标准差;通过可学习的transfer进行指数还原
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 均值和方差就是gt bbox的中心点
pts_y_mean = pts_y.mean(dim=1, keepdim=True)
pts_x_mean = pts_x.mean(dim=1, keepdim=True)
pts_y_std = torch.std(pts_y - pts_y_mean, dim=1, keepdim=True)
pts_x_std = torch.std(pts_x - pts_x_mean, dim=1, keepdim=True)
# self.moment_transfer也进行梯度增强操作
moment_transfer = (self.moment_transfer * self.moment_mul) + (self.moment_transfer.detach() * (1 - self.moment_mul))
moment_width_transfer = moment_transfer[0]
moment_height_transfer = moment_transfer[1]
# 解码
half_width = pts_x_std * torch.exp(moment_width_transfer)
half_height = pts_y_std * torch.exp(moment_height_transfer)
bbox = torch.cat([pts_x_mean - half_width, pts_y_mean - half_height, pts_x_mean + half_width, pts_y_mean + half_height],dim=1)
Loss
  • 分类:focal loss
  • 回归:smooth L1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
labels = labels.reshape(-1)
label_weights = label_weights.reshape(-1)
cls_score = cls_score.permute(0, 2, 3,1).reshape(-1, self.cls_out_channels)
cls_score = cls_score.contiguous()
loss_cls = self.loss_cls(
cls_score,
labels,
label_weights,
avg_factor=num_total_samples_refine)
bbox_pred_init = self.points2bbox(pts_pred_init.reshape(-1, 2 * self.num_points), y_first=False)
# 为了稳定训练过程
normalize_term = self.point_base_scale * stride
loss_pts_init = self.loss_bbox_init(
bbox_pred_init / normalize_term,
bbox_gt_init / normalize_term,
bbox_weights_init,
avg_factor=num_total_samples_init)
loss_pts_refine = self.loss_bbox_refine(
bbox_pred_refine / normalize_term,
bbox_gt_refine / normalize_term,
bbox_weights_refine,
avg_factor=num_total_samples_refine)
# 前向输出的offset值是特征图尺度的,但是计算loss时候的bbox_pred_init、bbox_pred_refine是原图尺度
pts = xy_pts_shift * self.point_strides[i_lvl] + pts_center

通用方法(技巧)

Is Sampling Heuristics Necessary in Training Deep Object Detectors?

特点

  1. 通过初始化与损失放缩达到和采样方法近似或更好的效果:实验发现CE在经过初始化和loss分类与回归权重调整之后是可以和FL比拟的

结构

【正样本占整体样本比例,是事先计算的】

  • 最优bias初始化

    最优的设置通过求导来确定:

  • Guided Loss:通过在训练中实时确定r,来降低分类比例,希望分类和回归loss维持相同的水平,使得分类损失是回归损失的倍;r是不经过BP的(torch.no_grad);这里貌似被抵消了,但是loss一个是用来算delta,另一个是用来进行梯度回传,更改网络参数操作的。

  • 类别自适应阈值:每个类别采用不同的过滤阈值

fusion

RelationNet++: Bridging Visual Representations for Object Detection via Transformer Decoder——2020

一种基于注意力的方法,用多种表示方式(anchor、center、corner)学习的信息特征融合起来。详见OD with new tech.

旋转目标检测

主要应用于遥感和文本检测。这里的IOU被SkewIOU替换。

数据集

VOC

  • 图片大小:333*500
  • 一半的图像只有一个物体,最多一张图包含39个物体

旋转目标检测

DOTA

HRSC2016

ICDRA2015

转载请注明出处,谢谢。

愿 我是你的小太阳

买糖果去喽