YOLACT: Real-time Instance Segmentation

Github: https://github.com/dbolya/yolact

论文提出了基于one-stage的目标检+分割的框架YOLACT。类似于YOLO,主打的亮点在于实时性。精度弱于mask-rcnn,但是速度却比mask-rcnn快很多。在MS-COCO上达到了29.8的map和33fps的速度(Titan XP)。

论文贡献:

  1. 提出了实时(>30fps)的检测+分割框架YOLACT
  2. 提出了NMS的改进版fast nms,可以比传统的NMS快12ms,并且只有很小的精度损失。
  3. 有别于传统的思路,直接在分类,回归,并行的加一个分支进行分割的预测。而是将其分为了2个任务。第一步,产生整体的prototype masks,类似于语义分割,但是只区分前景背景,不区分类别,可以获得整体图像的mask,对小目标也有更好的感受野。第二步,预测相等通道数的prototype masks的加权组合的权值,有正负之分。最后,将前2步的预测组合起来,得到不同anchor对应的mask分割结果。

整体结构:

网络基础骨架为ResNet101,采用FPN的思想进行底层和高层的特征融合。输入图片大小为550*550。网络最大的亮点,就在于,增加了下面的prorotype分支进行不同位置和前景背景分割的预测。并且在FPN的预测部分,也增加了分割的一个分支。使用FPN处分割的分支预测的权值和prorotype mask结合,生成每一个anchor对应的mask分割结果。

Prototype mask结构:

Prototype部分通过对P3的feature map进行卷积操作和反卷积操作,生成最终138*138*k的输出特征图。对p3层进行了1次上采样操作。最终输出的特征图大小为原图的1/4大小。输出的通道数为k,这里的k默认取32。K值具有很强的鲁棒性。即使取别的值,也不会对结果有很大的影响。

这里,底层的特征图,可以产生更加鲁棒的mask。高层的特征图可以产生更加高质量的特征,同时对小目标效果更好。所以这里选择了高层特征P3层。

Prototype分支,最终输出经过了RELU处理,保证最终的输出的分割结果都是正值。

Mask Coefficients分支:

Mask相关系数分支,对RetinaNet的多任务分支,进行了融合。最终在传统分类和回归的2个分支的基础上,加了一个额外的分割的分支。

其中,c表示分类的类别数,a表示anchor数目,k表示分割的输出通道数,和Prototype分支输出的通道数一样多。

 

Mask Assembly:

其中,P表示h*w*k的prototype masks,C表示n*k的mask coefficients,其中n表示经过NMS之后的预测的物体个数。σ表示sigmoid操作,保证输出的最终mask为正值。

 

损失函数:

分类Lclssoftmax CrossEntropc个类别+1个背景类)

回归LboxSmooth L1

实例分割Lmaskbinary cross entropy

语义分割:Prototype mask分支模块加入二分类的语义分割loss。在训练过程中,直接在P3层的输出,接一个1*1卷积,输出c个通道,进行语义分割的loss传递训练。类似YOLOv3,使用c个sigmoid二分类,而不是c+1个softmax多分类。最终可以获得0.4map的收益提升。

这个分支和mask rcnn的还是有区别。yolact类似于回归k个值,k个值可以进行排列组合,可以得到2^k个组合方式,从而得到最终的结果。而mask rcnn中分割的channel数为类别数目,不同的channel针对不同的类别进行分割的学习。单从通道数来看,yolact也进行了相应的参数的压缩。

 

当然也可以不接该分支进行训练。在测试的时候,不需要该分支。

 

Anchor设置:

aspect ratios 1,  1/2,  2

scales 24 48 96192384

 

Prototype Behavior:

Prototype 1,4分别激活左面的边界,5激活下面的边界。Prototype 2负责激活底部,左面的方向。Prototype3负责激活背景,Prototype6负责激活前景。

Fast NMS

1)对每一个类别ci,取top-n个候选目标,并按得分降序排列;

2 计算一个c×n×nIOU矩阵,其中每个n×n矩阵表示对该类n个候选框,两两之间的IOU

3)因为自己与自己的IOU=1,IOU(A,B)=IOU(B,A),所以对上一步得到的IOU矩阵进行一次处理。具体做法是将每一个通道,的对角线元素和下三角部分置为0(公式2);

4)去除与得分高的候选框重叠比例较大的框,具体做法是对上一步得到的矩阵,按列取最大值(公式3),然后对取完最大值的矩阵按阈值t划分,大于阈值t的全部置为0(K>t),得到最终Fast NMS的结果。

layers/functions/detection.py

def fast_nms(self, boxes, masks, scores, iou_threshold=0.5, top_k=200, second_threshold=False):
        scores, idx = scores.sort(1, descending=True)
 
        idx = idx[:, :top_k].contiguous()
        scores = scores[:, :top_k]
    
        num_classes, num_dets = idx.size()
 
        boxes = boxes[idx.view(-1), :].view(num_classes, num_dets, 4)
        masks = masks[idx.view(-1), :].view(num_classes, num_dets, -1)
 
        iou = jaccard(boxes, boxes)
        iou.triu_(diagonal=1)
        iou_max, _ = iou.max(dim=1)
 
        # Now just filter out the ones higher than the threshold
        keep = (iou_max <= iou_threshold)
 
        # We should also only keep detections over the confidence threshold, but at the cost of
        # maxing out your detection count for every image, you can just not do that. Because we
        # have such a minimal amount of computation per detection (matrix mulitplication only),
        # this increase doesn't affect us much (+0.2 mAP for 34 -> 33 fps), so we leave it out.
        # However, when you implement this in your method, you should do this second threshold.
        if second_threshold:
            keep *= (scores > self.conf_thresh)#0.05
            #keep *= (scores > 0.3)
 
        # Assign each kept detection to its corresponding class
        classes = torch.arange(num_classes, device=boxes.device)[:, None].expand_as(keep)
        classes = classes[keep]
 
        boxes = boxes[keep]
        masks = masks[keep]
        scores = scores[keep]
        
        # Only keep the top cfg.max_num_detections highest scores across all classes
        scores, idx = scores.sort(0, descending=True)
        idx = idx[:cfg.max_num_detections]
        scores = scores[:cfg.max_num_detections]
 
        classes = classes[idx]
        boxes = boxes[idx]
        masks = masks[idx]
 
        return boxes, masks, classes, scores

这里有个需要注意的地方,就是second_threshold=False,这个参数,来保证对得分很低的框进行滤除。(self.conf_thresh=0.05)对于正常的NMS来说,是应该做这一步的。但是在这里没有直接做这一布,而是在后续的处理中(layers/output_utils.py中postprocess函数),使用更高的阈值0.3进行滤除。

if score_threshold > 0:#score_threshold=0.3
        keep = dets['score'] > score_threshold
 
        for k in dets:
            if k != 'proto':
                dets[k] = dets[k][keep]
        
        if dets['score'].size(0) == 0:
            return [torch.Tensor()] * 4

上面的操作放在哪里做的都可以,效果也都一样。

 

在YOLACT框架中,Fast NMS相比Cython实现的传统NMS快11.8ms,并且精度只下降0.1MAP。

在MASK-RCNN框架中,Fast NMS相比CUDA实现的传统NMS快16.5ms,并且精度只下降0.3MAP。

实验结果:

Mask 质量

Mask 得分:

Fast NMS效果和效率的平衡:

Prototypes中输出通道数k的鲁棒性测试

其他baselines:

讨论:

定位失败,Localization Failure

YOLACTMap精度低,主要是由于分类的错误和边框定位不准造成。而分割的结果会受到定位不准的影响。

如上图2个相邻的卡车定位成了一个,导致分割也没有分割开。

 

泄露,Leakage


由于YOLACT采用整体分割,再组合的方式得到mask,再从中根据框切出mask的方式,所以对框周围的噪声比较敏感。而对于mask-rcnn这种有了框再分割的思想,对于外部噪声就不敏感,就不容易犯这样的错误。

对于一个框内部的相同类别的分割,容易都当成一个。如上图的人左下角的一部分,都会将另一个人的一部分身体当成这个人的。

 

总结:

  1. YOLACT,一个实时的检测+分割的one-stage框架。
  2. 将分割分为Prototype mask和Mask Coefficients两个部分的组合的思想非常好。
  3. 实际使用感觉,相比mask rcnn,yolact的漏检,误检,泄露问题都明显多一些。可以在精度要求不高,速度要求高的场合考虑使用。