摘要
大多数现有的显著目标检测模型都通过聚合从卷积神经网络中提取的多级特征取得了长足的进步。然而,由于不同卷积层的感受野不同,这些层生成的特征之间存在很大差异。常见的特征融合策略(加法或连接)忽略了这些差异,并可能导致次优解决方案。在本文中,我们提出了F3Net来解决上述问题,它主要由交叉特征模块(CFM)和通过最小化新的像素位置感知损失(PPA)训练的级联反馈解码器(CFD)组成。具体来说,CFM旨在选择性地聚合多级特征。与加法和连接不同,CFM在融合前自适应地从输入特征中选择互补分量,可以有效避免引入过多可能破坏原始特征的冗余信息。此外,CFD采用多级反馈机制,将对监督封闭的特征引入到前一层的输出中,以补充它们并消除特征之间的差异。在生成最终的显著图之前,这些细化的特征将经过多次类似的迭代。此外,与二元交叉熵不同的是,所提出的PPA损失并不平等对待像素,它可以综合像素的局部结构信息来引导网络更多地关注局部细节。来自边界或容易出错的部分的硬像素将得到更多关注,以强调它们的重要性。F3Net能够准确地分割显著目标区域并提供清晰的局部细节。对五个基准数据集的综合实验表明,F3Net在六个评估指标上优于最先进的方法。
一、创新点
- 我们引入交叉特征模块(CFM)来融合不同层次的特征,能够提取特征之间的共享部分,抑制彼此的背景噪声,补充彼此缺失的部分。
- 我们提出了用于SOD的级联反馈解码器(CFD),它可以将高分辨率和高语义的特征反馈到之前的特征上,从而对其进行纠正和改进,以更好地生成显著图。
- 我们设计像素位置感知损失,为不同的位置分配不同的权重。它可以更好地挖掘特征中包含的结构信息,帮助网络更多地关注细节区域。
二、提出方法
1.Cross Feature Module(CFM)
CFM通过进行特征交叉来减少特征之间的差异。首先通过元素乘法提取fl(低层特征)和fh(高层特征)之间的公共部分,然后通过元素加法分别将其与原始fl和fh组合。与现有研究中采用的直接添加或串联相比,CFM避免了向fl和fh引入的冗余信息,这可能会“污染”原始特征,给显著性图的生成带来不利影响。通过多个特征交叉,fl和fh将逐渐相互吸收有用的信息来相互补充,即fl的噪声将被抑制,fh的边界将被锐化。
其中,每个Mh(·)、Ml(·)、Gh(·)、Gl(·)都是3×3卷积、批量规范和ReLu的组合。
2.Cascaded Feedback Decoder(CFD)
对于自底向上的过程,CFM将特征从高层逐渐聚合到低层。聚合的特征将被监督,并产生一个粗糙的显著图。对于自顶向下的过程,由最后一个过程聚合的特征被直接进行下采样,并添加到CFM导出的以前的多级特征中,以对其进行细化。这些改进后的特征将被发送到下一个解码器进行同样的过程。事实上,在CFD中,多个解码器的两个过程被逐个连接,形成一个网格网。多层次的特征在这个网络中迭代地流动和细化。最后,这些特征足够完整,可以生成更加精细的显著图。
对于大小为HxW的输入图像,ResNet-50将在五个级别上提取其特征,表示为{fi|i = 1,…,5},分辨率为[H/2i−1, w/2i−1]。由于低级别的特性带来太多的计算成本,但性能提高很小,我们只使用最后四级f2、f3、f3、f4、f5的特性,它们的分辨率更低,计算成本更低。CFD的整个过程可以表述为Alg。1,其中Dei(·)为第i个子解码器,Dsi(·)表示下采样操作。
3.Pixel Position Aware Loss
在SOD中,二进制交叉熵(BCE)是应用最广泛的损失函数。然而,BCE的损失有三个缺点。首先,它独立地计算每个像素的损失,而忽略了图像的全局结构。其次,在背景占主导地位的图片中,前景像素的丢失会被稀释。第三,它平等地对待所有像素。事实上,位于杂乱或拉长区域(如杆和角)的像素容易出现错误的预测,值得更多的关注,而像素位于区域,如天空和草,值得更少的关注。因此,我们提出了一个加权二进制交叉熵损失(wBCE)。
在(3)中,每个像素将被分配一个权重α。硬像素对应较大的α,简单像素将分配较小的α。α可以看作是像素重要性的指标,根据中心像素与其周围环境的差值计算.
为了进一步使网络关注全局结构,我们引入了加权IoU(wIoU)损失。
像素位置感知损失如等式6所示。综合局部结构信息,对所有像素生成不同的权值,同时引入像素约束(3)和全局约束(5),可以更好地指导网络的学习和生成。
CFD中的每个子解码器对应一个(6)。此外,还增加了多层次监督(MLS)作为辅助损失,以促进充分的训练。给定CFD和M级中的N个子解码器,整个损失在等式中定义。
第一项对应所有子解码器损失的平均值,第二项对应辅助损失的加权和,其中高层次损失由于误差较大而权重较小。
三、实验
1.评价指标
2.消融研究
γ用于PPA损失来调整硬像素的比例。当γ等于5时,这些指标达到了最高分。
N表示CFD中的子解码器数。当N=2时,模型的性能最好。
我们测试了不同的损失函数的影响,包括BCE、IoU和PPA。其中,PPA损失在三个评价指标上的表现最好。此外,我们不断添加多层次监督、跨特征模块和级联反馈解码器来评估其性能。如我们所见,所有这些模块都提高了模型的性能。当这些模块被组合起来时,我们可以得到最好的SOD结果。
四、代码
代码如下(CFM部分代码):
class CFM(nn.Layer):
def __init__(self):
super(CFM, self).__init__()
self.conv1h = nn.Conv2D(64, 64, kernel_size=3, stride=1, padding=1)
self.bn1h = nn.BatchNorm2D(64)
self.conv2h = nn.Conv2D(64, 64, kernel_size=3, stride=1, padding=1)
self.bn2h = nn.BatchNorm2D(64)
self.conv3h = nn.Conv2D(64, 64, kernel_size=3, stride=1, padding=1)
self.bn3h = nn.BatchNorm2D(64)
self.conv4h = nn.Conv2D(64, 64, kernel_size=3, stride=1, padding=1)
self.bn4h = nn.BatchNorm2D(64)
self.conv1v = nn.Conv2D(64, 64, kernel_size=3, stride=1, padding=1)
self.bn1v = nn.BatchNorm2D(64)
self.conv2v = nn.Conv2D(64, 64, kernel_size=3, stride=1, padding=1)
self.bn2v = nn.BatchNorm2D(64)
self.conv3v = nn.Conv2D(64, 64, kernel_size=3, stride=1, padding=1)
self.bn3v = nn.BatchNorm2D(64)
self.conv4v = nn.Conv2D(64, 64, kernel_size=3, stride=1, padding=1)
self.bn4v = nn.BatchNorm2D(64)
def forward(self, left, down):
if down.shape[2:] != left.shape[2:]:
down = F.interpolate(down, size=left.shape[2:], mode='bilinear')
out1h = F.relu(self.bn1h(self.conv1h(left )), )
out2h = F.relu(self.bn2h(self.conv2h(out1h)), )
out1v = F.relu(self.bn1v(self.conv1v(down )), )
out2v = F.relu(self.bn2v(self.conv2v(out1v)), )
fuse = out2h*out2v
out3h = F.relu(self.bn3h(self.conv3h(fuse )), )+out1h
out4h = F.relu(self.bn4h(self.conv4h(out3h)), )
out3v = F.relu(self.bn3v(self.conv3v(fuse )), )+out1v
out4v = F.relu(self.bn4v(self.conv4v(out3v)), )
return out4h, out4v
代码如下(CFD部分代码):
class Decoder(nn.Layer):
def __init__(self):
super(Decoder, self).__init__()
self.cfm45 = CFM()
self.cfm34 = CFM()
self.cfm23 = CFM()
def forward(self, out2h, out3h, out4h, out5v, fback=None):
if fback is not None:
refine5 = F.interpolate(fback, size=out5v.shape[2:], mode='bilinear')
refine4 = F.interpolate(fback, size=out4h.shape[2:], mode='bilinear')
refine3 = F.interpolate(fback, size=out3h.shape[2:], mode='bilinear')
refine2 = F.interpolate(fback, size=out2h.shape[2:], mode='bilinear')
out5v = out5v+refine5
out4h, out4v = self.cfm45(out4h+refine4, out5v)
out3h, out3v = self.cfm34(out3h+refine3, out4v)
out2h, pred = self.cfm23(out2h+refine2, out3v)
else:
out4h, out4v = self.cfm45(out4h, out5v)
out3h, out3v = self.cfm34(out3h, out4v)
out2h, pred = self.cfm23(out2h, out3v)
return out2h, out3h, out4h, out5v, pred
代码如下(F3Net部分代码):
class F3Net(nn.Layer):
def __init__(self):
super(F3Net, self).__init__()
self.bkbone = ResNet()
self.squeeze5 = nn.Sequential(nn.Conv2D(2048, 64, 1), nn.BatchNorm2D(64), nn.ReLU())
self.squeeze4 = nn.Sequential(nn.Conv2D(1024, 64, 1), nn.BatchNorm2D(64), nn.ReLU())
self.squeeze3 = nn.Sequential(nn.Conv2D( 512, 64, 1), nn.BatchNorm2D(64), nn.ReLU())
self.squeeze2 = nn.Sequential(nn.Conv2D( 256, 64, 1), nn.BatchNorm2D(64), nn.ReLU())
self.decoder1 = Decoder()
self.decoder2 = Decoder()
self.linearp1 = nn.Conv2D(64, 1, kernel_size=3, stride=1, padding=1)
self.linearp2 = nn.Conv2D(64, 1, kernel_size=3, stride=1, padding=1)
self.linearr2 = nn.Conv2D(64, 1, kernel_size=3, stride=1, padding=1)
self.linearr3 = nn.Conv2D(64, 1, kernel_size=3, stride=1, padding=1)
self.linearr4 = nn.Conv2D(64, 1, kernel_size=3, stride=1, padding=1)
self.linearr5 = nn.Conv2D(64, 1, kernel_size=3, stride=1, padding=1)
self.initialize()
def forward(self, x, shape=None):
out1, out2h, out3h, out4h, out5v = self.bkbone(x)
out2h, out3h, out4h, out5v = self.squeeze2(out2h), self.squeeze3(out3h), self.squeeze4(out4h), self.squeeze5(out5v)
out2h, out3h, out4h, out5v, pred1 = self.decoder1(out2h, out3h, out4h, out5v)
out2h, out3h, out4h, out5v, pred2 = self.decoder2(out2h, out3h, out4h, out5v, pred1)
shape = x.shape[2:] if shape is None else shape
pred1 = F.interpolate(self.linearp1(pred1), size=shape, mode='bilinear')
pred2 = F.interpolate(self.linearp2(pred2), size=shape, mode='bilinear')
out2h = F.interpolate(self.linearr2(out2h), size=shape, mode='bilinear')
out3h = F.interpolate(self.linearr3(out3h), size=shape, mode='bilinear')
out4h = F.interpolate(self.linearr4(out4h), size=shape, mode='bilinear')
out5h = F.interpolate(self.linearr5(out5v), size=shape, mode='bilinear')
return pred1, pred2, out2h, out3h, out4h, out5h