语义分割是从粗推理到精推理的自然步骤,它不仅提供类,还提供关于这些类的空间位置的附加信息。
语义分割通过对每个像素进行密集的预测、推断标签来实现细粒度的推理,从而使每个像素都被标记为其封闭对象矿石区域的类别。
Instance Segmentation可以看做object dection和semantic segmentation的结合。
R-CNN的系列算法分成两个阶段,先在图像上产生候选区域,再对候选区域进行分类并预测目标物体位置,它们通常被叫做两阶段检测算法。
SSD和YOLO算法则只使用一个网络同时产生候选区域并预测出物体的类别和位置,所以它们通常被叫做单阶段检测算法。
单阶段检测器检测速度快,然而精度不高,原因是正负样本不平衡,YOLO中样本中会存在大量的easy examples,且都是负样本,属于背景的样本,这样网络学不到有用的东西,无法正确分类。双阶段任务在RPN里产生ROI,已经删去很多。为了我们要降低easy examples的影响,我们为交叉熵叫一个权重,其中权重因子的大小一般为相反类的比重。即负样本不是多吗,它越多,我们给它的权重越小。这样就可以降低负样本的影响。
Focal Loss既做到了解决正负样本不平衡,也做到了解决easy与hard样本不平衡的问题。
Focal loss让单阶段检测器也变的牛逼起来,解决了class imbalance的问题。是同时解决了正负样本不平衡以及区分简单与复杂样本的问题。
目标检测:R-FCN FastRCNN FasterRCNN,YOLO
实例分割产生的背景:
在白色框与黄色框重复的区域,有些像素的语义就出现了差池,如在白色框中是背景,在黄色框中是前景。对于这个问题,由于传统的图像分割网络采用交叉熵,结合图像标签端到端训练,在标签中,对于一个像素点,语义类别是固定的,一个像素点只能对应一种固定的语义,由于卷积的平移不变性,一个像素点只能对应一种语义,因此没有办法达到实例分割的效果。也因此,产生了实例分割的问题。
尤其在处理多分类(C > 2)问题,分类器最后的输出单元需要Softmax 函数进行数值处理。
Softmax 的输出表征了不同类别之间的相对概率。。Softmax 将连续数值转化成相对概率,更有利于我们理解。
SVM 使用 hinge loss,更关注分类正确样本和错误样本之间的距离「Δ = 1」,只要距离大于 Δ,就不在乎到底距离相差多少,忽略细节。
而 Softmax 中每个类别的得分函数都会影响其损失函数的大小。
我们知道正则化的目的是限制权重参数 W 的大小,防止过拟合。正则化参数 λ 越大,对 W 的限制越大
若使用正则化参数 λ,由于限制了 W 的大小,得到的线性输出也会等比例缩小,正确样本和错误样本之间的相对概率差距变小了。
正则化参数 λ 越大,Softmax 各类别输出越接近。大的 λ 实际上是「均匀化」正确样本与错误样本之间的相对概率。但是,概率大小的相对顺序并没有改变,这点需要留意。
因此,也不会影响到对 Loss 的优化算法。
selective search的核心是SVM
提取2000个待检测区域,就是通过一些传统图像处理方法将图像分成若干块,然后通过一个SVM将属于同一目标的若干块拿出来。
AlexNet
是直接在ImageNet上面训练的,也就是说,使用图像分类数据集训练了一个仅仅用于提取特征的网络.
SVM
使用bounding box regression进行目标包围框的修正。
耗时的selective search,耗时的串行式CNN前向传播,对于每一个RoI,都需要经过一个AlexNet提特征,三个模块是分别训练的.
首先还是采用selective search提取2000个候选框,然后,使用一个神经网络对全图进行特征提取。
接着,使用一个RoI Pooling Layer在全图特征上摘取每一个RoI对应的特征,
再通过全连接层(FC Layer)进行分类与包围框的修正。Fast R-CNN的贡献可以主要分为两个方面:
取代R-CNN的串行特征提取方式,直接采用一个神经网络对全图提取特征(这也是为什么需要RoI Pooling的原因
除了selective search,其他部分都可以合在一起训练
缺点:
耗时的selective search
首先使用共享的卷积层为全图提取特征,然后将得到的feature maps送入RPN,RPN生成待检测框(指定RoI的位置)并对RoI的包围框进行第一次修正。之后就是Fast R-CNN的架构了,RoI Pooling Layer根据RPN的输出在feature map上面选取每个RoI对应的特征,并将维度置为定值。最后,使用全连接层(FC Layer)对框进行分类,并且进行目标包围框的第二次修正。
RPN要做的事情有两个,第一个是判断anchor到底是前景还是背景,意思就是判断这个anchor到底有没有覆盖目标,第二个是为属于前景的anchor进行第一次坐标修正。对于前一个问题,Faster R-CNN的做法是使用SoftmaxLoss直接训练,在训练的时候排除掉了超越图像边界的anchor;对于后一个问题,采用SmoothL1Loss进行训练。RPN的本质是一个树状结构,树干是一个3×3的卷积层,树枝是两个1×1的卷积层,第一个1×1的卷积层解决了前后景的输出,第二个1×1的卷积层解决了边框修正的输出一个1×1的卷积层输出了18个值(92),另一个1×1的卷积层输出了36个值(94)那么,要得到这些值,RPN网络需要训练。在训练的时候,就需要对应的标签。前景与背景分类训练方法:如果一个anchor与ground truth的IoU在0.7以上,那这个anchor就算前景(positive)。类似地,如果这个anchor与ground truth的IoU在0.3以下,那么这个anchor就算背景(negative)。在训练anchor属于前景与背景的时候,是在一张图中,随机抽取了128个前景anchor与128个背景anchor。anchor边框修正的训练方法,边框修正主要由4个值完成,tx,ty,th,tw。txty做出平移,thtw放大一定的倍数,那么如何训练。采用SmoothL1loss进行训练,都需要进行anchor包围框修正的参数训练,只是对positive的anchors有这一步。因此,在训练RPN的时候,只有对128个随机抽取的positive anchors有这一步训练。对于每个RoI而言,需要从共享卷积层上摘取对应的特征,并且送入全连接层进行分类因此,RoI Pooling主要做了两件事,第一件是为每个RoI选取对应的特征,第二件事是为了满足全连接层的输入需求,将每个RoI对应的特征的维度转化成某个定值6*6。分类器主要是分这个提取的RoI具体是什么类别(人,车,马等等),一共C+1类(包含一类背景)。RoI边框修正和RPN中的anchor边框修正原理一样,同样也是SmoothL1 Loss
首先通过RPN生成约20000个anchor(40×60×9)。
对20000个anchor进行第一次边框修正,得到修订边框后的proposal。
对超过图像边界的proposal的边进行clip,使得该proposal不超过图像范围。
忽略掉长或者宽太小的proposal。
将所有proposal按照前景分数从高到低排序,选取前12000个proposal。
使用阈值为0.7的NMS算法排除掉重叠的proposal。
针对上一步剩下的proposal,选取前2000个proposal进行分类和第二次边框修正。
Faster R-CNN的loss分两大块,第一大块是训练RPN的loss(包含一个SoftmaxLoss和SmoothL1Loss),第二大块是训练Fast R-CNN中分类器的loss(包含一个SoftmaxLoss和SmoothL1Loss)
使用在ImageNet上预训练的模型初始化共享卷积层并训练RPN。
使用上一步得到的RPN参数生成RoI proposal。再使用ImageNet上预训练的模型初始化共享卷积层,训练Fast R-CNN部分(分类器和RoI边框修订)。
将训练后的共享卷积层参数固定,同时将Fast R-CNN的参数固定,训练RPN。(从这一步开始,共享卷积层的参数真正被两大块网络共享)
同样将共享卷积层参数固定,并将RPN的参数固定,训练Fast R-CNN部分。
从输入图上的RoI到特征图上的RoI feature,RoI Pooling是直接通过四舍五入取整得到的结果。再将每个RoI对应的特征转化为固定大小的维度时,又采用了取整操作。这种取整操作(在Mask R-CNN中被称为quantization)对RoI分类影响不大,可是对逐像素的预测目标是有害的,因为对每个RoI取得的特征并没有与RoI对齐。Mask R-CNN对RoI Pooling做了改进并提出了RoI Align。
针对问题1,不再进行取整操作。
针对问题2,使用双线性插值来更精确地找到每个块对应的特征。在Mask R-CNN中的RoI Align之后有一个"head"部分,主要作用是将RoI Align的输出维度扩大,这样在预测Mask时会更加精确。在Mask Branch的训练环节,作者没有采用FCN式的SoftmaxLoss,反而是输出了K个Mask预测图(为每一个类都输出一张),并采用average binary cross-entropy loss训练,当然在训练Mask branch的时候,输出的K个特征图中,也只是对应ground truth类别的那一个特征图对Mask loss有贡献。
MaskR-CNN将判断类别和输出模板(mask)这两个任务解耦合,用sigmoid配合对率(logistic)损失函数对每个类别的模板单独处理,比经典分割方法用softmax让所有类别一起竞争效果更好。
1、整张图片送入CNN,进行特征提取
2、在最后一层卷积featuremap上,通过RPN生成ROI,每张图片大约300个建议窗口
3、通过RoIAlign层使得每个建议窗口生成固定大小的feature map(ROIAlign是生成mask预测的关键)
4、得到三个输出向量,第一个是softmax分类,第二个是每一类的bounding box回归,第三个是每一个ROI的二进制掩码Mask(FCN生成)
mask 编码了 输入的 object 的空间布局(spatial layout),针对每个 RoI,采用 FCN 预测一个 m×m 的 mask。mask 分支的每一网络层均可保持 m×m 的 object 空间布局,而不用压扁拉伸成向量形式来表示,导致空间信息损失。pixel-to-pixel 操作需要保证 RoI 特征图的对齐性,以保留 per-pixel 空间映射关系(映射到ROI原图). 即 RoIAlign。
原来RoIPooling是映射原图RoI 到特征图 RoI,其间基于 stride 间隔来取整,导致将特征图RoI映射回原图RoI时。出现 stride 造成的误差(max pool 后特征图的 RoI 与原RoI 间的空间不对齐更加明显). 会影响像素级的 mask 分割. 因此需要像素级的对齐ROIAlign。
RoIPool 用于从每个 RoI 中提取小的特征图的操作,RoIPool 选择的特征图区域,会与原图中的区域有轻微出入,分析ROIpool的步骤:把浮点数ROI量化到离散粒度的特征图,细分为空间直方图的bins,最后每个bin所涵盖的特征值被聚合(常用max pooling聚合)。也就是说,对浮点数 RoI 量化,再提取分块的直方图,最后利用 max pooling 组合,导致 RoI 和提取的特征间的 misalignments。对于平移不变性的分类任务影响不大,但对于要求精确的像素级 masks 预测具有较大的负影响。
RoIAlign 能够去除 RoIPool 引入的 misalignments,准确地对齐输入的提取特征. 即: 避免 RoI 边界或 bins 进行量化(如,采用 来替代 [四舍五入处理] );采用 bilinear interpolation 根据每个 RoI bin 的四个采样点来计算输入特征的精确值,并采用 max 或 average 来组合结果。而是使用双线性插值(bilinear interpolation)准确获得 2.93 像素位置的信息,避免了排列错误。
Backbone卷积网络用于特征提取,ResNet-50,ResNet-FPN(特征金字塔网络),ResNeXt-101
Head 网络 —— 用于对每个 RoI 分别进行 bounding-box 识别(分类和回归) 和 Mask 预测。
基础网络的增强,ResNeXt-101+FPN的组合可以说是现在特征学习的王牌了;
分割 loss 的改进,由原来的 FCIS 的 基于单像素softmax的多项式交叉熵变为了基于单像素sigmod二值交叉熵。softmax会产生FCIS的 ROI inside map与ROI outside map的竞争。但文章作者确实写到了类间的竞争, 二值交叉熵会使得每一类的 mask 不相互竞争,而不是和其他类别的 mask 比较 ;
ROIAlign解决Misalignment 的问题,说白了就是对 feature map 的插值。直接的ROIPooling的那种量化操作会使得得到的mask与实际物体位置有一个微小偏移,个人感觉这个没什么 insight,就是工程上更好的实现方式。
说明:这么好的效果是由多个阶段的优化实现的,大头的提升还是由数据和基础网络的提升:多任务训练带来的好处其实可以看作是更多的数据带来的好处;FPN 的特征金字塔,ResNeXt更强大的特征表达能力都是基础网络。ROI Align 很好地解决了ROI Pooling操作中两次量化造成的区域不匹配(mis-alignment)的问题。
ROI Pooling 的作用是根据预选框的位置坐标在特征图中将相应区域池化为固定尺寸的特征图,以便进行后续的分类和包围框回归操作ROI Pooling的主要作用是将proposal调整到统一大小。步骤如下:将proposal映射到feature map对应位置,将映射后的区域划分为相同大小的sections,对每个sections进行max pooling/avg pooling操作,由于预选框的位置通常是由模型回归得到的,一般来讲是浮点数,而池化后的特征图要求尺寸固定。
故ROI Pooling这一操作存在两次量化的过程。将候选框边界量化为整数点坐标值。从roi proposal到feature map的映射时,取[x/16],这里x是原始roi的坐标值,而方框代表四舍五入。 将量化后的边界区域平均分割成 k x k 个单元(bin), 对每一个单元的边界进行量化,每个bin使用max pooling。事实上,经过上述两次量化,此时的候选框已经和最开始回归出来的位置有一定的偏差,这个偏差会影响检测或者分割的准确度。在论文里,作者把它总结为“不匹配问题(misalignment)。
下面我们用直观的例子具体分析一下上述区域不匹配问题。如 图1 所示,这是一个Faster-RCNN检测框架。输入一张800800的图片,图片上有一个665665的包围框(框着一只狗)。图片经过主干网络提取特征后,特征图缩放步长(stride)为32。因此,图像和包围框的边长都是输入时的1/32。800正好可以被32整除变为25。但665除以32以后得到20.78,带有小数,于是ROI Pooling 直接将它量化成20。接下来需要把框内的特征池化77的大小,因此将上述包围框平均分割成77个矩形区域。显然,每个矩形区域的边长为2.86,又含有小数。于是ROI Pooling 再次把它量化到2。经过这两次量化,候选区域已经出现了较明显的偏差(如图中绿色部分所示)。更重要的是,该层特征图上0.1个像素的偏差,缩放到原图就是3.2个像素。那么0.8的偏差,在原图上就是接近30个像素点的差别,这一差别不容小觑。
做segment是pixel级别的,但是faster rcnn中roi pooling有2次量化操作导致了没有对齐。
ROI Align 的主要思想和具体方法:
为了解决ROI Pooling的上述缺点,作者提出了ROI Align这一改进的方法(如图2)。ROI Align的思路很简单:取消量化操作,使用双线性内插的方法获得坐标为浮点数的像素点上的图像数值,从而将整个特征聚集过程转化为一个连续的操作。值得注意的是,
在具体的算法操作上,ROI Align并不是简单地补充出候选区域边界上的坐标点,然后将这些坐标点进行池化,而是重新设计了一套比较优雅的流程:
遍历每一个候选区域,保持浮点数边界不做量化。将候选区域分割成k x k个单元,每个单元的边界也不做量化。在每个单元中计算固定四个坐标位置,用双线性内插的方法计算出这四个位置的值,然后进行最大池化操作。这里对上述步骤的第三点作一些说明:这个固定位置是指在每一个矩形单元(bin)中按照固定规则确定的位置。比如,如果采样点数是1,那么就是这个单元的中心点。如果采样点数是4,那么就是把这个单元平均分割成四个小方块以后它们分别的中心点。显然这些采样点的坐标通常是浮点数,所以需要使用插值的方法得到它的像素值。在相关实验中,作者发现将采样点设为4会获得最佳性能,甚至直接设为1在性能上也相差无几。
事实上,ROI Align 在遍历取样点的数量上没有ROIPooling那么多,但却可以获得更好的性能,这主要归功于解决了misalignment的问题。值得一提的是,我在实验时发现,ROI Align在VOC2007数据集上的提升效果并不如在COCO上明显。经过分析,造成这种区别的原因是COCO上小目标的数量更多,而小目标受misalignment问题的影响更大(比如,同样是0.5个像素点的偏差,对于较大的目标而言显得微不足道,但是对于小目标,误差的影响就要高很多)。
RoIWarp第一次量化了,第二次没有,RoIAlign两次都没有量化 。
利用分类的结果,在mask之路,只取对应类别的channel然后做sigmoid,减少类间竞争,避免出现一些洞之类。
相比于FCIS,FCIS使用全卷积网络,同时预测物体classes、boxes、masks,速度更快,但是对于重叠物体的分割效果不好。
由于全连接层输入的要求,需要ROI-Pooling技术,这对于不同尺度的目标(尤其是面积比较大的目标)来说,细节信息损失巨大。
ROI-Pooling技术指所有目标区域都要被转化为相同的尺度
那么,在前景与背景分离这个问题上面,对于面积大的目标区域,是将其缩小之后再进行分离,然后将得到的结果(Mask)放大到原来的尺度作为前景,
这样对ROI区域的操作非常容易损失掉目标的细节信息(比如说,车子的轮子在缩小的过程中就没了,再放大之后也不会再有)。
首先针对问题1,ROI-Pooling被取消了,取而代之的是对ROI区域的聚合,实质就是复制粘贴。
然后针对问题2,全连接层(FC layer)被取消了,取而代之的是分类器(softmax)。
最后,针对问题3,图像分割与图像分类使用的是相同的特征图。
对于卷积层的平移不变性,本文采用的方法是生成若干组特征图(k×k组),对于每一张输入图片,生成k×k组特征图(在上图的例子中k为3),每一组特征图,称为score map。每一组score map,表示的是输入图像中每一个ROI的不同位置的分数(score)比如,左上角的一张score map,代表的就是输入图像中每一个ROI中的左上角部分(标志为1的小框内部)的score,其他的类推。score代表的含义是某个特定位置(小窗)的像素点属于ROI中的前景的分数。这样做解决了卷积的平移不变性带来的问题,使得不同ROI中的重叠区域在不同ROI中的score是不一样的。确实,这样每张ROI对每个像素的前景打分不同。这就很好的实现了,相同区域,不同语义的问题。
通过两类score map解决,一类叫inside score map,一类叫outside score map。inside score map表征了像素点在ROI区域内部中前景的分数,如果一个像素点是位于一个ROI区域内部并且是目标(前景),那么在inside score map中就应该有较高的分数,而在outside score map中就应该有较低的分数。针对图像分割,使用两类score map,通过一个分类器就可以分出前景与背景。针对图像分类,将两类score map结合起来,可以实现分类问题。通过两类score map的结合,可以甄别出ROI检测失误的区域。
针对图像分割任务和图像分类任务的并行,FCIS这样处理:
首先,对于每个ROI区域,将inside score maps和outside score maps中的小块特征图复制出来,拼接成为了ROI inside map和ROI outside map。针对图像分割任务,直接对上述两类map通过softmax分类器分类,得到ROI中的目标前景区域(Mask)。针对图像分类任务,将两类map中的score逐像素取最大值,得到一个map,然后再通过一个softmax分类器,得到该ROI区域对应的图像类别。在完成图像分类的同时,还顺便验证了ROI区域检测是否合理,具体做法是求取最大值得到的map的所有值的平均数,如果该平均数大于某个阈值,则该ROI检测是合理的。
针对输入图像上的每一个像素点,有三种情况:第一种情况是inside score高,outside score低;则该像素点位于ROI中的目标部分。第二种情况是inside score低,outside score高,则该像素点位于ROI中的背景部分。第三种情况是inside score和outside score都很低,那么该像素点不在任何一个ROI里面。因此,我们在上一段中描述的,针对ROI inside map和ROI outside map中逐像素点取最大值得到的图像:如果求平均后分数还是很低,那么,我们可以断定这个检测区域是不合理的。如果求平均后分数超过了某个阈值,我们就通过softmax分类器求ROI的图像类别,再通过softmax分类器求前景与背景。
图像输入进来,经过卷积层提取初步特征,然后利用这些特征,一边经过RPN(Region Proposal Network)网络提取ROI区域,一边再经过一些卷积层生成2×(C+1)×k×k个特征图。2代表inside和outside两类;C+1代表图像类别一共C类,再加上背景(未知的)1类;
k×k代表每一类score map中各有k个(上图的例子中k就为3)。在经过assembling之后(其实就是复制粘贴),对于每一个ROI,k×k个position-sensitive score map被综合成了一个,然后放小了16倍(长宽各变成1/4),得到2×(C+1)个特征图。然后开始并行操作,第一条线:对于每一类的ROI inside map和ROI outside map逐像素取最大值,得到了C+1个特征图,对这C+1个特征图逐个求平均值,将平均值同阈值比较,若大于阈值,则判定该ROI合理,则直接送入softmax分类器进行分类,得到图像类别。若小于阈值,则不进行任何操作。第二条线:做C+1次softmax分类,对每一个类别得到前景与背景,然后根据第一条并行线的分类结果,选择出对应类别的前景与背景划分结果。
对于训练过程,对于每一个ROI,loss由三部分组成,如下表述:
在C+1类上面的分类loss(softmax loss)。
对于positive ROI,有一个前景与背景的分类loss(softmax loss)。
对于positive ROI,有一个包围框回归的loss(L1 loss)(由Fast R-CNN提出)
positive ROI指的是,这个ROI与Ground Truth上面与其最近的目标包围框重叠区域占比在0.5以上。
FPN是ResNet或DenseNet等通用特征提取网络的附加组件。在以往的faster rcnn进行目标检测时,无论是rpn还是fast rcnn,roi 都作用在最后一层,这在大目标的检测没有问题,但是对于小目标的检测就有些问题。因为对于小目标来说,当进行卷积池化到最后一层,实际上语义信息已经没有了,因为我们都知道对于一个roi映射到某个feature map的方法就是将底层坐标直接除以stride,显然越后,映射过去后就越小,甚至可能就没有了。 所以为了解决多尺度检测的问题,引入了特征金字塔网络。(如果可以结合多层级的特征,就可以大大提高多尺度检测的准确性。 而FPN网络则使用多层特征图进行预测。)
1、整张图片送入FPN,进行特征提取
2、自下到上的通道增强将低层的信息融入高层,生成新的特征图
3、经过适应特征池化层
4、输入两个分支,得到三个输出向量,一是softmax分类&Bbox回归,二是每一个ROI的掩码Mask(FC融合)
Bottom-up 路径增强:为了加强低层信息变得更容易传播,细节利用上
Adapting 特征池:允许每个候选区从访问各级信息进行预测。
FPN中,从P2-P6(P6仅用作生成proposal,不用作RoIPooling时提取特征)多尺度地生成proposal,然后做RoIPooling时会根据proposal的大小将它分配到不同的level去crop特征,小的proposal去low-level的层,大的proposal去high-level的层。
这样做虽然简单也蛮有效,但它不是最好的处理方式,尽管P2-P5(N2-N5)已经融合了low-level和high-level的特征,然后它们的主要特征还是以它本有的level为主 重要的特征与所在的层无关,如果小的proposal能从high-level层获取到更多的上下文语义信息和较大识别域是有利于它分类的,而大的proposal能从low-leve层获取到更好的细节是有利定位准确性的。
因此,打算每个proposal从所有level的特征上做RoIPooling,然后在后面融合,融合的阶段和方式都可实验,比如分类时是两个fc,这个融合阶段可以是fuse,fc1, fc2或者fc1, fuse, fc2,融合策略可是sum也可以是max,最后证明fc1, fuse,fc2和max最好。这个改进是增加些运算负担。
MaskRCNN中Mask分支就是个简版的fcn,fcn是全卷积网络,它根据一个局部的视野域来预测,且参数是全图共享,而全连接fc是全图视野域对位置更敏感,看得更大,这一点large kernel也间接证明了大视野域的作用。
因此,这里打算多加一条用全连接层预测的支路来做mask预测,然后和fcn融合,具体做法如下图所示,至于conv4_fc接在fcn支路哪一个卷积后后面融合,,实验对比,conv3后面结果更好一点。
YOLOv3算法的基本思想可以分成两部分:
按一定规则在图片上产生一系列的候选区域,然后根据这些候选区域与图片上物体真实框之间的位置关系对候选区域进行标注。跟真实框足够接近的那些候选区域会被标注为正样本,同时将真实框的位置作为正样本的位置目标。偏离真实框较大的那些候选区域则会被标注为负样本,负样本不需要预测位置或者类别。使用卷积神经网络提取图片特征并对候选区域的位置和类别进行预测。这样每个预测框就可以看成是一个样本,根据真实框相对它的位置和类别进行了标注而获得标签值,通过网络模型预测其位置和类别,将网络预测值和标签值进行比较,就可以建立起损失函数。
上半部分所示的过程是使用卷积神经网络对图片提取特征,随着网络不断向前传播,特征图的尺寸越来越小,每个像素点会代表更加抽象的特征模式,直到输出特征图,其尺寸减小为原图的1/32下半部分首先将原图划分成多个小方块,每个小方块的大小是32×3232 \times 3232×32,然后以每个小方块为中心分别生成一系列锚框,整张图片都会被锚框覆盖到。在每个锚框的基础上产生一个与之对应的预测框,根据锚框和预测框与图片上物体真实框之间的位置关系,对这些预测框进行标注。将上方支路中输出的特征图与下方支路中产生的预测框标签建立关联,创建损失函数,开启端到端的训练过程。
如何产生候选区域,是检测模型的核心设计方案,目前大多数基于卷积神经网络的模型所采用的方式大体如下:按一定的规则在图片上生成一系列位置固定的锚框,将这些锚框看作是可能的候选区域。对锚框是否包含目标物体进行预测,如果包含目标物体,还需要预测所包含物体的类别,以及预测框相对于锚框位置需要调整的幅度。
如果t是网络预测的输出值,将txty作为目标值,以他们之间的差距作为损失函数,则可以建立起一个回归问题,通过学习网络参数,使得ttt足够接近txty,从而能够求解出预测框的位置坐标和大小。对候选区域进行标注,每个区域可以产生3种不同形状的锚框,每个锚框都是一个可能的候选区域,锚框是否包含物体,这可以看成是一个二分类问题,如果锚框包含了物体,那么它对应的预测框的中心位置和大小应该是多少,如果锚框包含了物体,那么具体类别是什么。
先通过真实框计算其中心点,然后在中心点生成它的三个锚框,用这3个不同形状的锚框跟真实框计算IoU,选出IoU最大的锚框。将它所对应的预测框的objectness标签设置为1,其所包括的物体类别就是真实框里面的物体所属类别。依次可以找出其他几个真实框对应的IoU最大的锚框,然后将它们的预测框的objectness标签也都设置为1。这里一共有20×15×3=900个锚框,只有3个预测框会被标注为正。因为有20*15个grid,并且只有三个人。由于每个真实框只对应一个objectness标签为正的预测框,如果有些预测框跟真实框之间的IoU很大,但并不是最大的那个,那么直接将其objectness标签设置为0当作负样本,可能并不妥当,当预测框的objectness不为1,但是其与某个真实框的IoU大于iou_threshold时,就将其objectness标签设置为-1,不参与损失函数的计算。对于objectness=1的预测框,需要进一步确定其位置和包含物体的具体分类标签,但是对于objectness=0或者-1的预测框,则不用管他们的位置和类别。当锚框objectness=1时,需要确定预测框位置相对于它微调的幅度,也就是锚框的位置标签。如果以txtythtw)是网络预测的输出值以真实框坐标代入算出的txtythtw作为目标值,以它们之间的差距作为损失函数,则可以建立起一个回归问题,通过学习网络参数,使得ttt足够接近t∗t^*t ∗,从而能够求解出预测框的位置。这里使用one-hot向量来表示类别标签label。
通过这种方式,我们在每个小方块区域都生成了一系列的锚框作为候选区域,并且根据图片上真实物体的位置,标注出了每个候选区域对应的objectness标签、位置需要调整的幅度以及包含的物体所属的类别。位置需要调整的幅度由4个变量描述x,t y,t w ,t h,objectness标签需要用一个变量描述objobjobj,描述所属类别的变量长度等于类别数C。对于每个锚框,模型需要预测输出(tx,ty,tw,th,Pobj,P1,P2,…,PC),其中Pobj 是锚框是否包含物体的概率,P1,P2,…,PC则是锚框包含的物体属于每个类别的概率。接下来让我们一起学习如何通过卷积神经网络输出这样的预测值。特征图的步幅stride等于输入图片尺寸除以特征图尺寸。
根据输出特征图计算预测框位置和类别YOLOv3中对每个预测框计算逻辑如下:预测框是否包含物体。也可理解为objectness=1的概率是多少,可以用网络输出一个实数x,可以用Sigmoid(x)表示objectness为正的概率Pobj预测物体位置和形状。物体位置和形状可以用网络输出4个实数来表示tx,ty,tw,th 预测物体类别。预测图像中物体的具体类别是什么,或者说其属于每个类别的概率分别是多少。可以用网络输出C个实数,,对每个实数分别求Sigmoid函数,让Pi=Sigmoid(xi),则可以表示出物体属于每个类别的概率。
对于一个预测框,网络需要输出(5+C)个实数来表征它是否包含物体、位置和形状尺寸以及属于每个类别的概率。由于我们在每个小方块区域都生成了K个预测框,则所有预测框一共需要网络输出的预测值数目是:[K(5+C)]×m×n还有更重要的一点是网络输出必须要能区分出小方块区域的位置来,不能直接将特征图连接一个输出大小为[K(5+C)]×m×n的全连接层。
上面的小方格grid即图片特征图的一个像素
下面需要将像素点(i,j)(i,j)(i,j)与第i行第j列的小方块区域所需要的预测值关联起来,为了解决这一问题,对特征图进行多次卷积,并将最终的输出通道数设置为K(5+C)K(5 + C)K(5+C),即可将生成的特征图与每个预测框所需要的预测值巧妙的对应起来。当然,这种对应是为了将骨干网络提取的特征对接输出层来形成Loss。实际中,这几个尺寸可以随着任务数据分布的不同而调整,只要保证特征图输出尺寸(控制卷积核和下采样)和输出层尺寸(控制小方块区域的大小)相同即可。下面的程序是对C0进行多次卷积以得到跟预测框相关的特征图P0。
class YoloDetectionBlock(paddle.nn.Layer):
# define YOLOv3 detection head
# 使用多层卷积和BN提取特征
def __init__(self,ch_in,ch_out,is_test=True):
super(YoloDetectionBlock, self).__init__()
assert ch_out % 2 == 0, \
"channel {} cannot be divided by 2".format(ch_out)
self.conv0 = ConvBNLayer(
ch_in=ch_in,
ch_out=ch_out,
kernel_size=1,
stride=1,
padding=0)
self.conv1 = ConvBNLayer(
ch_in=ch_out,
ch_out=ch_out*2,
kernel_size=3,
stride=1,
padding=1)
self.conv2 = ConvBNLayer(
ch_in=ch_out*2,
ch_out=ch_out,
kernel_size=1,
stride=1,
padding=0)
self.conv3 = ConvBNLayer(
ch_in=ch_out,
ch_out=ch_out*2,
kernel_size=3,
stride=1,
padding=1)
self.route = ConvBNLayer(
ch_in=ch_out*2,
ch_out=ch_out,
kernel_size=1,
stride=1,
padding=0)
self.tip = ConvBNLayer(
ch_in=ch_out,
ch_out=ch_out*2,
kernel_size=3,
stride=1,
padding=1)
def forward(self, inputs):
out = self.conv0(inputs)
out = self.conv1(out)
out = self.conv2(out)
out = self.conv3(out)
route = self.route(out)
tip = self.tip(route)
return route, tip
NUM_ANCHORS = 3
NUM_CLASSES = 7
num_filters=NUM_ANCHORS * (NUM_CLASSES + 5)
backbone = DarkNet53_conv_body()
detection = YoloDetectionBlock(ch_in=1024, ch_out=512)
conv2d_pred = paddle.nn.Conv2D(in_channels=1024, out_channels=num_filters, kernel_size=1)
x = np.random.randn(1, 3, 640, 640).astype('float32')
x = paddle.to_tensor(x)
C0, C1, C2 = backbone(x)
route, tip = detection(C0)
P0 = conv2d_pred(tip)
print(P0.shape)
通过这种方式可以巧妙的将网络输出特征图,与每个小方块区域生成的预测框对应起来了。
从四维tensor中取出objectness,并计算输出概率。
上面从概念上将输出特征图上的像素点与预测框关联起来了,那么要对神经网络进行求解,还必须从数学上将网络输出和预测框关联起来,也就是要建立起损失函数跟网络输出之间的关系。
对于每个预测框,YOLOv3模型会建立三种类型的损失函数:
表征是否包含目标物体的损失函数,通过pred_objectness和label_objectness计算。
表征物体位置的损失函数,通过pred_location和label_location计算。
表征物体类别的损失函数,通过pred_classification和label_classification计算。
就可以将那些没有被标注为正样本,但又与真实框IoU比较大的样本objectness标签设置为-1了,不计算其对任何一种损失函数的贡献。
已经了解到了YOLOv3算法的大部分内容,包括如何生成锚框、给锚框打上标签、通过卷积神经网络提取特征、将输出特征图跟预测框相关联、建立起损失函数。
如果计算损失函数是在最小的特征图上进行的,特征图的尺寸比较小,像素点数目比较少,每个像素点的感受野很大,具有非常丰富的高层语义信息,可能比较容易检测到较大的目标。为了能够检测到尺寸较小的那些目标,需要在尺寸较大的特征图上面建立预测输出。如果在大的特征图上直接产生预测输出,可能面临新的问题,它们没有经过充分的特征提取,像素点包含的语义信息不够丰富,有可能难以提取到有效的特征模式。在目标检测中,解决这一问题的方式是,将高层级的特征图尺寸放大之后跟低层级的特征图进行融合,得到的新特征图既能包含丰富的语义信息,又具有较多的像素点,能够描述更加精细的结构。越往后的特征图上用到的锚框尺寸也越大,能捕捉到大尺寸目标的信息;越往前的特征图上锚框尺寸越小,能捕捉到小尺寸目标的信息。
输入图片经过特征提取得到三个层级的输出特征图P0(stride=32)、P1(stride=16)和P2(stride=8),相应的分别使用不同大小的小方块区域去生成对应的锚框和预测框,并对这些锚框进行标注。将三个层级的特征图与对应锚框之间的标签关联起来,并建立损失函数,总的损失函数等于三个层级的损失函数相加。通过极小化损失函数,可以开启端到端的训练过程。
这里使用非极大值抑制(non-maximum suppression, nms)来消除冗余框。基本思想是,如果有多个预测框都对应同一个物体,则只选出得分最高的那个预测框,剩下的预测框被丢弃掉。
不同于faster R-CNN的是,yolo_v3只会对1个prior进行操作,也就是那个最佳prior。而logistic回归就是用来从9个anchor priors中找到objectness score(目标存在可能性得分)最高的那一个。logistic回归就是用曲线对prior相对于 objectness score映射关系的线性建模。
分类用的多类别交叉熵,置信度用的二分类交叉熵。
一次性完成预测和检测任务,是rcnn结构无法做到的,得益于loss的逻辑设计上。
1、解决了grid和绝对位置的关系
2、grid与anchor建立了联系
3、grid和box相对位置联系起来
以上三个点,把整个输出所需要的所有关键数据全部联系起来了,让整个数据做到端到端的完整映射。这一方面确确实实很大程度上归功于数据的结构设计与损失函数的设计的结合,让yolo能够实现一次完成任务。
1.binary_crossentropy和sigmoid的应用,因为二分类是得到比较“绝对”的输出。
正是作者使用二分类的计算思维,能够让第“1”的内容所涉及的预测值,在结果中对“某些东西”的预测非常到位。
2.分支损失函数的制衡也是有他的道理的。存在一个制衡点,不能让一个特别大,也不能特别小。
在anchor一定的情况下,面积越小,置信度相对越低,必然使得object_mask偏小;然而box_loss_scale是跟面积成反比的函数,这两个值一乘起来,必然需要找局部极值点,才能使得loss局部最小。
3.多尺度检测保证物体多样性。
4.anchor选择使用k-means聚类方法,让预测值在特定数据集上泛化能力更强。
1、上面说到他使用二分类的策略构造损失函数,必然导致结果值是有偏向性的。
2、同一个位置,如果有一只大小差不多的猫和狗,yolo可能只能检测一个。
3、相比rcnn系列,精度还是欠缺一些的,这点是毋庸置疑的。
为什么多分类问题用二值交叉熵解决?
这里,我们可以理解为,“所有的分类都预测正确”为一个类1,否则就是另一个类0。这样就把多分类看做是二分类问题,当且仅当所有的分类都预测对时,loss最小。
(10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,32)
这9个prior为knn聚类得到的,如果是新的数据集还需要自行重新计算。v3每次对b-box进行predict时,输出和v2一样都是(tx,ty,tw,th,to),然后通过公式1计算出绝对的(x, y, w, h,c)。logistic回归用于对anchor包围的部分进行一个目标性评分(objectness score),即这块位置是目标的可能性有多大。这一步是在predict之前进行的,可以去掉不必要anchor,可以减少计算量。不同于faster R-CNN的是,yolo_v3只会对1个prior进行操作,也就是那个最佳prior。而logistic回归就是用来从9个anchor priors中找到objectness score(目标存在可能性得分)最高的那一个。**logistic回归就是用曲线对prior相对于 objectness score映射关系的线性建模。**除了w, h的损失函数依然采用总方误差之外,其他部分的损失函数用的是二值交叉熵。
9个anchor会被三个输出张量平分的。根据大中小三种size各自取自己的anchor。每个输出y在每个自己的网格都会输出3个预测框,这3个框是9除以3得到的,这是作者设置的代码上来看,3*(5+80)中的3是直接由num_anchors//3得到的。作者使用了logistic回归来对每个anchor包围的内容进行了一个目标性评分(objectness score)。根据目标性评分来选择anchor prior进行predict,而不是所有anchor prior都会有输出。
1.输入端:这里指的创新主要是训练时对输入端的改进,主要包括Mosaic数据增强、cmBN、SAT自对抗训练
2.BackBone主干网络:将各种新的方式结合起来,包括:CSPDarknet53、Mish激活函数、Dropblock
3.Neck:目标检测网络在BackBone和最后的输出层之间往往会插入一些层,比如Yolov4中的SPP模块、FPN+PAN结构
4.Prediction:输出层的锚框机制和Yolov3相同,主要改进的是训练时的损失函数CIOU_Loss,以及预测框筛选的nms变为DIOU_nms
Mosaic数据增强则采用了4张图片,随机缩放、随机裁剪、随机排布的方式进行拼接。而Coco数据集中也包含大量的小目标,但比较麻烦的是小目标的分布并不均匀。
优点:
丰富数据集:随机使用4张图片,随机缩放,再随机分布进行拼接,大大丰富了检测数据集,特别是随机缩放增加了很多小目标,让网络的鲁棒性更好。
减少GPU:可能会有人说,随机缩放,普通的数据增强也可以做,但作者考虑到很多人可能只有一个GPU,
因此Mosaic增强训练时,可以直接计算4张图片的数据,使得Mini-batch大小并不需要很大,一个GPU就可以达到比较好的效果。
CSPDarknet53是在Yolov3主干网络Darknet53的基础上,借鉴2019年CSPNet的经验,产生的Backbone结构,其中包含了5个CSP模块。CSPNet的作者认为推理计算过高的问题是由于网络优化中的梯度信息重复导致的。因此采用CSP模块先将基础层的特征映射划分为两部分,然后通过跨阶段层次结构将它们合并,在减少了计算量的同时可以保证准确率。
优点一:增强CNN的学习能力,使得在轻量化的同时保持准确性。
优点二:降低计算瓶颈
优点三:降低内存成本
其实和常见网络中的Dropout功能类似,也是缓解过拟合的一种正则化方式。卷积层对于这种随机丢弃并不敏感,因为卷积层通常是三层连用:卷积+激活+池化层,池化层本身就是对相邻单元起作用。而且即使随机丢弃,卷积层仍然可以从相邻的激活单元学习到相同的信息。因此,在全连接层上效果很好的Dropout在卷积层上效果并不好。Dropblock的研究者则干脆整个局部区域进行删减丢弃。cutout是一个数据增强方式,是将输入图像的部分区域清零,而Dropblock则是将cutout应用到每一个特征图。而且并不是用固定的归零比率,而是在训练时以一个小的比率开始,随着训练过程线性的增加这个比率。
优点一:Dropblock的效果优于Cutout
优点二:Cutout只能作用于输入层,而Dropblock则是将Cutout应用到网络中的每一个特征图上
优点三:Dropblock可以定制各种组合,在训练的不同阶段可以修改删减的概率,从空间层面和时间层面,和Cutout相比都有更精细的改进。
在目标检测领域,为了更好的提取融合特征,通常在Backbone和输出层,会插入一些层,这个部分称为Neck。相当于目标检测网络的颈部,也是非常关键的。
(1)SPP模块
SPP模块中,使用k={11,55,99,1313}的最大池化的方式,再将不同尺度的特征图进行Concat操作。
和Yolov4作者的研究相同,采用SPP模块的方式,比单纯的使用k*k最大池化的方式,更有效的增加主干特征的接收范围,显著的分离了最重要的上下文特征。
(2)FPN+PAN
FPN层自顶向下传达强语义特征,而特征金字塔则自底向上传达强定位特征,两两联手,从不同的主干层对不同的检测层进行参数聚合。
Yolov3的FPN层输出的三个大小不一的特征图①②③直接进行预测。但Yolov4的FPN层,只使用最后的一个76*76特征图①,而经过两次PAN结构,输出预测的特征图②和③。原本的PANet网络的PAN结构中,两个特征图结合是采用shortcut操作,而Yolov4中则采用concat(route)操作,特征图融合后的尺寸发生了变化。
(1)CIOU_loss
目标检测任务的损失函数一般由Classificition Loss(分类损失函数)和Bounding Box Regeression Loss(回归损失函数)两部分构成。
Bounding Box Regeression的Loss近些年的发展过程是:Smooth L1 Loss-> IoU Loss(2016)-> GIoU Loss(2019)-> DIoU Loss(2020)->CIoU Loss(2020)
好的目标框回归函数应该考虑三个重要几何因素:重叠面积、中心点距离,长宽比。
IOU_Loss:主要考虑检测框和目标框重叠面积。
GIOU_Loss:在IOU的基础上,解决边界框不重合时的问题。
DIOU_Loss:在IOU和GIOU的基础上,考虑边界框中心点距离的信息。
CIOU_Loss:在DIOU的基础上,考虑边界框宽高比的尺度信息。
CIOU_Loss和DIOU_Loss前面的公式都是一样的,不过在此基础上还增加了一个影响因子,将预测框和目标框的长宽比都考虑了进去。
(2)DIOU_nms
Nms主要用于预测框的筛选,
(1)提出了一种高效而强大的目标检测模型,使用 1080Ti 或 2080Ti 就能训练出超快、准确的目标检测器。
(2)在检测器训练过程中,验证了最先进的一些研究成果对目标检测器的影响。
(3)改进了 SOTA 方法,使其更有效、更适合单 GPU 训练。
想起第一次学习目标检测距现在已经快三年了,伴随着我的整个大学时光,借最近一个准备面试的机会重新学习,又加深了一次新的理解对于这些,为此倍感快乐。也深深感受到算法的奥妙与无穷,而且最深的理解还是得学习源码体会其实现的微妙,方能真正地理解算法。借用一位博主说的:在算法学习的过程中,去一些浮躁,好好理解算法比只会用算法难得很多。希望未来的自己能不忘初心,继续努力,也希望自己考研胜利,面试成功!!!
参考链接:
https://blog.csdn.net/algorithmPro/article/details/106632024
https://blog.csdn.net/weixin_42078618/article/details/85019707
https://muzhan.blog.csdn.net/article/details/82660381
https://blog.csdn.net/jiongnima/article/details/79094159
https://blog.csdn.net/jiongnima/article/details/78961147
https://blog.csdn.net/qinghuaci666/article/details/80900882
https://blog.csdn.net/tMb8Z9Vdm66wH68VX1/article/details/79637895
https://blog.csdn.net/jiongnima/article/details/79094159