【计数算法归纳】一、论文学习笔记-LCFCN

本文主要参考:

https://blog.csdn.net/sinat_37532065/article/details/97910470

https://blog.csdn.net/qq_14845119/article/details/99883316

论文标题:《Where are the Blobs: Counting by Localization with Point Supervision》

出处:ECCV2018

原文链接:https://arxiv.org/abs/1807.09856v1

相关repo:https://github.com/ElementAI/LCFCN

摘要

    关于对象计数任务,当下最好的办法都是基于回归的思想进行优化。总的来说,这些方法比基于检测的方法效果好,因为相比检测模型,这些方法不需要学习如何定位对象位置、计算对象大小形状等。 

    原文中这样描述:“本文提出一种基于检测的方法LCFCN,但不需要估计对象大小和形状。”。我认为LCFCN的思想是借鉴了检测任务的定位思想,而模型搭建则是完全延用了语义分割模型。

1 介绍

目标计数问题存在的一些难点

(1)模型需要适应目标在形状、大小、姿势和外观方面的可变性;
(2)对象可能以不同的角度和分辨率出现,并且可能存在部分遮挡;
(3)背景、天气条件、光照等都会影响泛化效果。
    一般来说,基于检测的方法需要估计对象实例的位置、形状、大小,这些相对来说是更困难的任务。 

    对于对象计数任务,预测对象实例的确切大小和形状并不是必需的,并且通常会带来更困难的优化问题。出于此原因,本文简单地专注于在场景中定位实例来完成一个比检测更加容易的任务。

    现在最先进的基于密度估计的方法,通常假定对象有固定的尺寸(由高斯核定义)或限制环境,这使得其难以计算不同尺寸和形状的物体。LCFCN通过点监督来指导学习定位场景中出现的对象,可以灵活地为不同对象实例预测不同大小的区域,这使得其可适用于计算大小和形状不同的对象。

文章主要贡献:

(1)论文提出了4个新颖的loss,基于该loss,可以使得仅仅依靠一个点的标注,就使得模型学习到将每一个实例单独分出的blob。
(2)论文提出了基于直线和基于分水岭算法的两种实例分割方法
(3)本文的算法在Pascal VOC和Penguins数据集上取得了state-of-the-art的效果,该算法甚至超过基于强监督的深度特征的方法(多点回归,边框检测)
 

2 相关工作

(1)基于聚类的计数方法
    提取运动特征,进行无监督聚类。缺点是只适用于视频序列,不适用于静止图像。

(2)基于回归的计数方法
    基于回归的计数方法被证明比基于检测的方法更快、更准确,其主要包含glance类方法和密度估计两大类。

a. 密度估计类方法通过将点监督信息与高斯核卷积构造密度分布图来进行回归,其难点在于最佳高斯核尺寸高度依赖对象尺寸。

b. Glance类方法,也称子化范围类方法,优点是只需要图像级监督,缺点对画面中仅对象个数较少时比较有效。

(3)基于检测的计数方法
    对检测模型而言,定位被遮挡目标的位置,计算其大小和形状都是很困难的任务。而这些又是计数任务最需要解决的点,而且检测任务目标是得到对象的类别、位置、大小,其中大小和位置并不是计数模型所关心的。总而言之,基于检测计数方法虽然可行,但效果一般。

    LCFCN
    本文提出的LCFCN模型结构类似经典语义分割模型,如U-Net等。但不同之处在于,LCFCN在训练时只需要点监督信息,而不需要像训练语义分割模型一样,需要全部像素点的信息。

3 Localization-based Counting FCN

LC: 本文提出的基于定位的计数损失

FCN:全卷积网络(此处可以替换成任何语义分割模型)

3.1 损失函数解释


LC-loss由四部分组成:

    前两项图像级别loss和点级loss参考弱监督语义分割模型设计,目的是迫使模型预测图像中每个像素的语义标签。但仅使用这两部分loss是不足以进行对象计数的,因为语义分割模型并不具有区分不同实例对象的能力,所以可能得到的结果就是一个预测的blob中包含多个instance。

    第三项loss目的是鼓励模型为每个instance输出一个单独blob。

    第四项loss目的是为了去除不含有instance的blob。

    最重要的一点,LC-loss仅需要指示对象位置的点级注释,而不需要诸如大小、形状之类的信息。

(1)Image-level loss


    Ce表示图像中出现的对象的类别集合,对于Ce中的每一类,该项loss旨在提高其出现至少一个像素点的概率;C-e表示图像中没有出现过的对象的类别集合,对于C-e中的每一类,该项loss旨在降低其出现任意一个像素点的概率。和都是从Ground Truth中得到的。

    直观来说,对于图像中出现的每一个类别,画面中应该至少有一个像素点被预测为该类。而对于画面中没有出现的任一类别,画面中不应该有任何一个像素点被预测为该类。本文中假定每幅图像中均存在背景像素,所以背景像素被归在Ce当中。

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

(2)Point-level loss

该项loss(仅)鼓励模型正确地标记Ground Truth中一小组受监督的像素点。其中,Is表示Ground Truth中受监督的点集,也代表了对象在画面中的位置信息。注意,该项loss忽略了所以未标注的点。

loss += F.nll_loss(S_log, points,
                       ignore_index=0,
                       reduction='sum')

(3)Split-level loss
    该项loss阻止模型输出包含大于等于两个注释点的blob。大体做法是先找出包含两个以上对象的blob,然后找出两个(或多个)对象的边界,最后引导模型学习如何将边界上的点预测为背景,这样就达到了拆分多个对象的目的。

    a. 首先,对模型输出的feature map进行处理,根据是否是前景/背景进行二值化,得到一个二进制前景掩码;
    b. 通过连通域查找算法,得到中的blob集合;
    c .进而得到集合中包含有两个及两个以上对象的blob集合,最后运行分割blob算法。

本文提出了两种分割blob的方法:

(1)线分割法

    考虑B中的任意一个点对,通过一个评分函数来计算这一个点对所构成的分割线是否是最优的划分界限。评分函数被定义为分割线上所有点属于背景的平均概率:

    最佳分界线被定义为在所有线中具有最高背景概率的那条,即两个对象之间最有可能的分离界线。 

(2)漫水分割法

漫水分割法由全局和局部两个分割步骤组成。

global step:将GT-points设为种子点,在整副图像上应用漫水法分割。基于分割结果,在前景概率矩阵上进行距离变换,最终可以得到k个分割区域,k是Ground  Truth中的对象个数。
local step:对中的每一个blob,基于其包含的GT-points作为种子点,进行漫水分割。
最后,将两步分割结果定义为边界。

    上图中解释了两种分割方法的分割效果(黄色线)。基于两种方法任意其一,可以得到分割边界集合Tb.这样就可以按照下式计算分割loss:

其中,Si0表示表示像素属于背景的概率,ai表示像素所属的blob中的注释点的个数。该项loss鼓励模型专注于拆分包含有更多GT-points的blob,其动机是通过学习如何预测不同对象之间的边界来使得模型更容易区分它们,因而通过该惩罚措施使得每一个blob中只包含一个注释点。

对计数任务,获取正确的分割边界是没有必要的。我们只需要保证每个对象身上有一个正的响应区域,且在对象之间有一个负的响应区域即可。这些loss都是辅助训练使用的,在推理时完全基于从输出矩阵中获取的blobs。

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')

(4)False Positive loss

该项loss抑制模型输出不包含对象的blob,对于虚警blob集合中的每一个像素点,最大化其属于背景类的概率。这一点对提高对象计数效果至关重要。

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 

3.2 LC-FCN模型结构和推理过程

    LC-FCN可以使用任何FCN结构,如FCN-8s、Deeplab、PSPNet等。主干网络部分可以使用ResNet、VGGNet等抽取特征,上采样部分旨在为每个像素点输出一个得分。

    LC-FCN的推理过程:

(1)上采样路径输出矩阵Z,每个点表示对应像素属于类c的概率;
(2)对每个类别,如果某像素点预测为该类,则置为1,反之为0,从而得到每个类别对应的一张二进制mask;
(3)对每个类别,查找其连通域个数,即为预测出的该类对象个数。

4 实验
4.1 评价指标
对於单一类别数据集,评价指标使用mean absolute error (MAE)
对于多类别数据集,评价指标使用mean root mean square error (mRMSE).
4.2 实验结果


4.3 分解研究
loss函数分析


    前两项loss(Image-level loss 和 Point-level loss)旨在通过点级注释训练语义分割模型,结果是如果只用这两项loss训练模型,会得到一个巨大的blob,其中包含很多个实例对象。
    第三项split loss旨在鼓励模型输出不包含多个对象的blob。这样的结果导致我们得到很多个blob,且很少有包含多个对象的blob。
    但仅靠上述三项loss,我们没有抑制模型的虚警,这导致模型预测结果中包含一些没有对象的blob,即误报。
最后一项fp-loss旨在阻止模型预测不包含点注释的blob。通过添加该项,LC-FCN不论是在定性还是定量角度得到了显著的改善。split-loss和fp-loss配合使用,互补缺点。


分割方法对比

    在split-loss设计中,文章提出了线分割法和漫水填充法两种方法来分割大的blob。通过实验对比,表明漫水分割法效果更佳,所以本文实验都采用了该方法计算split-loss。但分割方法是具有启发性的,后续这是一个可以重点优化的地方。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章