好久没写论文解读了。整个6月到7月中旬都在琢磨tensorflow C++的API和生成dll。今天为大家带来一篇实时性的实例分割论文,入选ICCV2019,我认为是一篇相当有水平的工作。在能保证实时性的情况下,某些指标和针对某一种情况,效果居然比二阶段的分割方法还要好,速度是二阶段网络的几十倍吧。如果采用模型剪枝和量化,说不定工业上就能运行实例分割了。
updated on 2019.12.26
yolact已经出了yolact++
项目地址和yolact一致。同一作者。
idea
作者巧妙的将实例分割分为两个子任务:
- 为每一个anchor生成一系列的蓝本mask(prototype mask)
- 为每一个anchor预测一系列的mask系数(coefficients),这些系数的长度和上一步一系列蓝本mask的长度一致。就是说,每一个mask对应了一个值(coefficient)。
然后把系数和mask线性组合。就是一个mask和对应的系数相乘,然后所有的mask和系数的乘积求和。形式可以用下面的公式描述。
finalmask=∑inmaski×coefficientifinal mask = \sum_i^n mask_i \times coefficient_ifinalmask=i∑n?maski?×coefficienti?
和一般做实例分割的方法不一样吗,二阶段的实例分割一般遵循,先找到框,再分割的思路。作者提出的方法,两个子任务是并行的。子任务之间没有联系,并行处理。整个网络是单阶段的。棒!
Method
作者认为,mask具有空间连续性,即相邻的像素很可能被认为是一个类的。单阶段的目标检测算法都是依赖全连接层,(作者估计把1x1的卷积也当做全连接层了)那么就会出现一个问题,FC层的输出是没有空间的连续性的。MaskRcnn等一干算法,通过RePooling(Align,pooling)操作来重新获得空间连续性。但是这样就注定没法得到实时的效果了。作者将实例分割分解为两个子任务,全部都是基于卷积网络,自然不会遇到空间不连续的问题。
既然有两个子任务,并且前面提到了,两个子任务是并行的,所以整个网络就会有两个大的分支。
- 其中一个分支,沿着FPN的P3输出到ProtoNet,预测蓝本mask
- 另一个分支,将FPN的修正,即C5经过两个3x3的stride为2的卷积,作为P7和P6,C5和P5联系。从P7,P6,P5,P4,P3都去预测bbox和score。经过PredictionHead,和NMS输出。
protoNet
这个子网络,预测k个mask,过程就和语义分割完全一致。
就是一个上采样的过程。卷积层全是3x3的,上采样的过程是先Upsample,然后接conv层。和语义分割不同的是,这个网络不直接参与计算损失函数。
mask 的 系数(coefficients)
ProtoNet预测了K个mask,每一个maks都对应一个系数,就是在prediction head分支预测出来。
需要注意的是,这个分支同样需要计算坐标值的回归值(4个),类别概率(c个),mask的系数(k个)。就是说,一个anchor对应4+c+个输出。k个系数要接tanh激活函数。
两个分支的聚合(assembly)
predictionHead分支的过程:
- 每一个anchor对应c+k+4和值,按照score过滤一个anchor,接着NMS继续过滤一些anchor,这些活下来的anchor(论文用surviving)作为系数分支最终的输出。
两个分支的聚合就是简单地线性组合。这一点就是在idea那里提到了,mask*系数 再求和。然后再用sigmoid把值映射到0-1.
P是 h×w×kh\times w\times kh×w×k,C是n×kn\times kn×k,那么M就是h×w×nh \times w \times nh×w×n,n是预测目标个数,就是活下来的anchor数目。k是mask的数目。我们可以发现,即便是不同的目标(n的index不用),他们使用的mask是一样的。导致分割结果的不同,是因为不同的anchor的k个系数不一样,而通过用系数的线性组合,居然可以得到不同实例的分割图。赞!
作者也说了,本来也有更加复杂的融合方法,但他们就简单地使用了线性组合。
损失函数
bboxes分支和
score分支就用ssd提出的办法,mask分支就用BCE。
cropping mask
由于实例分割的特殊性,一张图有很多目标,如果把这些目标都在原图的大小计算损失函数,大的目标损失函数值之和较大,而小的目标损失函数值之和,因为像素数量不一样嘛,所以再求平均之后(除以像素总数),小目标的梯度相对于大目标就很小了,导致网络可能不重视小目标的分割。为了避免这个问题,作者的办法是:
- 在训练的时候,得到原图大小的mask,按照ground truth,把各个目标mask都crop出来,计算损失函数,再除以各自crop出来的区域像素总数。
- 在预测的时候,根据bboxes分支的目标预测框,把mask crop出来,再去和系数做线性组合,然后经过sigmiod和阈值分割。然和按照位置把得到的mask还原回原图位置上。
Mask分支预测的Prototype 起到了什么作用
作者展示了6个Prototype mask,(k=6),作者发现,每一个mask都有目标的某一部分(partition)很敏感。比如第4个mask,对左下角的目标很敏感;第3个mask对目标的边沿很敏感。通过线性组合,恰好组合形成实例目标的分割结果。
更多的details
不介绍了。不能像以前看论文那么细了,导师催得紧。目前注重方法。不过源码还是要看的。
这部分本该涉及:
- backbone 经过修改的ResNet101 + FPN结构,anchor也有修改
- Fast NMS
- 加入语义分割loss辅助监督
- 一些缺点,比如mask的泄露(mask不在目标身上),
实验效果
这里推荐一篇博客写的不错。比我详细