一边Upsample一边Convolve:Efficient Sub-pixel-convolutional-layers详解

本文介绍了子像素卷积(Sub-pixel convolution)的概念及其在图像超分辨率任务中的应用。该方法结合了升采样和卷积操作,能有效地提高图像分辨率,并在相同的计算速度下提供更好的位置记忆能力和更高的精度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

这篇文章介绍<Is the deconvolution layer the same as a convolutional layer?>论文中提出的一种结合上升采样upsample和卷积操作的的一种方法,称之为Sub-piexl convolution。这种方法作用于低分率图像得然后通过其操作得到高分辨率图像,其实说白了就是上升采样,我们之前做过的maxpooling、average-pooling和transposed-convolution都有类似的效果,也就是将一个尺寸较小的图像通过算法使之变为尺寸较大的图像,如下图。 (cs231n课程课件截图)

相比于其他的上升采样操作,本文介绍的这个算法在相同运算速度下,不仅具有比maxpooling和averagepooling有对位置的记忆功能,而且相比Transposed convolution具有精度更高的优点。

思想描述

文章将这个算法描述为:LR network,即低分辨卷积网络。文章拿来与之作对比的是HR network,高分辨卷积网络,一般HR network是现将低分辨力的图像进行二次插值变换后然后对变换后的图像再进行卷积网络。像HR network是先将图像进行upsample后才进行卷积,而文中的这个算法操作则是在upsample的过程中对图像就进行了卷积。

这个算法的实现流程如上图,举个例子,实现的功能就是将一个1×1的image通过Sub-pixel操作将其变为

的高分辨率图像,但是这个实现过程不是直接产生这个高分辨率图像而是先得到

个通道特征图然后通过周期筛选(periodic shuffing)得到这个高分辨率的图像,其中r为upscaling factor,也就是图像扩大倍率。

知识铺垫

在说明这个具体的算法流程之前,我们先对几个知识回顾一下:

Transposed convolution and sub-pixel convolution layers

上图是一个简单的1D的卷积网络,x为输入y为输出,通过滤波器f来实现这个过程,x的size为8,f为4,y为5,x中灰色的方块表示用0进行padding。在f权重中的灰色方块代表f中某些值与x中的0进行了相乘。这个图就是1D卷积的过程,从x到y。

上图左面是一个转置卷积(stride为2),右面是sub-pixel convolution(stride为1/2),作用在1D上。

通过观察可以很容易的看到左边x为5,y为8,f为4,y中灰色部分代表被截去的部分,而右边通过对x进行部分 取值达到 过滤效果。通过比较两个卷积方法可以发现:如果把右边的fliter进行reverse(倒转),会发现两边得到的y值相同(自己可以试着加一下)。也就是说在参数(filter中权重值)可以学习的情况下,右边的操作和左边的操作完全相同。

Deconvolution layer vs Convolution in LR

接下来我们展示的是:在LR space中用一个简单的卷积参数为(o* r* r, i,k,k)-(输出通道,输入通道,kernel width,kernel height)的卷积操作,和deconvolution,其中卷积参数为((o, i ,k * r,k * r),k为正数)的作用是相同的。

我们用2D来进行演示,input为(1,4,4),kernel为(1,1,4,4),假设我们upsample的比率为2,我们要得到(1,8,8)的输出。

如上图,我们首先通过fractional indices从原input中创建一个sub-pixel图像,其中白色的像素就是原input中的像素(在LR sapce中),灰色是通过zero padding而来的。

用一个(1,1,4,4)的卷积核来和刚才生成的sub-pixel图像进行卷积,首先发现卷积核和sub-pixel图像中非零的像素进行了第一次有效卷积(图中紫色像素代表被激活的权重),然后我们将sub-pixels整体向右移动一格,让卷积核在进行一次卷积,会发现卷积核中蓝色的像素权重被激活,同理绿色和红色(注意这里是中间的那个8×8的sub-pixel图像中的白色像素点进行移动,而每次卷积的方式都相同)。

最后我们输出得到HR图像,HR图像和sub-pixel图像的大小是一致的,我们将其涂上颜色,颜色代表这个卷积核中权重和sub-pixel图像中哪个像素点进行了卷积(也就是哪个权重对对应的像素进行了贡献)。

需要注意的是,这是一个(1,1,4,4)的卷积核,上面每个权重皆为独立地被激活,也就是说我们可以轻易地将其分成(4,1,2,2)的卷积核(如上图)。这个操作是可逆的因为每个卷及权重在操作过程中都是独立的。

这样,这里不适用(1,1,4,4)的卷积核,而是使用(4,1,2,2)的卷积核,,对sub-pixel图像直接在LR space中进行卷及操作(如上图)。再将得到的(4,4,4)的输出进行周期筛选(periodic shuffling)将其reshape为HR image。

这个图片展示了将左面图像上方的(9,32,3,3)卷积层转化为底部的(1,32,9,9)卷积层,转化过程正如右方所示。也就是说,上方的卷积层对一个32通道的图像进行卷积然后周期筛选后得到的图像和使用下方的卷积层进行反卷积(deconvolution)得到的结果相同。

这个卷积操作是很灵活的,看一下之前的1D的卷积操作,可以将滤波器f=(1,2,3,4)更换为f1=(2,4)以及f2=(1,3),然后产生y1 = f1 * x和y2 = f2 * 2其中代表卷积操作。通过对y1和y2进行组合得到最终的结果y。还记得上面谈说过: “在LR space中用一个简单的卷积参数为(o r* r,i,k,k)-(输出通道,输入通道,kernel width,kernel height)的卷积操作,和deconvolution,其中卷积参数为((o,i,k * r,k * r),k为正数)的作用是相同的。” 这里的f我们一般都以(k* r)来表示,但其实这个f可以为任意值,加入f=(1,2,3),我们就可以分成f=(2),f=(1,3),同样,得到结果组合起来即可。

核心思想以及结论

现在回到最开始说的那个操作,上图是这个算法的整个过程。通过上面的一系列论述,我们可以得到deconvolition layer的操作和在LR中输出

的convolution操作得到的结果是一样的(d代表维度)。也就是说,得到

个通道的这个卷积过程和在它之前得到

个特征图的操作是一样的。

在上面的Hidden convolutional layers中,看倒数第二个layer,有n个feature maps。我们现在可以知道,在LR space中,通过一个放大倍率为2的upsampling去学习表示n feature maps和在HR space中,通过一个n/4个feature maps去表示的效果是一样的。想象在相同运行速度下,一个n=32(LR space),一个n/4=8个feature maps(HR space),在LR 下的network比在HR network下的表示能力是很强大的。

举个实际的例子,LR network(32 x 32 x 3 x 3 x W/2 x H/2)的复杂度和HR中的(8 x 8 x 6 x 6 x W x H)的复杂度是一样的(W和H代表image的宽和高)。特征图中存留的信息也是一样的:LR(l x 32 x w/2 x h/2)和HR(l x 8 x w x h),其中l代表layer的数量。接受域来说,在原始input LR space中都是一样的,但是LR network的参数(l x 32 x 32 x 3 x 3)比HR network(l x 8 x 8 x 6 x 6)的多,也就是说,LR中,network的卷积比HR中先对input进行upsample的卷积有很强的表示学习能力。

所以有时候先对原始input进行upsample然后再进行卷积并不好,至少在表示学习能力并没有sub-pixel convolution效果好。

实现代码(摘自Deepfake-keras实现版本)

class PixelShuffler(Layer):
	def __init__(self, size=(2, 2), data_format=None, **kwargs):
		super(PixelShuffler, self).__init__(**kwargs)
		self.data_format = conv_utils.normalize_data_format(data_format)
		self.size = conv_utils.normalize_tuple(size, 2, 'size')  # (2,2)

	def call(self, inputs):

		input_shape = K.int_shape(inputs)
		if len(input_shape) != 4:
			raise ValueError('Inputs should have rank ' +
							 str(4) +
							 '; Received input shape:', str(input_shape))

		if self.data_format == 'channels_first':
			batch_size, c, h, w = input_shape
			if batch_size is None:
				batch_size = -1
			rh, rw = self.size
			oh, ow = h * rh, w * rw
			oc = c // (rh * rw)

			out = K.reshape(inputs, (batch_size, rh, rw, oc, h, w))
			out = K.permute_dimensions(out, (0, 3, 4, 1, 5, 2))
			out = K.reshape(out, (batch_size, oc, oh, ow))
			return out

		elif self.data_format == 'channels_last':
			batch_size, h, w, c = input_shape
			if batch_size is None:
				batch_size = -1
			rh, rw = self.size
			oh, ow = h * rh, w * rw
			oc = c // (rh * rw)

			out = K.reshape(inputs, (batch_size, h, w, rh, rw, oc))
			out = K.permute_dimensions(out, (0, 1, 3, 2, 4, 5))
			out = K.reshape(out, (batch_size, oh, ow, oc))
			return out

	def compute_output_shape(self, input_shape):

		if len(input_shape) != 4:
			raise ValueError('Inputs should have rank ' +
							 str(4) +
							 '; Received input shape:', str(input_shape))

		if self.data_format == 'channels_first':
			height = input_shape[2] * self.size[0] if input_shape[2] is not None else None
			width = input_shape[3] * self.size[1] if input_shape[3] is not None else None
			channels = input_shape[1] // self.size[0] // self.size[1]

			if channels * self.size[0] * self.size[1] != input_shape[1]:
				raise ValueError('channels of input and size are incompatible')

			return (input_shape[0],
					channels,
					height,
					width)

		elif self.data_format == 'channels_last':
			height = input_shape[1] * self.size[0] if input_shape[1] is not None else None
			width = input_shape[2] * self.size[1] if input_shape[2] is not None else None
			channels = input_shape[3] // self.size[0] // self.size[1]

			if channels * self.size[0] * self.size[1] != input_shape[3]:
				raise ValueError('channels of input and size are incompatible')

			return (input_shape[0],
					height,
					width,
					channels)

参考资料: https://arxiv.org/pdf/1603.07285v1.pdf https://arxiv.org/abs/1609.07009

<think>好的,我现在要处理用户关于NightVisionNet多阶段GAN用于夜间图像增强的查询。首先,我需要确认用户的问题核心是了解这个架构和实现细节。用户可能对GAN在夜间图像处理中的应用感兴趣,特别是多阶段的结构。 首先,我应该回忆一下现有的相关技术。比如,多阶段GAN通常用于逐步细化生成结果,像StackGAN或者Progressive GAN这样的模型可能有关联。不过NightVisionNet可能是一个特定的实现,我需要查找是否有相关的论文或资料。 用户提到的参考文献是RefineNet,虽然主题是语义分割,但可能涉及到多路径优化,这可能与多阶段处理有共通之处。不过NightVisionNet的具体架构可能结合了不同模块,比如不同阶段的生成器和判别器,或者特定的损失函数来增强夜间图像。 接下来,我需要考虑多阶段GAN的典型结构。通常,第一阶段可能处理低分辨率图像去噪,后续阶段逐步提升细节和颜色校正。每个阶段可能有不同的损失函数,比如对抗损失、感知损失、颜色一致性损失等。此外,夜间图像增强可能需要处理低光照、噪声、颜色失真等问题,因此网络设计可能包含这些模块。 实现方面,可能需要使用PyTorch或TensorFlow框架,生成器和判别器的结构可能基于U-Net或类似架构。数据预处理可能涉及配对或非配对的夜间-白天图像数据集。训练策略可能包括分阶段训练,先训练第一阶段,再逐步加入后续阶段。 但用户提到的NightVisionNet可能是一个具体的模型,如果找不到直接的信息,可能需要根据类似模型进行推测。例如,可能结合了Retinex理论,将图像分解为光照和反射成分,再通过GAN进行增强。或者使用注意力机制来聚焦关键区域。 另外,需要考虑实现中的关键技术点,比如如何处理噪声和伪影,如何保持颜色自然,以及如何评估增强效果。可能引用相关的研究,比如使用感知损失或对抗训练来提高视觉质量。 在回答时,需要结构清晰,分点说明架构和实现,可能包括生成器设计、判别器结构、损失函数、训练策略等。同时,要确保使用正确的中文术语和LaTeX格式,比如数学公式用$...$包裹,独立公式用$$...$$。 最后,生成相关问题,可能涉及与其他方法的比较、训练数据集的选择、实际应用场景等。确保引用正确,如果引用了RefineNet或其他文献,需要标注对应的引用标识。 现在需要整理这些思路,形成结构化的回答,并检查是否符合用户的要求,包括格式和内容准确性。</think>关于**NightVisionNet多阶段GAN夜间图像增强**的架构与实现,目前公开资料中尚未有直接匹配的文献,但根据多阶段GAN和夜间图像增强的通用设计思路,其核心架构可能包含以下关键模块和实现方法: --- ### 一、架构设计 1. **多阶段生成器** - **第一阶段(去噪与初步增强)**:使用U-Net结构对输入的低光照图像进行噪声抑制和全局光照恢复,输出初步增强结果。公式表示为: $$G_1(I_{\text{night}}) \rightarrow I_{\text{stage1}}$$ - **第二阶段(细节细化)**:通过残差网络(ResNet)进一步优化局部细节(如边缘、纹理),并校正颜色失真: $$G_2(I_{\text{stage1}}) \rightarrow I_{\text{stage2}}$$ - **第三阶段(感知优化)**:引入感知损失(Perceptual Loss)和对抗训练,提升视觉自然度[^1]。 2. **判别器设计** - 多尺度判别器(Multi-Scale Discriminators)用于评估不同分辨率下的图像真实性,增强高频细节的生成质量。 3. **自适应注意力模块** - 在生成器中加入注意力机制,动态分配计算资源到低光照区域(如阴影、弱纹理区域)。 --- ### 二、实现关键点 1. **损失函数组合** - **对抗损失**:标准GAN损失(如LSGAN)确保生成图像的分布接近真实白天图像: $$\mathcal{L}_{\text{adv}} = \mathbb{E}[\log D(I_{\text{real}})] + \mathbb{E}[\log (1 - D(G(I_{\text{night}})))]$$ - **感知损失**:基于预训练VGG网络的特征匹配: $$\mathcal{L}_{\text{percep}} = \sum_{i} \| \phi_i(I_{\text{gen}}) - \phi_i(I_{\text{real}}) \|_1$$ - **颜色一致性损失**:约束增强后图像与真实白天图像的色彩分布相似性。 2. **数据准备** - 使用配对(夜间-白天)或非配对数据集(如CycleGAN风格),需包含多种夜间场景(城市、自然、室内等)。 3. **训练策略** - **分阶段训练**:先独立训练第一阶段生成器,再逐步加入后续阶段。 - **渐进式学习率调整**:避免后阶段训练破坏前阶段参数。 --- ### 三、代码实现示例(基于PyTorch伪代码) ```python class GeneratorStage1(nn.Module): def __init__(self): super().__init__() self.downsample = nn.Sequential( # 下采样层 nn.Conv2d(3, 64, 4, stride=2, padding=1), nn.LeakyReLU(0.2) ) self.res_blocks = nn.Sequential( # 残差块 ResBlock(64), ResBlock(64) ) self.upsample = nn.Sequential( # 上采样层 nn.ConvTranspose2d(64, 3, 4, stride=2, padding=1), nn.Tanh() ) def forward(self, x): x = self.downsample(x) x = self.res_blocks(x) return self.upsample(x) ``` ---
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值