|
import os |
|
import torch |
|
from collections import OrderedDict |
|
from torch import nn as nn |
|
from torchvision.models import vgg as vgg |
|
|
|
from basicsr.utils.registry import ARCH_REGISTRY |
|
|
|
VGG_PRETRAIN_PATH = 'experiments/pretrained_models/vgg19-dcbb9e9d.pth' |
|
NAMES = { |
|
'vgg11': [ |
|
'conv1_1', 'relu1_1', 'pool1', 'conv2_1', 'relu2_1', 'pool2', 'conv3_1', 'relu3_1', 'conv3_2', 'relu3_2', |
|
'pool3', 'conv4_1', 'relu4_1', 'conv4_2', 'relu4_2', 'pool4', 'conv5_1', 'relu5_1', 'conv5_2', 'relu5_2', |
|
'pool5' |
|
], |
|
'vgg13': [ |
|
'conv1_1', 'relu1_1', 'conv1_2', 'relu1_2', 'pool1', 'conv2_1', 'relu2_1', 'conv2_2', 'relu2_2', 'pool2', |
|
'conv3_1', 'relu3_1', 'conv3_2', 'relu3_2', 'pool3', 'conv4_1', 'relu4_1', 'conv4_2', 'relu4_2', 'pool4', |
|
'conv5_1', 'relu5_1', 'conv5_2', 'relu5_2', 'pool5' |
|
], |
|
'vgg16': [ |
|
'conv1_1', 'relu1_1', 'conv1_2', 'relu1_2', 'pool1', 'conv2_1', 'relu2_1', 'conv2_2', 'relu2_2', 'pool2', |
|
'conv3_1', 'relu3_1', 'conv3_2', 'relu3_2', 'conv3_3', 'relu3_3', 'pool3', 'conv4_1', 'relu4_1', 'conv4_2', |
|
'relu4_2', 'conv4_3', 'relu4_3', 'pool4', 'conv5_1', 'relu5_1', 'conv5_2', 'relu5_2', 'conv5_3', 'relu5_3', |
|
'pool5' |
|
], |
|
'vgg19': [ |
|
'conv1_1', 'relu1_1', 'conv1_2', 'relu1_2', 'pool1', 'conv2_1', 'relu2_1', 'conv2_2', 'relu2_2', 'pool2', |
|
'conv3_1', 'relu3_1', 'conv3_2', 'relu3_2', 'conv3_3', 'relu3_3', 'conv3_4', 'relu3_4', 'pool3', 'conv4_1', |
|
'relu4_1', 'conv4_2', 'relu4_2', 'conv4_3', 'relu4_3', 'conv4_4', 'relu4_4', 'pool4', 'conv5_1', 'relu5_1', |
|
'conv5_2', 'relu5_2', 'conv5_3', 'relu5_3', 'conv5_4', 'relu5_4', 'pool5' |
|
] |
|
} |
|
|
|
|
|
def insert_bn(names): |
|
"""Insert bn layer after each conv. |
|
|
|
Args: |
|
names (list): The list of layer names. |
|
|
|
Returns: |
|
list: The list of layer names with bn layers. |
|
""" |
|
names_bn = [] |
|
for name in names: |
|
names_bn.append(name) |
|
if 'conv' in name: |
|
position = name.replace('conv', '') |
|
names_bn.append('bn' + position) |
|
return names_bn |
|
|
|
|
|
@ARCH_REGISTRY.register() |
|
class VGGFeatureExtractor(nn.Module): |
|
"""VGG network for feature extraction. |
|
|
|
In this implementation, we allow users to choose whether use normalization |
|
in the input feature and the type of vgg network. Note that the pretrained |
|
path must fit the vgg type. |
|
|
|
Args: |
|
layer_name_list (list[str]): Forward function returns the corresponding |
|
features according to the layer_name_list. |
|
Example: {'relu1_1', 'relu2_1', 'relu3_1'}. |
|
vgg_type (str): Set the type of vgg network. Default: 'vgg19'. |
|
use_input_norm (bool): If True, normalize the input image. Importantly, |
|
the input feature must in the range [0, 1]. Default: True. |
|
range_norm (bool): If True, norm images with range [-1, 1] to [0, 1]. |
|
Default: False. |
|
requires_grad (bool): If true, the parameters of VGG network will be |
|
optimized. Default: False. |
|
remove_pooling (bool): If true, the max pooling operations in VGG net |
|
will be removed. Default: False. |
|
pooling_stride (int): The stride of max pooling operation. Default: 2. |
|
""" |
|
|
|
def __init__(self, |
|
layer_name_list, |
|
vgg_type='vgg19', |
|
use_input_norm=True, |
|
range_norm=False, |
|
requires_grad=False, |
|
remove_pooling=False, |
|
pooling_stride=2): |
|
super(VGGFeatureExtractor, self).__init__() |
|
|
|
self.layer_name_list = layer_name_list |
|
self.use_input_norm = use_input_norm |
|
self.range_norm = range_norm |
|
|
|
self.names = NAMES[vgg_type.replace('_bn', '')] |
|
if 'bn' in vgg_type: |
|
self.names = insert_bn(self.names) |
|
|
|
|
|
max_idx = 0 |
|
for v in layer_name_list: |
|
idx = self.names.index(v) |
|
if idx > max_idx: |
|
max_idx = idx |
|
|
|
if os.path.exists(VGG_PRETRAIN_PATH): |
|
vgg_net = getattr(vgg, vgg_type)(pretrained=False) |
|
state_dict = torch.load(VGG_PRETRAIN_PATH, map_location=lambda storage, loc: storage) |
|
vgg_net.load_state_dict(state_dict) |
|
else: |
|
vgg_net = getattr(vgg, vgg_type)(pretrained=True) |
|
|
|
features = vgg_net.features[:max_idx + 1] |
|
|
|
modified_net = OrderedDict() |
|
for k, v in zip(self.names, features): |
|
if 'pool' in k: |
|
|
|
if remove_pooling: |
|
continue |
|
else: |
|
|
|
modified_net[k] = nn.MaxPool2d(kernel_size=2, stride=pooling_stride) |
|
else: |
|
modified_net[k] = v |
|
|
|
self.vgg_net = nn.Sequential(modified_net) |
|
|
|
if not requires_grad: |
|
self.vgg_net.eval() |
|
for param in self.parameters(): |
|
param.requires_grad = False |
|
else: |
|
self.vgg_net.train() |
|
for param in self.parameters(): |
|
param.requires_grad = True |
|
|
|
if self.use_input_norm: |
|
|
|
self.register_buffer('mean', torch.Tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1)) |
|
|
|
self.register_buffer('std', torch.Tensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1)) |
|
|
|
def forward(self, x): |
|
"""Forward function. |
|
|
|
Args: |
|
x (Tensor): Input tensor with shape (n, c, h, w). |
|
|
|
Returns: |
|
Tensor: Forward results. |
|
""" |
|
if self.range_norm: |
|
x = (x + 1) / 2 |
|
if self.use_input_norm: |
|
x = (x - self.mean) / self.std |
|
|
|
output = {} |
|
for key, layer in self.vgg_net._modules.items(): |
|
x = layer(x) |
|
if key in self.layer_name_list: |
|
output[key] = x.clone() |
|
|
|
return output |
|
|