如何消除棋盘格伪影

6
我正在使用全卷积自编码器将黑白图像着色,但输出结果存在棋盘格状图案,我想要去除它。到目前为止,我看到的棋盘格伪影比我的大得多,通常消除伪影的方法是用双线性上采样替换所有的反池化操作(有人告诉过我这个方法)。
但我不能简单地替换反池化操作,因为我使用不同大小的图像,因此需要反池化操作,否则输出张量可能与原始张量的大小不同。
TLDR: 如何在不更换反池化操作的情况下消除这些棋盘格伪影?
class AE(nn.Module):
    def __init__(self):
        super(AE, self).__init__()
        self.leaky_reLU = nn.LeakyReLU(0.2)
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2, padding=1, return_indices=True)
        self.unpool = nn.MaxUnpool2d(kernel_size=2, stride=2, padding=1)
        self.softmax = nn.Softmax2d()

        self.conv1 = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1, padding=1)
        self.conv2 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1)
        self.conv3 = nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=1)
        self.conv4 = nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, stride=1, padding=1)
        self.conv5 = nn.Conv2d(in_channels=512, out_channels=1024, kernel_size=3, stride=1, padding=1)
        self.conv6 = nn.ConvTranspose2d(in_channels=1024, out_channels=512, kernel_size=3, stride=1, padding=1)
        self.conv7 = nn.ConvTranspose2d(in_channels=512, out_channels=256, kernel_size=3, stride=1, padding=1)
        self.conv8 = nn.ConvTranspose2d(in_channels=256, out_channels=128, kernel_size=3, stride=1, padding=1)
        self.conv9 = nn.ConvTranspose2d(in_channels=128, out_channels=64, kernel_size=3, stride=1, padding=1)
        self.conv10 = nn.ConvTranspose2d(in_channels=64, out_channels=2, kernel_size=3, stride=1, padding=1)

    def forward(self, x):

        # encoder
        x = self.conv1(x)
        x = self.leaky_reLU(x)
        size1 = x.size()
        x, indices1 = self.pool(x)

        x = self.conv2(x)
        x = self.leaky_reLU(x)
        size2 = x.size()
        x, indices2 = self.pool(x)

        x = self.conv3(x)
        x = self.leaky_reLU(x)
        size3 = x.size()
        x, indices3 = self.pool(x)

        x = self.conv4(x)
        x = self.leaky_reLU(x)
        size4 = x.size()
        x, indices4 = self.pool(x)

        ######################
        x = self.conv5(x)
        x = self.leaky_reLU(x)

        x = self.conv6(x)
        x = self.leaky_reLU(x)
        ######################

        # decoder
        x = self.unpool(x, indices4, output_size=size4)
        x = self.conv7(x)
        x = self.leaky_reLU(x)

        x = self.unpool(x, indices3, output_size=size3)
        x = self.conv8(x)
        x = self.leaky_reLU(x)

        x = self.unpool(x, indices2, output_size=size2)
        x = self.conv9(x)
        x = self.leaky_reLU(x)

        x = self.unpool(x, indices1, output_size=size1)
        x = self.conv10(x)
        x = self.softmax(x)

        return x

NNs output

4个回答

3

在解码器部分,可以使用插值技术将输出数据恢复到初始格式,而不是使用upconv层(如nn.ConvTranspose2d),例如torch.nn.functional.interpolate。使用该方法可避免出现棋盘伪影。

如果您想在解码器中使用可学习的权重,则应在每个插值操作之后使用conv层,例如nn.Conv2d


3
跳跃连接通常用于编码器-解码器架构,它通过将外观信息从编码器(鉴别器)的浅层传递到相应的解码器(生成器)的更深层来帮助产生精确结果。Unet是广泛使用的编码器-解码器类型架构。Linknet也非常流行,它与Unet的区别在于融合编码器层的外观信息与解码器层的方式。对于Unet,传入特征(来自编码器)在相应的解码器层中进行连接。另一方面,Linknet执行加法,因此Linknet在单个前向传递中需要较少的操作次数,并且比Unet快得多。
您的每个解码器中的卷积块可能如下所示: enter image description here 此外,我附上了下图,显示Unet和LinkNet的结构。希望使用跳跃连接会有所帮助。 enter image description here

谢谢!这听起来很有前途,但我可以使用常规的反池化操作来替代上采样吗? - Stefan
我不是pytorch的专家。然而,在forward函数中,您可以为每个编码器层分配相同的名称,以便您可以在相应的解码器层中访问编码器层的输出特征,并将该特征与解码器层中的上采样特征连接/相加。我不确定,但应该使用ConvTranspose2d进行上采样,同时使用stride=2,这比MaxUnpool2d更好,因为在ConvTranspose2d的情况下,会学习到上采样的特征。正如我已经提到的,先使用上采样,然后添加/连接,最后进行卷积。 - Kaushik Roy
很高兴知道它有所帮助。 - Kaushik Roy

0

正如Kaushik Roy所指出的, Skip-Connections 是正确的选择!

class AE(nn.Module):
    def __init__(self):
        super(AE, self).__init__()
        self.leaky_reLU = nn.LeakyReLU(0.2)
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2, padding=1, return_indices=True)
        self.unpool = nn.MaxUnpool2d(kernel_size=2, stride=2, padding=1)
        self.softmax = nn.Softmax2d()

        self.conv1 = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1, padding=1)
        self.conv2 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1)
        self.conv3 = nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=1)
        self.conv4 = nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, stride=1, padding=1)
        self.conv5 = nn.Conv2d(in_channels=512, out_channels=1024, kernel_size=3, stride=1, padding=1)
        self.conv6 = nn.Conv2d(in_channels=1024, out_channels=512, kernel_size=3, stride=1, padding=1)
        self.conv7 = nn.Conv2d(in_channels=1024, out_channels=256, kernel_size=3, stride=1, padding=1)
        self.conv8 = nn.Conv2d(in_channels=512, out_channels=128, kernel_size=3, stride=1, padding=1)
        self.conv9 = nn.Conv2d(in_channels=256, out_channels=64, kernel_size=3, stride=1, padding=1)
        self.conv10 = nn.Conv2d(in_channels=128, out_channels=2, kernel_size=3, stride=1, padding=1)

    def forward(self, x):

        # encoder
        x = self.conv1(x)
        out1 = self.leaky_reLU(x)
        x = out1
        size1 = x.size()
        x, indices1 = self.pool(x)

        x = self.conv2(x)
        out2 = self.leaky_reLU(x)
        x = out2
        size2 = x.size()
        x, indices2 = self.pool(x)

        x = self.conv3(x)
        out3 = self.leaky_reLU(x)
        x = out3
        size3 = x.size()
        x, indices3 = self.pool(x)

        x = self.conv4(x)
        out4 = self.leaky_reLU(x)
        x = out4
        size4 = x.size()
        x, indices4 = self.pool(x)

        ######################
        x = self.conv5(x)
        x = self.leaky_reLU(x)

        x = self.conv6(x)
        x = self.leaky_reLU(x)
        ######################

        # decoder
        x = self.unpool(x, indices4, output_size=size4)
        x = self.conv7(torch.cat((x, out4), 1))
        x = self.leaky_reLU(x)

        x = self.unpool(x, indices3, output_size=size3)
        x = self.conv8(torch.cat((x, out3), 1))
        x = self.leaky_reLU(x)

        x = self.unpool(x, indices2, output_size=size2)
        x = self.conv9(torch.cat((x, out2), 1))
        x = self.leaky_reLU(x)

        x = self.unpool(x, indices1, output_size=size1)
        x = self.conv10(torch.cat((x, out1), 1))
        x = self.softmax(x)

        return x

这个答案是由OP Stefan 在CC BY-SA 4.0下发布的,作为编辑问题如何摆脱棋盘状伪影的回答。


0

你之所以有这种模式,是因为反卷积(nn.ConvTranspose2d)。文章详细解释了这一点。

你可以尝试上采样作为替代方案。这不会产生棋盘格模式。

工作原理如下:

import torch 
input = torch.arange(1, 5, dtype=torch.float32).view(1, 1, 2, 2)
m = torch.nn.Upsample(scale_factor=2, mode='nearest')
m(input)

然而,使用 Upsample 并不能使您学到任何东西。它只是一种变换。

所以这是一种权衡。有许多在线论文介绍如何处理不同问题的棋盘格模式。

思路是训练您的网络,使棋盘格模式消失。


就像我之前所说的,我无法替换unpooling操作。 - Stefan
1
那么很明显,你需要学习你的网络来摆脱它。 - prosti

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接