CVPR 2023 | Texture-guided Saliency Distilling for Unsupervised Salient Object Detection

CVPR 2023 | Texture-guided Saliency Distilling for Unsupervised Salient Object Detection

image.png

主要内容

  • 目标任务: 无监督显著性目标检测, 包括RGB、RGB-D、RGB-T和Video SOD(这里使用预先计算的光流作为辅助输入).
  • 目标问题: 无监督显著性目标检测任务的常用策略是伪标签手段. 伪标签中会存在大量的噪声. 如何处理带噪标签是无监督显著性目标检测任务工作的一大重点. 现有方法专注利用有着更加可靠标签的容易样本, 但是忽略了难样本中有价值的知识. 这篇文章中关注与同时挖掘难易样本中的显著性知识.
  • 具体方法: 提出了两种策略来产生高质量的伪标签从而训练一个一般的显著性检测器.
    • 提出了Confidence-aware Salinecy Distilling策略基于样本的置信度来给样本打分. 通过利用自适应与训练过程的损失, 引导模型从容易样本到更复杂样本的渐进式学习.
    • 提出了Boundary-aware Texture Matching策略, 通过匹配纹理和预测的边界来细化噪声标签的边界.
  • 本质内容: 本文的工作主要是围绕伪标签生成阶段(即论文的第一阶段)中, 对模型的损失进行的设计. 对于第二阶段则直接使用现有的方法在第一阶段模型的预测(即伪标签)上进行学习.

具体方法

image.png

为了获得初始预测, 模型的设计借鉴了Activation to saliency: Forming high-quality labels for unsupervised salient object detection(同一个作者)中的方案.

image.png

  1. 从MoCo-V2预训练的ResNet-50中得到编码特征。
  2. 利用四个SEBlock集成.
  3. 去均值后,沿通道求和。这里移除空间均值 H ˉ \bar{H} Hˉ的处理可以辅助后续使用的固定的阈值实现对于输入的自适应效果,也可以确保正负样本的共存。
  4. 代码中inv之前并没有Sigmoid。为了确定预测中趋近于0的是前景还是背景,这里作者在代码中使用四角顶点处的预测结果做了一个人工的判定。从而对预测结果进行翻转。通过这一方式,将包含更多角的预测区域认为是背景,即小于0。
    1. 如果四点大于零的数量小于2, 即0和1的时候, 认为需要翻转. 即此时大于0的区域包含的角太少了.
    2. 如果四点大于零的数量大于等于2, 即2、3、4的时候, 此时大于0的区域包含了足够多的角, 就不需要翻转.
  5. 通过这里的计算,可以获得初始的显著性预测。现有的无监督方案,主要有两种定位显著性区域的方法,一种是基于传统的显著性方法生成的带噪声的显著性线索,另一种则是使用预训练模型来生成。本文使用的是后者。有两点原因,前者需要更多额外的计算,而且网络可以专注于挖掘显著性线索,而非拟合传统方法的固有偏置。在使用中,会使用固定的阈值0.5二值化得到初始标签。这里由于有前面去均值的处理,所以确保了对于输入图像的自适应。
def normalize(x):
    center = torch.mean(x, dim=(2, 3), keepdim=True)
    x = x - center
    return x

def foreground_sign(pred):
    b, c, w, h = pred.size()
    p = pred.gt(0).float()
    num_pos = p[:, :, 0, 0] + p[:, :, w-1, 0] + p[:, :, w-1, h-1] + p[:, :, 0, h-1]
    sign = ((num_pos < 2).float() * 2 - 1).view(b, c, 1, 1)
    return sign

...
feat = normalize(feat) # 去均值
pred = torch.sum(feat, dim=1, keepdim=True) # 沿通道求和
# Sign function
pred = pred * foreground_sign(pred) # 是否反转预测
pred = nn.functional.interpolate(pred, size=x_size, mode='bilinear', align_corners=True)

这里实际上也反映了一个现象, 直接从编码器特征图中获得的特征的正负并非是最终预测我们想要的正负. 这只实现了基本的区域划分, 但并未做到对于感兴趣区域的突出(即使用更大的值表示). 所以我们需要引入额外的信息来确定哪一区域是我们感兴趣的, 从而将其使用更大值突出出来.

这一前背景翻转的现象在全监督形式中的解码器的特征中也有体现.

对于全监督, 我们可以利于真值来实现参数化的翻转过程, 而对于无监督任务, 这就存在一定的困难. 这里坐着使用了类似于"中心先验"的传统显著性思想进行了人工设定.

本文工作的核心是设计第一阶段(训练用于伪标签生成的模型)的训练策略, 即损失函数.

第一阶段使用的数据仅仅是不同模态数据集的训练部分.

image.png

这里通过提出了三个不同的损失来对第一阶段(训练用于伪标签生成的模型)进行约束.

  • Lcsd, 即Confidence-aware Saliency Distilling损失. 系数为1.
  • Lbtm, 即Boundary-aware Texture Matching损失. 系数为0.05.
  • Lms, 即Multi-scale Consistency损失. 系数为1.

模型训练过程中, 引入了尺度一致性.

每次前向计算过程中, 会有两个不同尺度的输入独立前向传播. 这里一个可以理解为原始的预设输入尺寸, 另一个会有一个相对的放缩, 实际设置中, 前者为320, 后者为192、256、384、448中的随机值.

二者的输出独立计算Lcsd和Lbtm, 但是直接也会用Lms进行一致性约束.

第一阶段得到的显著性预测通过CRF优化后, 会作为初始的伪标签, 用于第二阶段的标签训练现有的方法.

第二阶段训练模型实际使用的数据集与现有的SOD方法一致.

Confidence-aware Saliency Distilling

如何获得高质量的伪标签是无监督算法关注的核心, 现有方案中, USPS使用来自传统方法的初始标签按照全监督的形式来训练深度模型, 但是这种全监督的形式对于无监督方法而言并非最优. 而另一种方案则是基于难易样本的学习策略, 例如A2S的v1版本更重视那些容易的样本. 然而难样本中, 噪声中隐含的显著性知识未得到充分探索. 对于无监督显著性方法, 以一个更有意义的顺序组织样本的方案可能更重要.

这里提出了CSD策略, 根据不同样本的置信度来给有噪声标签的样本打分. 受启发于self-paced learning(Curriculum learning), 这里对损失训练时的损失计算过程进行了调整, 提出了CSD损失:

image.png

这里对预测图中各点的显著性得分与0.5计算绝对差异, 使用一个与训练进度相关的指数来引入一种动态的适应性. 这里的参数 ρ \rho ρ是一个随着训练进度从0到1线性增加的参数.

这里同时可以定义置信度得分 ∣ k i ∣ = ∣ Φ ( x i ) − 0.5 ∣ |k_i|=|\Phi(x_i) - 0.5| ki=∣Φ(xi)0.5∣.

这里将显著性得分接近0.5的像素定义为难样本(这一思路和CVPR 2022的COD方法ZoomNet中的不确定性感知损失的设定类似). 难样本中这部分区域容易被误判, 因此传统的例如BCE损失并不能够帮助模型从这些区域中学习到鲁棒的显著性知识.

具体实现可见https://github.com/moothes/A2S-v2/blob/6108a48300b713dd2830d8b54d462d75fb0ac3eb/methods/a2s/loss.py#L56-L62.

def ADBloss(pred, feat, mask=False, epoch=1, config=None):
    """
    这里应当是论文中的 csd 损失
    """
    mul = 2 ** (1 - (epoch - 1) / config['epoch'])
    loss_map = torch.abs(pred - 0.5)
    loss_map = torch.pow(loss_map, mul)

    loss = pow(0.5, mul) - loss_map.mean()
    return loss

这里定义是ADB, 但是形式上与论文中的CSD颇为相似.

作者对比了四种形式的损失:

image.png

这里反映出了所提的CSD损失的梯度对于难易(即接近0.5的程度)样本、训练进度的适应性. 对于容易样本, 损失的梯度始终维持在一个较大的数值, 而难样本则随着训练的进行, 梯度逐渐增大到1. 即在训练的后期对加大了对于困难数据的损失梯度.

所以实际上这部分就是独立定义了一个损失函数.

Boundary-aware Texture Matching

这一过程是为了通过匹配纹理细节, 从而对齐显著性边界和图像边缘.

为了定义纹理信息, 作者引入了局部二值模式(Local Binary Pattern)的思想.

对于显著性预测中特定像素 Φ ( x i ) \Phi(x_i) Φ(xi), 计算其与邻域内像素的绝对差, 并组成纹理向量: T i s = [ t i , 1 s , t i , 2 s , … , t i , k 2 s ] , t i , j s = ∣ Φ ( x i ) − Φ ( x j ) ∣ , j ∈ K i T^s_i = [t^s_{i, 1}, t^s_{i, 2}, \dots, t^s_{i, k^2}], t^s_{i, j} = | \Phi(x_i) - \Phi(x_j) |, j \in K_i Tis=[ti,1s,ti,2s,,ti,k2s],ti,js=∣Φ(xi)Φ(xj),jKi

  • K i K_i Ki是目标像素的 k × k k \times k k×k大小的邻域.

其实局部二值模式的处理思路与梯度算子的思想有共同之处, 但是比较特殊的是, 这里使用了绝对差, 使得其与梯度算子产生了不同.

从外观数据提取纹理向量的过程依赖于RGB图像、光流图像、深度图像或者是红外图像. 通过集成多模态数据中的丰富的外观信息, 可以获得更加通用的引导.

在外观信息中的纹理向量的计算中, 为了提取更具有判别能力的特征, 这里使用了如下形式, 对不同模态的数据计算的纹理信息进行了加和.

T i a = [ t i , 1 a , t i , 2 a , … , t i , k 2 a ] ,   t i , j a = exp ( − α ∑ m ∣ ∣ x i ( m ) − x j ( m ) ∣ ∣ 2 ) ,   j ∈ K i T_i^a = [t^a_{i, 1}, t^a_{i, 2}, \dots, t^a_{i, k^2}], \ t^a_{i, j} = \text{exp}(-\alpha \sum_m || x_i^{(m)} - x_j^{(m)} ||^2), \ j \in K_i Tia=[ti,1a,ti,2a,,ti,k2a], ti,ja=exp(αm∣∣xi(m)xj(m)2), jKi

  • α \alpha α是一个超参数, 实际设为200.
  • x i ( m ) x_i^{(m)} xi(m)表示不同模态的外观数据.

由此, 差异较小的两个像素会产生一个较小的 t s t^s ts和较大的 t a t^a ta.
更大的后者有助于帮助从多模态数据中提取更具判别的特征表示.

image.png

最终使用两个从不同角度计算的纹理信息进行向量内积的计算, 得到最终的BTM损失.

关于"为什么写成这样的形式"的一些个人理解:

  • 作为损失函数, 最终需要越来越小, 所以如果从相似度的角度而言, 损失形式必然与相似度成反比. 就相似度而言, 这里利用了类似于余弦相似度的方式计算了两个未归一化的纹理向量的内积, 所以也可以理解为是相似度的一种. 两个模式中, 来自真实数据的模式显然是一个重要的参考信息. 所以这里利用指数函数的非线性, 突出了外观最相似部分的位置(空间信息). (似乎不太合理,没有归一化就不好说了)
  • Ta项实际上可以看做是对于Ts项的一个调制, 对于外观更接近的区域, 放大其显著性的差异, 从而通过优化过程促使其减小. 实现一致性的匹配. (这一点其实形式有些类似于双边滤波的计算方式)

这里使用的 b i b_i bi是二值边界掩码, 可以通过膨胀与腐蚀结果的差值获得这一结果.

def get_contour(label):
    lbl = label.gt(0.5).float()
    ero = 1 - F.max_pool2d(1 - lbl, kernel_size=5, stride=1, padding=2)  # erosion
    dil = F.max_pool2d(lbl, kernel_size=5, stride=1, padding=2)  # dilation
    edge = dil - ero
    return edge

整体的计算代码如下:

def ACLoss(pred, image, radius, config=None):
    """Appearance Coherence Loss,
    应当是论文中的 btm 损失。

    Args:
        pred (torch.Tensor): B,1,H,W
        image (torch.Tensor): B,3/6,H,W
        radius (int, optional): 邻域半径. Defaults to 5.
        config (_type_, optional): 相关设置. Defaults to None.

    Returns:
        torch.Tensor: 损失
    """
    rgb = config["rgb"]  # rgb=200 这应该是对应于公式中的alpha。
    modal = config["trset"]  # c/d/o/t/cdot
    # modal中即使没有c,也会读取rgb。所以这里modal数量也会加一。
    num_modal = len(modal) if "c" in modal else len(modal) + 1

    # 缩小尺寸到1/4倍
    sal_map = F.interpolate(pred, scale_factor=0.25, mode="bilinear", align_corners=True)
    image_ = F.interpolate(image, size=sal_map.shape[-2:], mode="bilinear", align_corners=True)
    features = torch.cat([image_, sal_map], dim=1)
    # 3*num_modal+1是拼接后数据的通道组成成分。显著性预测放到了最后。
    slices = range(0, 3 * num_modal + 1, 3)

    N, C, H, W = features.shape
    diameter = 2 * radius + 1
    # 利用unfold,实现局部二值模式的计算,邻域像素减去中心值。
    kernels = F.unfold(features, diameter, 1, radius).view(N, C, diameter, diameter, H, W)
    kernels = kernels - kernels[:, :, radius, radius, :, :].view(N, C, 1, 1, H, W)

    # 从外观信息计算纹理表示
    dis_modal = 1
    for idx, slice in enumerate(slices): # slice: 0,3,6
        if idx == len(slices) - 1:
            # 只处理彩色数据的通道,即前面数个3通道,对于最后的sal通道跳过
            continue

        dis_map = -rgb * kernels[:, slice : slices[idx + 1]] ** 2
        dis_map = dis_map.sum(dim=1, keepdim=True).exp() # N,3,K,K,H,W -> N,1,K,K,H,W

        if config["only_rgb"] and idx > 0:
            dis_map = dis_map * 0 + 1

        # 这里是在之前项的基础上乘的,由于是指数形式,所以等价于指数项相加了。
        dis_modal = dis_modal * dis_map

    # 从显著性预测信息计算纹理表示
    dis_sal = torch.abs(kernels[:, slices[-1] :]) # N,1,K,K,H,W

    # 公式5,这里把向量的内积拆分为点积和加和两个过程了。
    distance = dis_modal * dis_sal # N,1,K,K,H,W
    loss = distance.view(N, 1, diameter ** 2, H, W).sum(dim=2) # K,K -> 0

    mask = get_contour(sal_map)
    loss = torch.sum(loss * mask) / torch.sum(mask)
    return loss

Multi-scale Consistency

这里强调了显著性目标在不同尺度输入中所具有的一致性. 这可以理解为作者的一种假设. 这里通过对输入图像放缩到特定的参考尺寸, 通过强制约束放缩前后预测结果的一致性, 从而鼓励模型生成一致的预测.

def Loss(pre1, img1, pre2, img2, epoch, ws, config):
    """A2S-v2第一阶段的损失函数。

    实际调用:

        Y = model(images, 'train')
        config['param'] = tran_param(config)
        images_temp = transform(images, False, config) # 尺度放缩
        priors = torch.cat(priors, dim=1)
        priors_temp = transform(priors, False, config)
        Y_ref = model(images_temp, 'train')
        loss0, loss1, loss2 = model_loss(Y, priors, Y_ref, priors_temp, epoch, lr_weight, config)

    Args:
        pre1: 原始图像输入的预测, N,1,H,W
        img1: 原始图像, 以及可能存在的辅助模态的图像, N,3/6,H,W
        pre2: 变换后的原始图像输入的预测, N,1,H,W
        img2: 变换后的原始图像, 以及可能存在的辅助模态图像, N,3/6,H,W
        epoch: 当前的 epoch, 从1开始
        ws: 不同损失成分的权重
        config: 配置

    Returns:
        tuple: 各个损失成分
    """
    p1 = torch.sigmoid(pre1["sal"][0])
    p2 = torch.sigmoid(pre2["sal"][0])

    adb_loss = ADBloss(p1, epoch, config) + ADBloss(p2, epoch, config)
    if ws[1] > 0:
        ac_loss = ACLoss(p1, img1, 5, config) + ACLoss(p2, img2, 5, config)
    else:
        ac_loss = 0

    # 公式6:基于尺度变换一致性的监督(img1和img2的差异就在于后者是对前者的一个放缩)
    p2 = F.interpolate(p2, size=p1.size()[2:], mode="bilinear", align_corners=True)
    mse_loss = torch.mean(torch.pow(p1 - p2, 2))
    return adb_loss * ws[0], ac_loss * ws[1], mse_loss * ws[2]

性能对比

RGB SOD

image.png

这里实验了三种变体:

  • Ourss1: 第一阶段模型的结果, 不使用后处理.
  • Ours: 完整方法, 此时第一阶段不使用多模态数据.
  • Oursmm: 第一阶段使用多模态数据, 第二阶段使用带有伪标签的RGB SOD训练集训练额外的检测器.

关于该表的一些讨论可见issue:https://github.com/moothes/A2S-v2/issues/3

这里其实引出了这样的疑惑:**为什么训练集自训练得到的伪标签模型自身性能(Ourss1), 却比使用这些伪标签在这些训练数据上监督训练的方案(Ours)效果要差这么多呢?**难道说是因为额外的预测数据的存在的缘故? 还是说全监督形式本就具有自训练形式所不具有的某种优势, 即使在伪标签的辅助下也可以获得更好的泛化效果?

关于Ourss1和Ours的性能差异, 作者认为主要原因可能有以下几点:

  1. 网络拟合的是训练集中图像的信息,并不能保证在测试图像上的结果。举例来说,Lbtm可以指导网络在训练图像上得到不错的边缘,但是由于测试图像中颜色分布差异等因素,并不一定能够在测试图像上得到很好的边缘;
  2. 对生成的显著性伪标签使用了CRF,进一步优化了它们的边缘,也会带来一定的性能提升。

我个人觉得或许可以理解为, 第一阶段用于拟合一个逼近训练集的分布, 第二阶段则由于训练的时间少, 基本处于欠拟合的状态, 对于数据中的高频噪声具有一定的过滤能力, 再加上其本身无监督预训练的初始参数, 可能获得了更好的测试性能.

第二第三个变体有着较小的性能差异, 作者任务这主要是因为DUTS较大的数据规模使得显著性模型检测器性能趋于饱和, 即使是无监督形式(上限可能低一些).

RGB-Depth/Thermal/Flow SOD

image.png

这里对比了两种变体:

  • Ours: 仅使用任务特定的数据训练
  • Oursmm: 使用来自四个SOD任务的多模态数据.

伪标签的质量

image.png

作者展示了在不同数据集上伪标签自身的得分:

  • Ours仅使用任务特定的数据.
  • Oursmm使用了所有的多模态训练数据.
  • Oursrgb则使用了所有的rgb训练数据.

性能对比可以看出, Oursmm展现出了更好的性能, 这体现了多模态数据对于提升伪标签的质量的意义. 同时也展现出更多的训练数据的价值.

这里指出, 对于RGB和RGB-T上, Oursmm相较于Ours的性能劣势, 主要是因为, 由于没有真值, 提出的方法拟合混合数据集可能造成一些子集上的性能下降.

初始显著性线索的影响

image.png

这里对比了两种不同的伪标签策略:

  • 类似于其他深度学习的USOD方案, 使用传统方法来获得初始显著性线索.
  • 本文使用的, 基于预训练模型的激活图的方案.

对比中可以看到, 训练前后本文的方案性能展现出了大幅度的提升. 优于基于传统方案的策略.

这里令人疑惑的几个点:(具体讨论可见issue:https://github.com/moothes/A2S-v2/issues/4)

  • 使用的训练集是什么? 性能评估使用的测试集是什么?
    • 上述所有实验结果(Before training和After training)都是在MSRA-B的训练集+验证集上面完成的.
  • MC、DSR、RBD这些方案的训练指代的是什么?
    • 这一问题的理解可以参考文中Fig2里面的Noisy label G. 在实现中, G是通过网络生成的单通道图像二值化得到的噪声标签, 可以将其替换成MC、DSR和RBD等某个传统方法得到的噪声标签, 用于网络训练. 值得注意的是, 在消融实验中, 如果使用本文提出的方式, 每张图像的标签在训练过程中是不断优化的; 如果使用传统方法的结果, 每张图像的标签在训练过程中是固定的. 作者认为, 这也是方法能够在训练后得到更好伪标签的原因之一.
    • 关于涉及到所谓"标签监督"的表述, 其实涉及到理解方式的区别.**文中的公式3(CSD损失), 这里实际上省略了伪标签G.**伪标签G是激活图经过0.5阈值后得到的二值化图像.**公式3中拉大每个像素到0.5的距离, 等价于拉近每个像素与其伪标签G的距离(具体的距离计算公式可能有区别).**如果从拉大到0.5的距离来看, 我们是不依赖标签的, 或者说伪标签就是根据每个像素自身预测值得到的.**如果从拉近每个像素与伪标签的距离来看, 我们的方法是用了一种隐式的方法利用到了伪标签.**所以这里如果是传统方法生成的伪标签, 是只使用Lcsd损失的. 如果是使用本文的设定, 即激活图, 还会在Lbtm中用到动态的边缘信息.
  • Ours的before training指的是第一阶段训练之前的激活图经过crf处理后的性能么? Ours的after training指的是第一阶段训练后激活图进过crf处理后的性能么?
    • 这里Ours的"Before"和"After"实际上表示的就是第一阶段在模型通过自我约束训练前后的性能对比(当然, 这里训练后还加了CRF).

损失函数的消融

image.png

实验中使用的预训练方式

image.png

考虑到:“For the ImageNet dataset, the class labels of images usually indicate the category of the most salient object. It means that the super-vised encoder receives extra saliency knowledge from these manual labels.”. 这里对比了ImageNet和Moco-v2(本文使用的)两种预训练初始化策略下训练第一阶段后, 伪标签生成的性能. 可见其实两种初始化方式对于第一阶段而言差异不大.

或许对于第二阶段来说可能会有一些明显的差异.

  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值