做超分辨率重建的同学大概率遇到过这样的现象:网络输出的图片上覆盖着一层明暗相间的网格纹理,尤其在高倍放大时格外明显,像给图片蒙了一块棋盘布。这种伪影被称为棋盘效应(Checkerboard Artifacts),它是亚像素卷积(Sub-pixel Convolution,即PixelShuffle)和转置卷积在反卷积上采样时最常见的问题之一。要根治它,需要从卷积核权重的分布入手,初始化和平滑约束是两个最直接有效的手段。

棋盘效应到底是怎么产生的
亚像素卷积的标准做法是:先用一个普通卷积把通道数扩张到 r² 倍(r 为上采样倍率),然后通过 PixelShuffle 把通道维上的像素重新排列到空间维上。理论上这是无损的重新排布,但问题出在卷积核的学习上。当卷积核权重没有被均匀分配到各个输出通道时,重排后相邻的 r×r 个像素点就来自不同的卷积核切片,彼此的响应强度不一致,人眼就会感知到周期性的明暗网格。
更具体地说,假设上采样倍率为 2,卷积输出 4 个通道,PixelShuffle 会把这 4 个通道的对应位置依次填到 2×2 的空间块中。如果通道 0 的权重大、通道 3 的权重小,那么每个 2×2 块的左上角像素就会偏亮、右下角偏暗,整个图像呈现出周期为 2 像素的网格。倍率越高、通道间权重差异越大,棋盘伪影越明显。
除了权重不均,转置卷积还存在重叠感知野问题:当卷积核大小不能被步长整除时,部分像素位置会被多个卷积窗口覆盖,而另一些位置只被覆盖一次,覆盖次数的差异同样会形成周期性亮度波动。这也是为什么棋盘效应在转置卷积中比在亚像素卷积中更严重——后者至少没有重叠问题,只剩权重分布不均这一个病根。
ICNR初始化:从源头压制棋盘伪影
ICNR(Initialized to Convolutional NN with Checkerboard-free ReLU)初始化是目前针对亚像素卷积最流行的方案,思路非常直观:既然棋盘效应来自各子像素通道权重不一致,那就在初始化时强制让每个通道的初始权重完全相同。具体做法是先初始化一个"等效卷积核",再把它复制 r² 份填满整个卷积核,这样初始状态下 PixelShuffle 重排后的每个像素值都相等,网络从零伪影的起点开始训练。
在 PyTorch 中实现 ICNR 并不复杂,核心是对权重张量做 reshape 后广播复制:
import torch
import torch.nn as nn
import torch.nn.init as init
def icnr_init(conv, upscale_factor=2, initializer=init.kaiming_normal_):
"""对亚像素卷积层做ICNR初始化"""
out_channels, in_channels, kh, kw = conv.weight.shape
# 先初始化一个等效的小卷积核(输出通道数除以r^2)
sub_kernel = torch.empty(
out_channels // (upscale_factor ** 2),
in_channels, kh, kw
)
initializer(sub_kernel)
# 将小卷积核复制r^2份,保证各组子像素通道初始权重一致
kernel = sub_kernel.repeat_interleave(upscale_factor ** 2, dim=0)
conv.weight.data.copy_(kernel)
class UpsampleBlock(nn.Module):
def __init__(self, in_ch, out_ch, scale=2):
super().__init__()
self.conv = nn.Conv2d(in_ch, out_ch * scale * scale, 3, padding=1)
self.shuffle = nn.PixelShuffle(scale)
icnr_init(self.conv, scale)
def forward(self, x):
return self.shuffle(self.conv(x))
这段代码的关键在于 repeat_interleave:它把同一份子卷积核复制到相邻的 r² 个通道上,使得初始时 PixelShuffle 输出的每个 r×r 空间块内部像素完全一致。此时上采样实际上等效于最近邻插值加卷积,输出自然没有任何网格。训练开始后网络会逐渐分化各通道权重以学习更多细节,但由于起点无伪影,最终学到的权重分布也更均衡。
ICNR 的优点是实现简单、零额外计算开销,几乎所有超分框架(如 ESRGAN 的 RRDB 结构)都默认采用它。需要注意的是,ICNR 只改善初始条件,如果损失函数或数据分布本身诱导网络产生周期性模式,伪影仍可能回来,这时需要结合下面的平滑约束手段。
平滑约束与替代方案:多管齐下消除网格
如果初始化之后棋盘效应仍有残留,可以从网络结构和损失函数两个方向继续加约束。结构上最简单的技巧是在 PixelShuffle 之后接一个 3×3 的普通卷积或深度卷积,让网络有机会在后续层中平滑掉网格。代价是略微增加计算量,但效果通常立竿见影,许多轻量超分网络(如 ESPCN 的改进版本)都采用了这一设计。
损失函数方面,可以引入总变分损失(Total Variation Loss)惩罚图像的梯度突变。棋盘伪影本质上是高频周期噪声,TV 损失恰好能抑制这类成分:
def tv_loss(x, weight=1e-3):
"""总变分损失,抑制周期性高频伪影"""
dh = x[:, :, 1:, :] - x[:, :, :-1, :]
dw = x[:, :, :, 1:] - x[:, :, :, :-1]
return weight * (dh.abs().mean() + dw.abs().mean())
# 在训练循环中组合使用
loss = l1_loss(sr_img, hr_img) + tv_loss(sr_img)
除了上述手段,还有一个更彻底的思路:干脆避开带棋盘风险的上采样算子,改用先最近邻或双线性插值再卷积的方案。插值本身不会产生重叠覆盖问题,后续卷积核大小固定、无步长跳变,从数学上根除了棋盘效应的产生条件。SRResNet 之后的不少工作都采用了这种方式,虽然理论感受野上略有损失,但实际重建质量往往更稳定。
实际选型时可以参考这样的优先级:首先给亚像素卷积加上 ICNR 初始化(必做,成本几乎为零);其次在训练中观察输出图像,若仍有轻微网格,追加 PixelShuffle 后的平滑卷积;对画质要求极高的场景再叠加 TV 损失或改用插值加卷积的上采样。多倍放大(如4倍、8倍)时建议每个尺度因子单独一级上采样,而不是一次性用大的 r 做 PixelShuffle,逐级放大也能显著降低伪影强度。
总结一下,棋盘效应的病根是上采样过程中各像素位置接受到的权重贡献不均匀,ICNR 初始化让网络从无伪影状态出发,平滑卷积和 TV 损失则在训练过程中持续压制周期性成分。三者结合使用,基本可以让亚像素卷积输出干净的重建结果,同时保留其计算效率高的优势。