论文:Where are the Blobs: Counting by Localization with Point Supervision
Github: https://github.com/ElementAI/LCFCN
ECCV2018
论文贡献:
- 论文提出了4个新颖的loss,基于该loss,可以使得仅仅依靠一个点的标注,就使得模型学习到将每一个实例单独分出的blob。
- 论文提出了基于直线和基于分水岭算法的两种实例分割方法
- 本文的算法在Pascal VOC和Penguins数据集上取得了state-of-the-art的效果,该算法甚至超过基于强监督的深度特征的方法(多点回归,边框检测)
物体计数的几种方法:
(1)基于聚类的方法,counting by clustering
(2)基于回归 方法,counting by regression
(3)基于检测的方法, counting by detection
四个loss:
Image-level loss:
Ce为图片中出现的类别数目,包含背景类。
该loss的目的就是让训练后,模型的输出的多个通道中,对于每一个通道,至少有一个像素应该预测为该类别。
如果只有2个类别的情况(1个类别+背景类),让第一个通道预测为至少有一个像素应该预测为背景,第二个通道预测为至少有一个像素应该被预测为目标。
def compute_image_loss(S, Counts):
n,k,h,w = S.size()#input[1, 3, height, width],S:[1, 2, height, width],counts:[1,1]
# GET TARGET
ones = torch.ones(Counts.size(0), 1).long().cuda()#[1,1]
BgFgCounts = torch.cat([ones, Counts], 1)#[1,2]
Target = (BgFgCounts.view(n*k).view(-1) > 0).view(-1).float()#[2]
# GET INPUT
Smax = S.view(n, k, h*w).max(2)[0].view(-1)#[2]
loss = F.binary_cross_entropy(Smax, Target, reduction='sum')
return loss
Point-level loss:
该loss为正常的softmax交叉熵损失loss,所以需要忽略背景类别。
loss += F.nll_loss(S_log, points,
ignore_index=0,
reduction='sum')
Split-level loss:
该loss包含2种实现方法,基于直线的分割方法和基于分水岭分割方法。
基于直线的分割方法:
对于在blob集合B中的每一个blob,都是由blob中心坐标p周围的一系列点集构成,对于点集合bp中的任意一个点,都会与其周围点构成一个配对(pi,pj)。然后可以使用一条线,将pi,pj分开。直线分开的位置会被以最高的概率学习成为背景。从而使得一个blob与其周围的blob分开。
基于分水岭分割方法:
Si0表示像素i属于背景0的概率。
ai表示每一个blob中,属于该blob 的像素数目。
该loss让模型学习,使得2个相邻的blob之间由明显的分界线。
该loss包含个部分,第一个是基于全局的每一个blob内部的基于分界线的loss(Global loss),第二个是使得相邻blob分开的loss(split_mode loss)。
def compute_split_loss(S_log, S, points, blob_dict):
blobs = blob_dict["blobs"]
S_numpy = ut.t2n(S[0])
points_numpy = ut.t2n(points).squeeze()
loss = 0.
for b in blob_dict["blobList"]:
if b["n_points"] < 2:
continue
l = b["class"] + 1
probs = S_numpy[b["class"] + 1]
points_class = (points_numpy==l).astype("int")
blob_ind = blobs[b["class"] ] == b["label"]
T = watersplit(probs, points_class*blob_ind)*blob_ind
T = 1 - T
scale = b["n_points"] + 1
loss += float(scale) * F.nll_loss(S_log, torch.LongTensor(T).cuda()[None],
ignore_index=1, reduction='elementwise_mean')
return loss
# split_mode loss
if blob_dict["n_multi"] > 0:
loss += compute_split_loss(S_log, S, points, blob_dict)
# Global loss
S_npy = ut.t2n(S.squeeze())
points_npy = ut.t2n(points).squeeze()
for l in range(1, S.shape[1]):
points_class = (points_npy==l).astype(int)
if points_class.sum() == 0:
continue
T = watersplit(S_npy[l], points_class)
T = 1 - T
scale = float(counts.sum())
loss += float(scale) * F.nll_loss(S_log, torch.LongTensor(T).cuda()[None],
ignore_index=1, reduction='elementwise_mean')
False Positive loss:
Bfp表示预测blob为哪一个类别的那些像素,不包含背景类别。就是一个点周围的一圈的像素。
Si0表示第i类属于背景类别的概率。
整个过程优化第i类属于背景类别的概率为1,从而实现去掉FP的目的。其中,有FP的时候,才会优化该loss,否则不优化该loss。
def compute_fp_loss(S_log, blob_dict):
blobs = blob_dict["blobs"]
scale = 1.
loss = 0.
for b in blob_dict["blobList"]:
if b["n_points"] != 0:
continue
T = np.ones(blobs.shape[-2:])
T[blobs[b["class"]] == b["label"]] = 0
loss += scale * F.nll_loss(S_log, torch.LongTensor(T).cuda()[None],
ignore_index=1, reduction='elementwise_mean')
return loss
模型结构:
基础网络结构为vgg-16或者resnet-50,后续又进行上采样操作,最终输入和输出的大小一致。
实验结果:
结论:
4个loss单独提出非常新颖,使得点数可以取得很好的结果。