经典检测算法代码细节[无YOLO]
Faster R-CNN (w/ FPN)
流程:带FPN的backbone->rpn(生成anchor,rpnhead,赋予label)->->->->roi_head(检测头)
- FPN:传统的特征图像金字塔把图像swap成不同尺寸,在对每种尺寸的图片分别检测;SSD采用的是金字塔特征(每一层卷积层抽取特征);FPN则是在SSD的金字塔特征基础上进行不同特征层的融合,具体实施为高一层(空间小,通道多)通过上采样(插值算法)扩大空间,低一层(空间大、通道少)通过1x1卷积扩大通道,最终像素级求和。这种操作是是迭代的,不是仅仅相互两层影响,由最高层一层一层迭代到最底层。
- FPN:FPN提取完特征之后,每一层特征都经过了3x3大小,通道不变的卷积核进行调整。,最高层额外在这个卷积核之后加入maxpooling(1x1,stride=2)下采样提取额外的一层特征。这一层只用于RPN部分,即所有层产生的proposals的特征映射到所有层(除这一层)上。
- RPN:RPN由FPN结构生成,P2(32,{1:2,2:1,1:1}), P3(64,{1:2,2:1,1:1}),P4(128,{1:2,2:1,1:1}),P5(256,{1:2,2:1,1:1}),P6额外(512,{1:2,2:1,1:1})。
- 有多个特征层,是否需要每个特征层取使用一个预测层呢?答案上no,FPN论文有实验验证,这就是共享Head的由来吧!共享Head没有BN层,不然测试效果会极差,因为共享Head本质上处理多个检测任务,BN中的参数在测试时固定,所以学习到五(5个特征层)不像参数在测试时保存下来了。而GN却不会有这种问题,因为GN的参数在测试时也是变化的(根据每张图像)。
- P2-P6使用同一个RPN模块,P2-P5使用同一个Fast RCNN模块。P2-P6生成的RPN具体映射到那一层(P2、P3、P4、P5、P6)上进行预测(Fast RCNN)呢?通过一个公式得到的,w,h为每一层生成的RPN映射回原图的长宽。 k = ⌊ k 0 + l o g 2 ( w h ) 1 / 2 / 224 ⌋ k=\lfloor{k_0}+log_2(wh)^{1/2}/224 \rfloor k=⌊k0+log2(wh)1/2/224⌋
- 数据加载与数据预处理(测试与训练一样,不像跟踪,跟踪在测试阶段batch数为1,但最起码跟踪也要保持归一化等操作的一致性):dataset返回的不是一个信息,而是image,target两个信息,所以需要使用zip打包(collate_fn=utils.collate_fn)或者用字典将image和target信息合并。不同大小的图片统一缩放成相同大小的tensor,是tensor,而不是图像大小,在各种尺寸的图像上填0组成相同大小的tensor的,规则如下: 归一化处理(减均值、除方差) ->获取所有图像的长宽信息,定义最小为800,最大为1000,先根据最小边长与图片最小边长的缩放比例来缩放图片,若有的变长大于1000,则将缩放比例定义为1000和图片最大变长之比,训练时,bounding box也要缩放。 ->打包成batch,统一tensor的大小,并且把最大长宽统一到最接近32倍数的一个值,所以可以看到,图片最大为1000,但是输入的tensor最大为1024。
- 生成anchors的过程:利用set_cell_anchors来生5*3(5层特征P2-P6,3种比例)个基准anchors(中心为0,0) ;给基准anchors施加stride位移得到dense anchors(可以不以stride作为位移,但位移的次数要等于每一层的特征图大小,可见,越底层的特征层,anchors越多。因为stride等于每个像素感受野的区别);这两步骤对所有图像是相同的,接下来,遍历每张图像,将anchors乘stride映射回图片上,每一层的anchors和每一张图像上的anchors用list拼接(即嵌套list)。
- RPNHead:对P2-P6每一层通过3x3,padding=1的卷积核,不改变通道,再分别通过改变通道的cls和reg分支来得到每个像素的信息(1个像素负责3个anchors,即通道数为3或者3*4),并将reg输出赋予anchors得到proposals。
- 过滤proposals:遍历每张图像,将越界的proposals调整到边界->排除cls分数小于某个阈值的proposals(源代码为0.0),即这一步不起作用->nms处理,分数阈值为0.5->取 nms处理后的前些proposals(训练2000,测试1000)-> 最终将每个图像的proposals与其分数值拼接在列表里。
- 如果是训练的话:需要为每个anchors制定labels,包括cls和reg输出的labels。
- Roi_Head训练:采样训练样本并制定labels,cls的label值为-1,0,1,2,3,classes_num,-1丢弃,0为负样本(IoU<0.5),1正样本(IoU>0.5),选择的正负样本总量为512,负样本比例是0.25;这个步骤是一样的,接下来,遍历每张图片(每张图片512个训练样本),计算每个正anchor的reg的lables。Roi_align提取每个proposals的特征,提取方式上述FPN已详解记录。检测头,通过faltten再送入共享全连接层(或许是不能使用BN,这才是用全连接)->再送入cls和reg的全连接层,给每个类别都预测4个回归参数,而在RPN中,给每个anchor预测框即可!!!。如果是训练,就到此为止!
- Roi_Head测试:直接将rpn得到的1000个proposals用于检测。通过检测头再经过后处理。后处理如下:(1)根据proposal以及预测的回归参数计算出最终bbox坐标
(2)对预测类别结果进行softmax处理
(3)裁剪预测的boxes信息,将越界的坐标调整到图片边界上
(4)移除所有背景信息
(5)移除低概率目标
(6)移除小尺寸目标
(7)执行nms处理,并按scores进行排序
(8)根据scores排序返回前topk个目标的分数、boxes、目标类。 - 处理返回的结果:将bboxes返回原图像尺寸。
SSD(经典的one-stage结构)
在某些层结构提取特征进行目标检测(FPN的前作)。
- anchors的大小、比例为(21;1:1 ,1:2, 2:1,1:1[(2145)**0.5]) (45; 1:1 , 1:2, 2:1, 1:3,3:1, 1:1 [(4599)**0.5])、(99;1:1 ,1:2, 2:1,1:3,3:1,1:1[(99153)**0.5])、(153;1:1 ,1:2, 2:1,1:3,3:1,1:1[(153207)**0.5])、(207;1:1 ,1:2, 2:1,1:1[(207261)**0.5])、(261;1:1 ,1:2, 2:1,1:1[(261315)**0.5])
- 对于每个anchor的回归,至预测4个参数,不关注类别信息,而Faster R-CNN的二阶段则生成4*num_classed个参数,为每个类预测一个框。
- cls和reg的输出主要是通过3x3的卷积层实现的,而不是全连接层。
- 每个特征层上的相关工作用list打包,再用zip打包,得到打包的输出再用contiguous将数据连续存储,通常在进行切割操作之后,拼接时用这个函数。
- 生成anchors(8732个)的过程是根据原图像大小映射到feature maps,再映射回图像大小的anchors,这与Faster R-CNN不同。这是由于Faster R-CNN是得到anchors后统一输入FPN预测,而SSD是在每个特征层上预测。使用itertools.product函数生成每个anchors的中心,而不是通过基准anahor再通过位移,这与Faster R-CNN也不同。
- 计算损失时,先计算所有anchors的损失,得到损失向量(reduction=‘none’),再抽取正样负样本。
- SSD如何获取负样本(Hard negative mining):挑选损失值较大的anchors,所以这就是SSD先计算损失向量再挑选正负样本的原因。
- 计算类别cls损失和定位reg损失时求均值,都除N(正样本个数)。
- 正负样本的匹配(cls的labels)是在数据预处理时完成。每个anchors对应对大的GT的IoU(IoU排序->哪个GT)->每个GT对应对大的anchor的IoU(anchors排序->哪个anchor),通过这两条准则,给每个anchors找到其对应的GT(哪怕IoU小),以及每个GT至少得有1个anchors(IoU设置为2,大于0.5的数)。最后通过IoU阈值0.5过滤。
- NMS有个细节,给每个类别的框附加1个偏移量(这个偏移量就是labels*所有proposals中的最大坐标),使得每个类别的proposals不会重叠。另一种方法是给每个类别作NMS。
RetinaNet(Focal Loss)
focal loss提出后,one-stage检测器首次在理论实验上超越two-stage检测器。
- 沿用了FPN网络,但去除了P2(P2大,耗时),添加了P7。FPN中的P6是通过池化得到,这里是通过3x3卷积层,P7在P6之后再使用3x3卷积层生成。
- anchors的生成,FPN3组,RetinaNet9组,加入了不同尺度{1,2**(1/3),2**(2/3)}
- 没有使用Faster R-CNN中的全连接层来预测输出,而是每一层使用了共享Head(无BN),共享Head包括分类和回归分支。都是先通过4个通道不变,3x3的卷积层,再调整维度输出分类(Focal loss的使用不包含背景,这是跟踪算法没用focalloss的原因,而采用权重损失)和回归对应的通道数(类别不可知的预测:4*anchors_num个通道),这也是跟踪算法没用focalloss的原因。
- Focal Loss针对one-stage正负样本不匹配,因为two-stage在第二阶段这个问题不明显,它将类别分类两步进行处理,第一步:区分前景,筛掉了大量easy samples;第二步:区分具体类别,这一步也可以使用SSD中的在线Hard negative mining,。
- Focal Loss提出意义:easy samples(标签为1时,预测输出>>0.5;标签为0时,预测输出<<0.5)的loss值也挺大的,所以导致模型无法更多地关注hard samples。---------->因此,针对正负样本不均衡,计算损失时给样本增加权重;针对难易样本,增加一个因子使得模型更关注难区分的样本,即难区分样本损失大。
以下anchor free detection只分析训练部分
CornerNet(无回归且1个类别)
算法结构比较复杂。
- 网络梳理:输入(batch,3,511,511)图片,输出4元素列表,元素分别为:tl_heat(batch,1,128,128), br_heat(batch,1,128,128), tl_tag(batch,1,128), br_tag(batch,1,128),1 denotes number of classes。
- kp模块:通过self.up1(residual)得到高分辨率特征,通过self.up2得到不同的高分辨率特征,之后再相加torch.Size([batch, 256, 128, 128])。
- corner pooling通过编码显性的先验知识(e.g. 从右向左,从下往上确定左上角)来更好地确定角落位置。 tl_conv,br_conv流程一样。以tl_conv为例,流程图中channel指输出通道:
- 标签labels制定:每张图片返回5种信息:tl_heatmap, br_heatmap, tl_tags, br_tags, tag_masks。
tl_heatmap(batch,128,128):遍历每张图片中的boxes,获取第一个box的左上角,通过高宽和高斯阈值设置半径,通过**draw_gaussian函数(无return,通过np.maximum输出?)**输出以左上角为中心,半径内设置高斯标签;第二个box同理,然而,得到的标签是否会覆盖之前的heatmap,通过求最大处理。
tl_tags(batch,128,):获取所有boxes左上角的位置信息,从左到右,从上到下,在128*128的映射图上获取,例如129位于第2行第1列。再把这个信息传入tl_tags作为embedding向量,有几个boxes传入几个,所以这里tl_tags设置为128大小(128个boxes),这个label在gather特征时使用,变相参与loss计算。
br_tags:方式与tl_tags一样,只不过存的不是位置embedding,而是1。 - 训练:包括ace loss包括focal loss和pull push losss(ae loss)。
focal loss:计算标签heatmap与输出heat(batch,1,128,128)的损失,heat通过sigmod,再使用focal loss(实际上为heat输出的每个特征像素作二分类)
pull loss:给角点分组,统一box的左上、右下角点输出向量尽量小。
push loss:不同的boxes的角点输出尽量大一些。
CenterNet
这个检测算法整体非常简洁。
- 网络推理结构
- 可以用沙漏网络来代替resnet和可形变卷及层。可形变卷积层结构:
- 损失包括三部分:hmap_loss中心点、reg_loss中心偏移(x,y)、w_h_loss高宽
hmap_loss:输出的hmap经过sigmod。再微调CornerNet的focal loss使用。
reg_loss:使用l1 loss
w_h_loss:使用l1 loss直接学习高宽,为什么1个像素点的特征也能学到这么复杂的任务,因为这个点的感受野非常大。 - labels的制定:重点关注图片以及bboxes的仿射变换
FCOS
网络结构与RetianNet类似,不再叙述,本质上也是anchor-based,把点视为anchor。所以关键在如何定义训练样本。
- 通过三个mask操作,选出正样本。第一个mask选出偏移大于0的像素点(每个像素有4个偏移);第二个mask规定在FPN某一层的偏移范围[[-1,64],[64,128],[128,256],[256,512],[512,999999]];第三个mask选出制定半径内的像素点。
- 在预处理图像时,不仅要统一image的大小,还要统一每张图片中boxes的个数,这与Faster R-CNN不同
Anchor free总结
- CenterNet、CornerNet以点的方式存储gt boxes的labels,在用gather特征,类似姿态估计;而Fcos更像是anchor-based,只不过anchor为点。
分布式训练
读取一个batch数据->拿回参数->计算梯度(同步SGD,每块GPU同步计算)->每块GPU的梯度相加->发出梯度->更新梯度
- 数据如何在不同GPU之间分配:原始数据(5个)->shuffle->补充数据(加1[5个原始数据的第一个]为6个数据)->分配数据(GPU1:1,3,5;GPU2:2,4,6)
- 梯度如何在不同GPU之间通信:NVIDIA的GPU通信后端使用nccl。梯度求均值时,调整学习率为lr*num_gpus;梯度求和可忽略学习率调整。
- BatchNormalization如何在不同GPU之间同步(会降低并行速度,banchsize较大时同步BN没什么用):BN层有4个参数,均值和方差是通过动量的方法前向统计而来,均值和方差的偏移是训练得到。
- pin_memory:直接将数据加载到GPU,避免了与CPU通信的某些耗时(多级缓存)过程。