点数估计之LCFCN

论文:Where are the Blobs: Counting by Localization with Point Supervision

Github: https://github.com/ElementAI/LCFCN

 

ECCV2018

 

论文贡献:

  1. 论文提出了4个新颖的loss,基于该loss,可以使得仅仅依靠一个点的标注,就使得模型学习到将每一个实例单独分出的blob。
  2. 论文提出了基于直线和基于分水岭算法的两种实例分割方法
  3. 本文的算法在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单独提出非常新颖,使得点数可以取得很好的结果。

发布了219 篇原创文章 · 获赞 898 · 访问量 140万+

猜你喜欢

转载自blog.csdn.net/qq_14845119/article/details/99883316