赞
踩
PS:本来打算一篇文章写完的,发现自己做事太罗嗦了,就分成了两篇
最近学习到了看到一篇face restoration的论文,基于styleGAN来做的,由于之前没做过low-level的视觉知识,由于第一次做SR的任务,看到里面的代码觉得很头大,就写篇文章记录一下,顺便帮助同样烦恼的小伙伴们少走弯路,话不多说,先看效果
复原效果还是挺不错的,下面简要介绍一下论文的原理和代码,后面会将框架和原理拆解开来看
论文地址:Towards Real-World Blind Face Restoration with Generative Facial Prior
代码地址:github
按照图中序号的顺序分别介绍框架和代码:
1.Input处理,这块介绍一下原文对输入图像的处理
2.Degradation Removal,生成latent code作为GAN的输入
3.GAN,这里作者对styleGAN的输出又拼接了一点东西
4.Loss,网络的loss设计部分
首先看下Dataset怎么写的
def __getitem__(self, index):
......
#直接看return内容
if self.crop_components:
return_dict = {
'lq': img_lq,
'gt': img_gt,
'gt_path': gt_path,
'loc_left_eye': loc_left_eye,
'loc_right_eye': loc_right_eye,
'loc_mouth': loc_mouth
}
return return_dict
else:
return {'lq': img_lq, 'gt': img_gt, 'gt_path': gt_path}
这里返回的主要有三项:img_lq
,img_gt
, gt_path
,分别是LR输入、真值,和gt的路径。loc_left_eye
、loc_right_eye
、loc_mouth
是使用眼睛和嘴巴位置信息时,作为可选输出。
其中img_lq
是由img_gt
变换得到的LR图像,img_gt
是高分辨图像,再来看一下img_lq
的生成过程
def __getitem__(self, index): ...#数据读入部分 # ------------------------ generate lq image ------------------------ # # blur kernel = degradations.random_mixed_kernels(...) img_lq = cv2.filter2D(img_gt, -1, kernel) # downsample scale = np.random.uniform(self.downsample_range[0], self.downsample_range[1]) img_lq = cv2.resize(img_lq, (int(w // scale), int(h // scale)), interpolation=cv2.INTER_LINEAR) # noise if self.noise_range is not None: img_lq = degradations.random_add_gaussian_noise(img_lq, self.noise_range) # jpeg compression if self.jpeg_range is not None: img_lq = degradations.random_add_jpg_compression(img_lq, self.jpeg_range) # resize to original size img_lq = cv2.resize(img_lq, (w, h), interpolation=cv2.INTER_LINEAR) # random color jitter (only for lq) if self.color_jitter_prob is not None and (np.random.uniform() < self.color_jitter_prob): img_lq = self.color_jitter(img_lq, self.color_jitter_shift) # random to gray (only for lq) if self.gray_prob and np.random.uniform() < self.gray_prob: img_lq = cv2.cvtColor(img_lq, cv2.COLOR_BGR2GRAY) img_lq = np.tile(img_lq[:, :, None], [1, 1, 3]) if self.opt.get('gt_gray'): # whether convert GT to gray images img_gt = cv2.cvtColor(img_gt, cv2.COLOR_BGR2GRAY) img_gt = np.tile(img_gt[:, :, None], [1, 1, 3]) # repeat the color channels # BGR to RGB, HWC to CHW, numpy to tensor img_gt, img_lq = img2tensor([img_gt, img_lq], bgr2rgb=True, float32=True) # random color jitter (pytorch version) (only for lq) if self.color_jitter_pt_prob is not None and (np.random.uniform() < self.color_jitter_pt_prob): brightness = self.opt.get('brightness', (0.5, 1.5)) contrast = self.opt.get('contrast', (0.5, 1.5)) saturation = self.opt.get('saturation', (0, 1.5)) hue = self.opt.get('hue', (-0.1, 0.1)) img_lq = self.color_jitter_pt(img_lq, brightness, contrast, saturation, hue) ...#像素值范围映射unit8,正则化 ...#return
主要就是模糊、加噪、随机像素值抖动、随机灰度化、随机色彩变化等过程生成LR图像
网络结构这部分还是结合代码来看吧,其他栏主已经介绍了很多关于结构理论的内容,首先我们找到.yml
文件看到里面的配置,如下代码,总共定义了6个部件名称:network_d_left_eye
,network_d_right_eye
,network_d_mouth
是同一个结构,其实看四个结构就行。
# network structures
network_g:
type: GFPGANv1
network_d:
type: StyleGAN2Discriminator
network_d_left_eye:
type: FacialComponentDiscriminator
network_d_right_eye:
type: FacialComponentDiscriminator
network_d_mouth:
type: FacialComponentDiscriminator
network_identity:
type: ResNetArcFace
这块算是整个结构中内容最多的一部分了,首先是框图中的②和③,该部分是对输入图像的通道进行降维,产生latent code,通过MLP送到生成器中,
首先经过一个3通道到512通道的变换,然后再做通道和空间都进行下采样的6个相同操作,这部分是BasicSR里面的ResBlock函数完成的,可以自行查一下相关实现,最终输出latent code和需要进行空间特征约束的condition。
其中一部分作用是将img_lr 变成 latent code
def __init__(self, ...) first_out_size = 2**(int(math.log(out_size, 2))) #ConvLayer self.conv_body_first = ConvLayer(3, channels[f'{first_out_size}'], 1, bias=True, activate=True) # downsample in_channels = channels[f'{first_out_size}'] self.conv_body_down = nn.ModuleList() for i in range(self.log_size, 2, -1): out_channels = channels[f'{2**(i - 1)}'] self.conv_body_down.append(ResBlock(in_channels, out_channels, resample_kernel)) in_channels = out_channels self.final_linear = EqualLinear(channels['4'] * 4 * 4, linear_out_channel, bias=True, bias_init_val=0, lr_mul=1, activation=None) ... def forward(self, ...) # encoder feat = self.conv_body_first(x) for i in range(self.log_size - 2): feat = self.conv_body_down[i](feat) unet_skips.insert(0, feat) feat = self.final_conv(feat) style_code = self.final_linear(feat.view(feat.size(0), -1)) ...
另外一部分产生Fspatial再通过卷积操作得到两个系数作为condition约束,送到后面的
def __init__(): # for SFT modulations (scale and shift) self.condition_scale = nn.ModuleList() self.condition_shift = nn.ModuleList() for i in range(3, self.log_size + 1): out_channels = channels[f'{2**i}'] if sft_half: sft_out_channels = out_channels else: sft_out_channels = out_channels * 2 self.condition_scale.append( nn.Sequential( EqualConv2d(out_channels, out_channels, 3, stride=1, padding=1, bias=True, bias_init_val=0), ScaledLeakyReLU(0.2), EqualConv2d(out_channels, sft_out_channels, 3, stride=1, padding=1, bias=True, bias_init_val=1))) self.condition_shift.append( nn.Sequential( EqualConv2d(out_channels, out_channels, 3, stride=1, padding=1, bias=True, bias_init_val=0), ScaledLeakyReLU(0.2), EqualConv2d(out_channels, sft_out_channels, 3, stride=1, padding=1, bias=True, bias_init_val=0)))
然后将上面的两部分送到StyleGAN2Generator中处理,基本和沿用了StyleGAN2的代码,所不同的是,在逐层进行卷积运算时,将前面产生的conditions约束也加入运算,下面贴一张styleGAN2的结构,可以对照着结构看代码。
class StyleGAN2GeneratorSFT(StyleGAN2Generator): """StyleGAN2 Generator with SFT modulation (Spatial Feature Transform). Args: out_size (int): The spatial size of outputs. num_style_feat (int): Channel number of style features. Default: 512. num_mlp (int): Layer number of MLP style layers. Default: 8. channel_multiplier (int): Channel multiplier for large networks of StyleGAN2. Default: 2. resample_kernel (list[int]): A list indicating the 1D resample kernel magnitude. A cross production will be applied to extent 1D resample kernel to 2D resample kernel. Default: (1, 3, 3, 1). lr_mlp (float): Learning rate multiplier for mlp layers. Default: 0.01. narrow (float): The narrow ratio for channels. Default: 1. sft_half (bool): Whether to apply SFT on half of the input channels. Default: False. """ def __init__(self, out_size, num_style_feat=512, num_mlp=8, channel_multiplier=2, resample_kernel=(1, 3, 3, 1), lr_mlp=0.01, narrow=1, sft_half=False): super(StyleGAN2GeneratorSFT, self).__init__( out_size, num_style_feat=num_style_feat, num_mlp=num_mlp, channel_multiplier=channel_multiplier, resample_kernel=resample_kernel, lr_mlp=lr_mlp, narrow=narrow) self.sft_half = sft_half def forward(self, styles, conditions, input_is_latent=False, noise=None, randomize_noise=True, truncation=1, truncation_latent=None, inject_index=None, return_latents=False): """Forward function for StyleGAN2GeneratorSFT. Args: styles (list[Tensor]): Sample codes of styles. conditions (list[Tensor]): SFT conditions to generators. input_is_latent (bool): Whether input is latent style. Default: False. noise (Tensor | None): Input noise or None. Default: None. randomize_noise (bool): Randomize noise, used when 'noise' is False. Default: True. truncation (float): The truncation ratio. Default: 1. truncation_latent (Tensor | None): The truncation latent tensor. Default: None. inject_index (int | None): The injection index for mixing noise. Default: None. return_latents (bool): Whether to return style latents. Default: False. """ # style codes -> latents with Style MLP layer if not input_is_latent: styles = [self.style_mlp(s) for s in styles] # noises if noise is None: if randomize_noise: noise = [None] * self.num_layers # for each style conv layer else: # use the stored noise noise = [getattr(self.noises, f'noise{i}') for i in range(self.num_layers)] # style truncation if truncation < 1: style_truncation = [] for style in styles: style_truncation.append(truncation_latent + truncation * (style - truncation_latent)) styles = style_truncation # get style latents with injection if len(styles) == 1: inject_index = self.num_latent if styles[0].ndim < 3: # repeat latent code for all the layers latent = styles[0].unsqueeze(1).repeat(1, inject_index, 1) else: # used for encoder with different latent code for each layer latent = styles[0] elif len(styles) == 2: # mixing noises if inject_index is None: inject_index = random.randint(1, self.num_latent - 1) latent1 = styles[0].unsqueeze(1).repeat(1, inject_index, 1) latent2 = styles[1].unsqueeze(1).repeat(1, self.num_latent - inject_index, 1) latent = torch.cat([latent1, latent2], 1) # main generation out = self.constant_input(latent.shape[0]) out = self.style_conv1(out, latent[:, 0], noise=noise[0]) skip = self.to_rgb1(out, latent[:, 1]) i = 1 for conv1, conv2, noise1, noise2, to_rgb in zip(self.style_convs[::2], self.style_convs[1::2], noise[1::2], noise[2::2], self.to_rgbs): out = conv1(out, latent[:, i], noise=noise1) # the conditions may have fewer levels if i < len(conditions): # SFT part to combine the conditions #只有这里是跟styleGan有所区别的地方 if self.sft_half: # only apply SFT to half of the channels out_same, out_sft = torch.split(out, int(out.size(1) // 2), dim=1) out_sft = out_sft * conditions[i - 1] + conditions[i] out = torch.cat([out_same, out_sft], dim=1) else: # apply SFT to all the channels out = out * conditions[i - 1] + conditions[i] out = conv2(out, latent[:, i + 1], noise=noise2) skip = to_rgb(out, latent[:, i + 2], skip) # feature back to the rgb space i += 2 image = skip if return_latents: return image, latent else: return image, None
网络的Discriminator部分沿用的StyleGAN的部分,完全没做改动,可以看到这里就是一些卷积下采样和线性层的叠加,最终输出一个类似二分类的判定结果。
class StyleGAN2Discriminator(nn.Module): """StyleGAN2 Discriminator. Args: out_size (int): The spatial size of outputs. channel_multiplier (int): Channel multiplier for large networks of StyleGAN2. Default: 2. resample_kernel (list[int]): A list indicating the 1D resample kernel magnitude. A cross production will be applied to extent 1D resample kernel to 2D resample kernel. Default: (1, 3, 3, 1). stddev_group (int): For group stddev statistics. Default: 4. narrow (float): Narrow ratio for channels. Default: 1.0. """ def __init__(self, out_size, channel_multiplier=2, resample_kernel=(1, 3, 3, 1), stddev_group=4, narrow=1): super(StyleGAN2Discriminator, self).__init__() channels = { '4': int(512 * narrow), '8': int(512 * narrow), '16': int(512 * narrow), '32': int(512 * narrow), '64': int(256 * channel_multiplier * narrow), '128': int(128 * channel_multiplier * narrow), '256': int(64 * channel_multiplier * narrow), '512': int(32 * channel_multiplier * narrow), '1024': int(16 * channel_multiplier * narrow) } log_size = int(math.log(out_size, 2)) conv_body = [ConvLayer(3, channels[f'{out_size}'], 1, bias=True, activate=True)] in_channels = channels[f'{out_size}'] for i in range(log_size, 2, -1): out_channels = channels[f'{2**(i - 1)}'] conv_body.append(ResBlock(in_channels, out_channels, resample_kernel)) in_channels = out_channels self.conv_body = nn.Sequential(*conv_body) self.final_conv = ConvLayer(in_channels + 1, channels['4'], 3, bias=True, activate=True) self.final_linear = nn.Sequential( EqualLinear( channels['4'] * 4 * 4, channels['4'], bias=True, bias_init_val=0, lr_mul=1, activation='fused_lrelu'), EqualLinear(channels['4'], 1, bias=True, bias_init_val=0, lr_mul=1, activation=None), ) self.stddev_group = stddev_group # default=4 self.stddev_feat = 1 def forward(self, x): out = self.conv_body(x) b, c, h, w = out.shape # concatenate a group stddev statistics to out group = min(b, self.stddev_group) # Minibatch must be divisible by (or smaller than) group_size stddev = out.view(group, -1, self.stddev_feat, c // self.stddev_feat, h, w) stddev = torch.sqrt(stddev.var(0, unbiased=False) + 1e-8) stddev = stddev.mean([2, 3, 4], keepdims=True).squeeze(2) stddev = stddev.repeat(group, 1, h, w) out = torch.cat([out, stddev], 1) out = self.final_conv(out) out = out.view(b, -1) out = self.final_linear(out) return out
由于篇幅限制,后面的内容放到下一次吧
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。