first
This commit is contained in:
1 parent
54ccfb11fe
commit
a0818377bb
194 files changed
+41681
-262
No files matched your search
Binary file not shown.
+140
@@ -0,0 +1,140 @@
|
||||
import os
|
||||
import pandas as pd
|
||||
import torch
|
||||
|
||||
from transforms import transforms
|
||||
from util.autoaugment import ImageNetPolicy
|
||||
|
||||
# pretrained model checkpoints
|
||||
pretrained_model = {'resnet50' : '/data1/Hatcher/model/data_MPKFNet/weigth/FinedGrainedRecognition/pretrained/vgg16-397923af.pth',}
|
||||
|
||||
# transforms dict
|
||||
def load_data_transformers(resize_reso=512, crop_reso=448, swap_num=[7, 7]):
|
||||
center_resize = 600
|
||||
Normalize = transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
|
||||
data_transforms = {
|
||||
'swap': transforms.Compose([
|
||||
transforms.Randomswap((swap_num[0], swap_num[1])),
|
||||
]),
|
||||
'common_aug': transforms.Compose([
|
||||
transforms.Resize((resize_reso, resize_reso)),
|
||||
transforms.RandomRotation(degrees=15), # 将图片进行随机旋转
|
||||
transforms.RandomCrop((crop_reso,crop_reso)), # 图片进行随机裁剪
|
||||
transforms.RandomHorizontalFlip(), # 随机水平翻转
|
||||
]),
|
||||
'train_totensor': transforms.Compose([
|
||||
transforms.Resize((crop_reso, crop_reso)),
|
||||
# ImageNetPolicy(),
|
||||
transforms.ToTensor(),
|
||||
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
|
||||
]),
|
||||
'val_totensor': transforms.Compose([
|
||||
transforms.Resize((crop_reso, crop_reso)),
|
||||
transforms.ToTensor(),
|
||||
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
|
||||
]),
|
||||
'test_totensor': transforms.Compose([
|
||||
transforms.Resize((resize_reso, resize_reso)),
|
||||
transforms.CenterCrop((crop_reso, crop_reso)),
|
||||
transforms.ToTensor(),
|
||||
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
|
||||
]),
|
||||
'None': None,
|
||||
}
|
||||
return data_transforms
|
||||
|
||||
''' change three paths dataset save '''
|
||||
|
||||
class LoadConfig(object):
|
||||
def __init__(self, args, version):
|
||||
if version == 'train':
|
||||
get_list = ['train', 'val']
|
||||
elif version == 'val':
|
||||
get_list = ['val']
|
||||
elif version == 'test':
|
||||
get_list = ['test']
|
||||
else:
|
||||
raise Exception("train/val/test ???\n")
|
||||
|
||||
###############################
|
||||
#### add dataset info here ####
|
||||
###############################
|
||||
|
||||
# put image data in $PATH/data
|
||||
# put annotation txt file in $PATH/anno
|
||||
|
||||
if args.dataset == 'product':
|
||||
self.dataset = args.dataset
|
||||
self.rawdata_root = './../FGVC_product/data'
|
||||
self.anno_root = './../FGVC_product/anno'
|
||||
self.numcls = 2019
|
||||
elif args.dataset == 'CUB':
|
||||
self.dataset = args.dataset
|
||||
self.rawdata_root = './datasets/CUB_200_2011/data'
|
||||
self.anno_root = './datasets/CUB_200_2011/anno'
|
||||
self.numcls = 200
|
||||
elif args.dataset == 'STCAR':
|
||||
self.dataset = args.dataset
|
||||
self.rawdata_root = './dataset/st_car/data'
|
||||
self.anno_root = './dataset/st_car/anno'
|
||||
self.numcls = 196
|
||||
elif args.dataset == 'AIR':
|
||||
self.dataset = args.dataset
|
||||
self.rawdata_root = './dataset/aircraft/data'
|
||||
self.anno_root = './dataset/aircraft/anno'
|
||||
self.numcls = 200
|
||||
|
||||
elif args.dataset == 'HCData':
|
||||
self.dataset = args.dataset
|
||||
self.rawdata_root = '../../data_MPKFNet/data/DCL/datasets/HCData/data'
|
||||
self.anno_root = '../../data_MPKFNet/data/DCL/datasets/HCData/anno'
|
||||
self.numcls = 50
|
||||
else:
|
||||
raise Exception('dataset not defined ???')
|
||||
|
||||
# annotation file organized as :
|
||||
# path/image_name cls_num\n
|
||||
|
||||
if 'train' in get_list:
|
||||
self.train_anno = pd.read_csv(os.path.join(self.anno_root, 'train.txt'),
|
||||
sep=" ",
|
||||
header=None,
|
||||
names=['ImageName', 'label'])
|
||||
|
||||
if 'val' in get_list:
|
||||
self.val_anno = pd.read_csv(os.path.join(self.anno_root, 'test.txt'),
|
||||
sep=" ",
|
||||
header=None,
|
||||
names=['ImageName', 'label'])
|
||||
|
||||
if 'test' in get_list:
|
||||
self.test_anno = pd.read_csv(os.path.join(self.anno_root, 'test.txt'),
|
||||
sep=" ",
|
||||
header=None,
|
||||
names=['ImageName', 'label'])
|
||||
|
||||
self.swap_num = args.swap_num
|
||||
|
||||
self.save_dir = '../../data_MPKFNet/weigth/FinedGrainedRecognition/Train'
|
||||
if not os.path.exists(self.save_dir):
|
||||
os.mkdir(self.save_dir)
|
||||
self.backbone = args.backbone
|
||||
|
||||
self.use_dcl = True
|
||||
self.use_backbone = False if self.use_dcl else True
|
||||
self.use_Asoftmax = False
|
||||
self.use_focal_loss = False
|
||||
self.use_fpn = False
|
||||
self.use_hier = False
|
||||
|
||||
self.weighted_sample = False
|
||||
self.cls_2 = True
|
||||
self.cls_2xmul = False
|
||||
|
||||
self.log_folder = './logs'
|
||||
if not os.path.exists(self.log_folder):
|
||||
os.mkdir(self.log_folder)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import os
|
||||
import pandas as pd
|
||||
import torch
|
||||
|
||||
from fine.transforms import transforms
|
||||
|
||||
|
||||
|
||||
# pretrained model checkpoints
|
||||
pretrained_model = {'resnet50' : '/data1/Hatcher/model/data_MPKFNet/weigth/FinedGrainedRecognition/pretrained/vgg16-397923af.pth',}
|
||||
|
||||
# transforms dict
|
||||
def load_data_transformers(resize_reso=512, crop_reso=448, swap_num=[7, 7]):
|
||||
center_resize = 600
|
||||
Normalize = transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
|
||||
data_transforms = {
|
||||
'swap': transforms.Compose([
|
||||
transforms.Randomswap((swap_num[0], swap_num[1])),
|
||||
]),
|
||||
'common_aug': transforms.Compose([
|
||||
transforms.Resize((resize_reso, resize_reso)),
|
||||
transforms.RandomRotation(degrees=15), # 将图片进行随机旋转
|
||||
transforms.RandomCrop((crop_reso,crop_reso)), # 图片进行随机裁剪
|
||||
transforms.RandomHorizontalFlip(), # 随机水平翻转
|
||||
]),
|
||||
'train_totensor': transforms.Compose([
|
||||
transforms.Resize((crop_reso, crop_reso)),
|
||||
# ImageNetPolicy(),
|
||||
transforms.ToTensor(),
|
||||
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
|
||||
]),
|
||||
'val_totensor': transforms.Compose([
|
||||
transforms.Resize((crop_reso, crop_reso)),
|
||||
transforms.ToTensor(),
|
||||
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
|
||||
]),
|
||||
'test_totensor': transforms.Compose([
|
||||
transforms.Resize((resize_reso, resize_reso)),
|
||||
transforms.CenterCrop((crop_reso, crop_reso)),
|
||||
transforms.ToTensor(),
|
||||
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
|
||||
]),
|
||||
'None': None,
|
||||
}
|
||||
return data_transforms
|
||||
|
||||
''' change three paths dataset save '''
|
||||
|
||||
class LoadConfig(object):
|
||||
def __init__(self, args, version):
|
||||
get_list = []
|
||||
if version == 'train':
|
||||
get_list = ['train', 'val']
|
||||
elif version == 'val':
|
||||
get_list = ['val']
|
||||
elif version == 'test':
|
||||
get_list = ['test']
|
||||
|
||||
|
||||
###############################
|
||||
#### add dataset info here ####
|
||||
###############################
|
||||
|
||||
# put image data in $PATH/data
|
||||
# put annotation txt file in $PATH/anno
|
||||
|
||||
if args.dataset == 'product':
|
||||
self.dataset = args.dataset
|
||||
self.rawdata_root = './../FGVC_product/data'
|
||||
self.anno_root = './../FGVC_product/anno'
|
||||
self.numcls = 2019
|
||||
elif args.dataset == 'CUB':
|
||||
self.dataset = args.dataset
|
||||
self.rawdata_root = './datasets/CUB_200_2011/data'
|
||||
self.anno_root = './datasets/CUB_200_2011/anno'
|
||||
self.numcls = 200
|
||||
elif args.dataset == 'STCAR':
|
||||
self.dataset = args.dataset
|
||||
self.rawdata_root = './dataset/st_car/data'
|
||||
self.anno_root = './dataset/st_car/anno'
|
||||
self.numcls = 196
|
||||
elif args.dataset == 'AIR':
|
||||
self.dataset = args.dataset
|
||||
self.rawdata_root = './dataset/aircraft/data'
|
||||
self.anno_root = './dataset/aircraft/anno'
|
||||
self.numcls = 200
|
||||
|
||||
elif args.dataset == 'HCData':
|
||||
self.dataset = args.dataset
|
||||
self.rawdata_root = '../../data_MPKFNet/data/DCL/datasets/HCData/data'
|
||||
self.anno_root = '../../data_MPKFNet/data/DCL/datasets/HCData/anno'
|
||||
self.numcls = 50
|
||||
else:
|
||||
raise Exception('dataset not defined ???')
|
||||
|
||||
# annotation file organized as :
|
||||
# path/image_name cls_num\n
|
||||
|
||||
if 'train' in get_list:
|
||||
self.train_anno = pd.read_csv(os.path.join(self.anno_root, 'train.txt'),
|
||||
sep=" ",
|
||||
header=None,
|
||||
names=['ImageName', 'label'])
|
||||
|
||||
if 'val' in get_list:
|
||||
self.val_anno = pd.read_csv(os.path.join(self.anno_root, 'test.txt'),
|
||||
sep=" ",
|
||||
header=None,
|
||||
names=['ImageName', 'label'])
|
||||
|
||||
if 'test' in get_list:
|
||||
self.test_anno = pd.read_csv(os.path.join(self.anno_root, 'test.txt'),
|
||||
sep=" ",
|
||||
header=None,
|
||||
names=['ImageName', 'label'])
|
||||
|
||||
self.swap_num = args.swap_num
|
||||
self.backbone = args.backbone
|
||||
|
||||
self.use_dcl = True
|
||||
self.use_backbone = False if self.use_dcl else True
|
||||
self.use_Asoftmax = False
|
||||
self.use_focal_loss = False
|
||||
self.use_fpn = False
|
||||
self.use_hier = False
|
||||
|
||||
self.weighted_sample = False
|
||||
self.cls_2 = True
|
||||
self.cls_2xmul = False
|
||||
|
||||
self.log_folder = './logs'
|
||||
if not os.path.exists(self.log_folder):
|
||||
os.mkdir(self.log_folder)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import numpy as np
|
||||
from torch import nn
|
||||
import torch
|
||||
from torchvision import models, transforms, datasets
|
||||
import torch.nn.functional as F
|
||||
# import pretrainedmodels
|
||||
|
||||
pretrained_model = {'resnet50' : '../data_MPKFNet/weigth/FinedGrainedRecognition/pretrained/vgg16-397923af.pth',}
|
||||
|
||||
|
||||
import pdb
|
||||
|
||||
class MainModel(nn.Module):
|
||||
def __init__(self, config):
|
||||
super(MainModel, self).__init__()
|
||||
self.use_dcl = config.use_dcl
|
||||
self.num_classes = config.numcls
|
||||
self.backbone_arch = config.backbone
|
||||
self.use_Asoftmax = config.use_Asoftmax
|
||||
# 返回属性值
|
||||
if self.backbone_arch in dir(models):
|
||||
self.model = getattr(models, 'vgg16')() # getattr models.vgg16
|
||||
if self.backbone_arch in pretrained_model:
|
||||
self.model.load_state_dict(torch.load(pretrained_model[self.backbone_arch])) # 加载权重
|
||||
#else:
|
||||
# 在 Python 类的内部,无论是类属性还是实例属性,都是以字典的形式进行存储的,其中属性名作为键,而值作为该键对应的值
|
||||
# 将键对应的键值提取出来
|
||||
|
||||
#if self.backbone_arch in pretrained_model:
|
||||
#self.model = pretrainedmodels.__dict__[self.backbone_arch](num_classes=1000, pretrained=None)
|
||||
#else:
|
||||
#self.model = pretrainedmodels.__dict__[self.backbone_arch](num_classes=1000)
|
||||
# 去掉全连接层 模型结构转换
|
||||
if self.backbone_arch == 'resnet50' or self.backbone_arch == 'se_resnet50':
|
||||
self.model = nn.Sequential(*list(self.model.children())[:-2])
|
||||
if self.backbone_arch == 'senet154':
|
||||
self.model = nn.Sequential(*list(self.model.children())[:-3])
|
||||
if self.backbone_arch == 'se_resnext101_32x4d':
|
||||
self.model = nn.Sequential(*list(self.model.children())[:-2])
|
||||
if self.backbone_arch == 'se_resnet101':
|
||||
self.model = nn.Sequential(*list(self.model.children())[:-2])
|
||||
self.avgpool = nn.AdaptiveAvgPool2d(output_size=1)
|
||||
self.classifier = nn.Linear(512, self.num_classes, bias=False) # 输入通道进行了改变 原来是2048
|
||||
if self.use_dcl:
|
||||
# if config.cls_2:
|
||||
self.classifier_swap = nn.Linear(512, 2, bias=False)
|
||||
if config.cls_2xmul:
|
||||
self.classifier_swap = nn.Linear(512, 2*self.num_classes, bias=False)
|
||||
self.Convmask = nn.Conv2d(512, 1, 1, stride=1, padding=0, bias=True) # 输入 输出 卷积核大小 步长
|
||||
self.avgpool2 = nn.AvgPool2d(2, stride=2)
|
||||
|
||||
# if self.use_Asoftmax:
|
||||
# self.Aclassifier = AngleLinear(2048, self.num_classes, bias=False)
|
||||
# 分类网络:output[0] 对抗损失网络:output[1] 区域对齐网络:output[2]
|
||||
def forward(self, x, last_cont=None):
|
||||
x = self.model(x)
|
||||
if self.use_dcl:
|
||||
mask = self.Convmask(x) # 加一个全卷积层
|
||||
mask = self.avgpool2(mask)# 平均池化
|
||||
mask = torch.tanh(mask)
|
||||
mask = mask.view(mask.size(0), -1)
|
||||
x = self.avgpool(x) # 平均值池化
|
||||
x = x.view(x.size(0), -1)
|
||||
out = []
|
||||
out.append(self.classifier(x))
|
||||
|
||||
if self.use_dcl:
|
||||
out.append(self.classifier_swap(x))
|
||||
out.append(mask)
|
||||
|
||||
# if self.use_Asoftmax:
|
||||
# if last_cont is None:
|
||||
# x_size = x.size(0)
|
||||
# out.append(self.Aclassifier(x[0:x_size:2]))
|
||||
# else:
|
||||
# last_x = self.model(last_cont)
|
||||
# last_x = self.avgpool(last_x)
|
||||
# last_x = last_x.view(last_x.size(0), -1)
|
||||
# out.append(self.Aclassifier(last_x))
|
||||
|
||||
return out
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
from .transforms import *
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,750 @@
|
||||
from __future__ import division
|
||||
import torch
|
||||
import math
|
||||
import random
|
||||
from PIL import Image, ImageOps, ImageEnhance
|
||||
try:
|
||||
import accimage
|
||||
except ImportError:
|
||||
accimage = None
|
||||
import numpy as np
|
||||
import numbers
|
||||
import types
|
||||
import collections
|
||||
import warnings
|
||||
|
||||
|
||||
def _is_pil_image(img):
|
||||
if accimage is not None:
|
||||
return isinstance(img, (Image.Image, accimage.Image))
|
||||
else:
|
||||
return isinstance(img, Image.Image)
|
||||
|
||||
|
||||
def _is_tensor_image(img):
|
||||
return torch.is_tensor(img) and img.ndimension() == 3
|
||||
|
||||
|
||||
def _is_numpy_image(img):
|
||||
return isinstance(img, np.ndarray) and (img.ndim in {2, 3})
|
||||
|
||||
|
||||
def to_tensor(pic):
|
||||
"""Convert a ``PIL Image`` or ``numpy.ndarray`` to tensor.
|
||||
|
||||
See ``ToTensor`` for more details.
|
||||
|
||||
Args:
|
||||
pic (PIL Image or numpy.ndarray): Image to be converted to tensor.
|
||||
|
||||
Returns:
|
||||
Tensor: Converted image.
|
||||
"""
|
||||
if not(_is_pil_image(pic) or _is_numpy_image(pic)):
|
||||
raise TypeError('pic should be PIL Image or ndarray. Got {}'.format(type(pic)))
|
||||
|
||||
if isinstance(pic, np.ndarray):
|
||||
# handle numpy array
|
||||
img = torch.from_numpy(pic.transpose((2, 0, 1)))
|
||||
# backward compatibility
|
||||
if isinstance(img, torch.ByteTensor):
|
||||
return img.float().div(255)
|
||||
else:
|
||||
return img
|
||||
|
||||
if accimage is not None and isinstance(pic, accimage.Image):
|
||||
nppic = np.zeros([pic.channels, pic.height, pic.width], dtype=np.float32)
|
||||
pic.copyto(nppic)
|
||||
return torch.from_numpy(nppic)
|
||||
|
||||
# handle PIL Image
|
||||
if pic.mode == 'I':
|
||||
img = torch.from_numpy(np.array(pic, np.int32, copy=False))
|
||||
elif pic.mode == 'I;16':
|
||||
img = torch.from_numpy(np.array(pic, np.int16, copy=False))
|
||||
elif pic.mode == 'F':
|
||||
img = torch.from_numpy(np.array(pic, np.float32, copy=False))
|
||||
elif pic.mode == '1':
|
||||
img = 255 * torch.from_numpy(np.array(pic, np.uint8, copy=False))
|
||||
else:
|
||||
img = torch.ByteTensor(torch.ByteStorage.from_buffer(pic.tobytes()))
|
||||
# PIL image mode: L, P, I, F, RGB, YCbCr, RGBA, CMYK
|
||||
if pic.mode == 'YCbCr':
|
||||
nchannel = 3
|
||||
elif pic.mode == 'I;16':
|
||||
nchannel = 1
|
||||
else:
|
||||
nchannel = len(pic.mode)
|
||||
img = img.view(pic.size[1], pic.size[0], nchannel)
|
||||
# put it from HWC to CHW format
|
||||
# yikes, this transpose takes 80% of the loading time/CPU
|
||||
img = img.transpose(0, 1).transpose(0, 2).contiguous()
|
||||
if isinstance(img, torch.ByteTensor):
|
||||
return img.float().div(255)
|
||||
else:
|
||||
return img
|
||||
|
||||
|
||||
def to_pil_image(pic, mode=None):
|
||||
"""Convert a tensor or an ndarray to PIL Image.
|
||||
|
||||
See :class:`~torchvision.transforms.ToPIlImage` for more details.
|
||||
|
||||
Args:
|
||||
pic (Tensor or numpy.ndarray): Image to be converted to PIL Image.
|
||||
mode (`PIL.Image mode`_): color space and pixel depth of input data (optional).
|
||||
|
||||
.. _PIL.Image mode: http://pillow.readthedocs.io/en/3.4.x/handbook/concepts.html#modes
|
||||
|
||||
Returns:
|
||||
PIL Image: Image converted to PIL Image.
|
||||
"""
|
||||
if not(_is_numpy_image(pic) or _is_tensor_image(pic)):
|
||||
raise TypeError('pic should be Tensor or ndarray. Got {}.'.format(type(pic)))
|
||||
|
||||
npimg = pic
|
||||
if isinstance(pic, torch.FloatTensor):
|
||||
pic = pic.mul(255).byte()
|
||||
if torch.is_tensor(pic):
|
||||
npimg = np.transpose(pic.numpy(), (1, 2, 0))
|
||||
|
||||
if not isinstance(npimg, np.ndarray):
|
||||
raise TypeError('Input pic must be a torch.Tensor or NumPy ndarray, ' +
|
||||
'not {}'.format(type(npimg)))
|
||||
|
||||
if npimg.shape[2] == 1:
|
||||
expected_mode = None
|
||||
npimg = npimg[:, :, 0]
|
||||
if npimg.dtype == np.uint8:
|
||||
expected_mode = 'L'
|
||||
elif npimg.dtype == np.int16:
|
||||
expected_mode = 'I;16'
|
||||
elif npimg.dtype == np.int32:
|
||||
expected_mode = 'I'
|
||||
elif npimg.dtype == np.float32:
|
||||
expected_mode = 'F'
|
||||
if mode is not None and mode != expected_mode:
|
||||
raise ValueError("Incorrect mode ({}) supplied for input type {}. Should be {}"
|
||||
.format(mode, np.dtype, expected_mode))
|
||||
mode = expected_mode
|
||||
|
||||
elif npimg.shape[2] == 4:
|
||||
permitted_4_channel_modes = ['RGBA', 'CMYK']
|
||||
if mode is not None and mode not in permitted_4_channel_modes:
|
||||
raise ValueError("Only modes {} are supported for 4D inputs".format(permitted_4_channel_modes))
|
||||
|
||||
if mode is None and npimg.dtype == np.uint8:
|
||||
mode = 'RGBA'
|
||||
else:
|
||||
permitted_3_channel_modes = ['RGB', 'YCbCr', 'HSV']
|
||||
if mode is not None and mode not in permitted_3_channel_modes:
|
||||
raise ValueError("Only modes {} are supported for 3D inputs".format(permitted_3_channel_modes))
|
||||
if mode is None and npimg.dtype == np.uint8:
|
||||
mode = 'RGB'
|
||||
|
||||
if mode is None:
|
||||
raise TypeError('Input type {} is not supported'.format(npimg.dtype))
|
||||
|
||||
return Image.fromarray(npimg, mode=mode)
|
||||
|
||||
|
||||
def normalize(tensor, mean, std):
|
||||
"""Normalize a tensor image with mean and standard deviation.
|
||||
|
||||
See ``Normalize`` for more details.
|
||||
|
||||
Args:
|
||||
tensor (Tensor): Tensor image of size (C, H, W) to be normalized.
|
||||
mean (sequence): Sequence of means for each channel.
|
||||
std (sequence): Sequence of standard deviations for each channely.
|
||||
|
||||
Returns:
|
||||
Tensor: Normalized Tensor image.
|
||||
"""
|
||||
if not _is_tensor_image(tensor):
|
||||
raise TypeError('tensor is not a torch image.')
|
||||
# TODO: make efficient
|
||||
for t, m, s in zip(tensor, mean, std):
|
||||
t.sub_(m).div_(s)
|
||||
return tensor
|
||||
|
||||
|
||||
def resize(img, size, interpolation=Image.BILINEAR):
|
||||
"""Resize the input PIL Image to the given size.
|
||||
|
||||
Args:
|
||||
img (PIL Image): Image to be resized.
|
||||
size (sequence or int): Desired output size. If size is a sequence like
|
||||
(h, w), the output size will be matched to this. If size is an int,
|
||||
the smaller edge of the image will be matched to this number maintaing
|
||||
the aspect ratio. i.e, if height > width, then image will be rescaled to
|
||||
(size * height / width, size)
|
||||
interpolation (int, optional): Desired interpolation. Default is
|
||||
``PIL.Image.BILINEAR``
|
||||
|
||||
Returns:
|
||||
PIL Image: Resized image.
|
||||
"""
|
||||
if not _is_pil_image(img):
|
||||
raise TypeError('img should be PIL Image. Got {}'.format(type(img)))
|
||||
if not (isinstance(size, int) or (isinstance(size, collections.Iterable) and len(size) == 2)):
|
||||
raise TypeError('Got inappropriate size arg: {}'.format(size))
|
||||
|
||||
if isinstance(size, int):
|
||||
w, h = img.size
|
||||
if (w <= h and w == size) or (h <= w and h == size):
|
||||
return img
|
||||
if w < h:
|
||||
ow = size
|
||||
oh = int(size * h / w)
|
||||
return img.resize((ow, oh), interpolation)
|
||||
else:
|
||||
oh = size
|
||||
ow = int(size * w / h)
|
||||
return img.resize((ow, oh), interpolation)
|
||||
else:
|
||||
return img.resize(size[::-1], interpolation)
|
||||
|
||||
|
||||
def scale(*args, **kwargs):
|
||||
warnings.warn("The use of the transforms.Scale transform is deprecated, " +
|
||||
"please use transforms.Resize instead.")
|
||||
return resize(*args, **kwargs)
|
||||
|
||||
|
||||
def pad(img, padding, fill=0, padding_mode='constant'):
|
||||
"""Pad the given PIL Image on all sides with speficified padding mode and fill value.
|
||||
|
||||
Args:
|
||||
img (PIL Image): Image to be padded.
|
||||
padding (int or tuple): Padding on each border. If a single int is provided this
|
||||
is used to pad all borders. If tuple of length 2 is provided this is the padding
|
||||
on left/right and top/bottom respectively. If a tuple of length 4 is provided
|
||||
this is the padding for the left, top, right and bottom borders
|
||||
respectively.
|
||||
fill: Pixel fill value for constant fill. Default is 0. If a tuple of
|
||||
length 3, it is used to fill R, G, B channels respectively.
|
||||
This value is only used when the padding_mode is constant
|
||||
padding_mode: Type of padding. Should be: constant, edge, reflect or symmetric. Default is constant.
|
||||
constant: pads with a constant value, this value is specified with fill
|
||||
edge: pads with the last value on the edge of the image
|
||||
reflect: pads with reflection of image (without repeating the last value on the edge)
|
||||
padding [1, 2, 3, 4] with 2 elements on both sides in reflect mode
|
||||
will result in [3, 2, 1, 2, 3, 4, 3, 2]
|
||||
symmetric: pads with reflection of image (repeating the last value on the edge)
|
||||
padding [1, 2, 3, 4] with 2 elements on both sides in symmetric mode
|
||||
will result in [2, 1, 1, 2, 3, 4, 4, 3]
|
||||
|
||||
Returns:
|
||||
PIL Image: Padded image.
|
||||
"""
|
||||
if not _is_pil_image(img):
|
||||
raise TypeError('img should be PIL Image. Got {}'.format(type(img)))
|
||||
|
||||
if not isinstance(padding, (numbers.Number, tuple)):
|
||||
raise TypeError('Got inappropriate padding arg')
|
||||
if not isinstance(fill, (numbers.Number, str, tuple)):
|
||||
raise TypeError('Got inappropriate fill arg')
|
||||
if not isinstance(padding_mode, str):
|
||||
raise TypeError('Got inappropriate padding_mode arg')
|
||||
|
||||
if isinstance(padding, collections.Sequence) and len(padding) not in [2, 4]:
|
||||
raise ValueError("Padding must be an int or a 2, or 4 element tuple, not a " +
|
||||
"{} element tuple".format(len(padding)))
|
||||
|
||||
assert padding_mode in ['constant', 'edge', 'reflect', 'symmetric'], \
|
||||
'Padding mode should be either constant, edge, reflect or symmetric'
|
||||
|
||||
if padding_mode == 'constant':
|
||||
return ImageOps.expand(img, border=padding, fill=fill)
|
||||
else:
|
||||
if isinstance(padding, int):
|
||||
pad_left = pad_right = pad_top = pad_bottom = padding
|
||||
if isinstance(padding, collections.Sequence) and len(padding) == 2:
|
||||
pad_left = pad_right = padding[0]
|
||||
pad_top = pad_bottom = padding[1]
|
||||
if isinstance(padding, collections.Sequence) and len(padding) == 4:
|
||||
pad_left = padding[0]
|
||||
pad_top = padding[1]
|
||||
pad_right = padding[2]
|
||||
pad_bottom = padding[3]
|
||||
|
||||
img = np.asarray(img)
|
||||
# RGB image
|
||||
if len(img.shape) == 3:
|
||||
img = np.pad(img, ((pad_top, pad_bottom), (pad_left, pad_right), (0, 0)), padding_mode)
|
||||
# Grayscale image
|
||||
if len(img.shape) == 2:
|
||||
img = np.pad(img, ((pad_top, pad_bottom), (pad_left, pad_right)), padding_mode)
|
||||
|
||||
return Image.fromarray(img)
|
||||
|
||||
|
||||
def crop(img, i, j, h, w):
|
||||
"""Crop the given PIL Image.
|
||||
|
||||
Args:
|
||||
img (PIL Image): Image to be cropped.
|
||||
i: Upper pixel coordinate.
|
||||
j: Left pixel coordinate.
|
||||
h: Height of the cropped image.
|
||||
w: Width of the cropped image.
|
||||
|
||||
Returns:
|
||||
PIL Image: Cropped image.
|
||||
"""
|
||||
if not _is_pil_image(img):
|
||||
raise TypeError('img should be PIL Image. Got {}'.format(type(img)))
|
||||
|
||||
return img.crop((j, i, j + w, i + h))
|
||||
|
||||
|
||||
def center_crop(img, output_size):
|
||||
if isinstance(output_size, numbers.Number):
|
||||
output_size = (int(output_size), int(output_size))
|
||||
w, h = img.size
|
||||
th, tw = output_size
|
||||
i = int(round((h - th) / 2.))
|
||||
j = int(round((w - tw) / 2.))
|
||||
return crop(img, i, j, th, tw)
|
||||
|
||||
|
||||
def resized_crop(img, i, j, h, w, size, interpolation=Image.BILINEAR):
|
||||
"""Crop the given PIL Image and resize it to desired size.
|
||||
|
||||
Notably used in RandomResizedCrop.
|
||||
|
||||
Args:
|
||||
img (PIL Image): Image to be cropped.
|
||||
i: Upper pixel coordinate.
|
||||
j: Left pixel coordinate.
|
||||
h: Height of the cropped image.
|
||||
w: Width of the cropped image.
|
||||
size (sequence or int): Desired output size. Same semantics as ``scale``.
|
||||
interpolation (int, optional): Desired interpolation. Default is
|
||||
``PIL.Image.BILINEAR``.
|
||||
Returns:
|
||||
PIL Image: Cropped image.
|
||||
"""
|
||||
assert _is_pil_image(img), 'img should be PIL Image'
|
||||
img = crop(img, i, j, h, w)
|
||||
img = resize(img, size, interpolation)
|
||||
return img
|
||||
|
||||
|
||||
def hflip(img):
|
||||
"""Horizontally flip the given PIL Image.
|
||||
|
||||
Args:
|
||||
img (PIL Image): Image to be flipped.
|
||||
|
||||
Returns:
|
||||
PIL Image: Horizontall flipped image.
|
||||
"""
|
||||
if not _is_pil_image(img):
|
||||
raise TypeError('img should be PIL Image. Got {}'.format(type(img)))
|
||||
|
||||
return img.transpose(Image.FLIP_LEFT_RIGHT)
|
||||
|
||||
|
||||
def vflip(img):
|
||||
"""Vertically flip the given PIL Image.
|
||||
|
||||
Args:
|
||||
img (PIL Image): Image to be flipped.
|
||||
|
||||
Returns:
|
||||
PIL Image: Vertically flipped image.
|
||||
"""
|
||||
if not _is_pil_image(img):
|
||||
raise TypeError('img should be PIL Image. Got {}'.format(type(img)))
|
||||
|
||||
return img.transpose(Image.FLIP_TOP_BOTTOM)
|
||||
|
||||
|
||||
def swap(img, crop):
|
||||
def crop_image(image, cropnum):
|
||||
width, high = image.size
|
||||
crop_x = [int((width / cropnum[0]) * i) for i in range(cropnum[0] + 1)]
|
||||
crop_y = [int((high / cropnum[1]) * i) for i in range(cropnum[1] + 1)]
|
||||
im_list = []
|
||||
for j in range(len(crop_y) - 1):
|
||||
for i in range(len(crop_x) - 1):
|
||||
im_list.append(image.crop((crop_x[i], crop_y[j], min(crop_x[i + 1], width), min(crop_y[j + 1], high))))
|
||||
return im_list
|
||||
|
||||
widthcut, highcut = img.size
|
||||
img = img.crop((10, 10, widthcut-10, highcut-10))
|
||||
images = crop_image(img, crop)
|
||||
pro = 5
|
||||
if pro >= 5:
|
||||
tmpx = []
|
||||
tmpy = []
|
||||
count_x = 0
|
||||
count_y = 0
|
||||
k = 1
|
||||
RAN = 2
|
||||
for i in range(crop[1] * crop[0]):
|
||||
tmpx.append(images[i])
|
||||
count_x += 1
|
||||
if len(tmpx) >= k:
|
||||
tmp = tmpx[count_x - RAN:count_x]
|
||||
random.shuffle(tmp)
|
||||
tmpx[count_x - RAN:count_x] = tmp
|
||||
if count_x == crop[0]:
|
||||
tmpy.append(tmpx)
|
||||
count_x = 0
|
||||
count_y += 1
|
||||
tmpx = []
|
||||
if len(tmpy) >= k:
|
||||
tmp2 = tmpy[count_y - RAN:count_y]
|
||||
random.shuffle(tmp2)
|
||||
tmpy[count_y - RAN:count_y] = tmp2
|
||||
random_im = []
|
||||
for line in tmpy:
|
||||
random_im.extend(line)
|
||||
|
||||
# random.shuffle(images)
|
||||
width, high = img.size
|
||||
iw = int(width / crop[0])
|
||||
ih = int(high / crop[1])
|
||||
toImage = Image.new('RGB', (iw * crop[0], ih * crop[1]))
|
||||
x = 0
|
||||
y = 0
|
||||
for i in random_im:
|
||||
i = i.resize((iw, ih), Image.ANTIALIAS)
|
||||
toImage.paste(i, (x * iw, y * ih))
|
||||
x += 1
|
||||
if x == crop[0]:
|
||||
x = 0
|
||||
y += 1
|
||||
else:
|
||||
toImage = img
|
||||
toImage = toImage.resize((widthcut, highcut))
|
||||
return toImage
|
||||
|
||||
|
||||
|
||||
def five_crop(img, size):
|
||||
"""Crop the given PIL Image into four corners and the central crop.
|
||||
|
||||
.. Note::
|
||||
This transform returns a tuple of images and there may be a
|
||||
mismatch in the number of inputs and targets your ``Dataset`` returns.
|
||||
|
||||
Args:
|
||||
size (sequence or int): Desired output size of the crop. If size is an
|
||||
int instead of sequence like (h, w), a square crop (size, size) is
|
||||
made.
|
||||
Returns:
|
||||
tuple: tuple (tl, tr, bl, br, center) corresponding top left,
|
||||
top right, bottom left, bottom right and center crop.
|
||||
"""
|
||||
if isinstance(size, numbers.Number):
|
||||
size = (int(size), int(size))
|
||||
else:
|
||||
assert len(size) == 2, "Please provide only two dimensions (h, w) for size."
|
||||
|
||||
w, h = img.size
|
||||
crop_h, crop_w = size
|
||||
if crop_w > w or crop_h > h:
|
||||
raise ValueError("Requested crop size {} is bigger than input size {}".format(size,
|
||||
(h, w)))
|
||||
tl = img.crop((0, 0, crop_w, crop_h))
|
||||
tr = img.crop((w - crop_w, 0, w, crop_h))
|
||||
bl = img.crop((0, h - crop_h, crop_w, h))
|
||||
br = img.crop((w - crop_w, h - crop_h, w, h))
|
||||
center = center_crop(img, (crop_h, crop_w))
|
||||
return (tl, tr, bl, br, center)
|
||||
|
||||
|
||||
def ten_crop(img, size, vertical_flip=False):
|
||||
"""Crop the given PIL Image into four corners and the central crop plus the
|
||||
flipped version of these (horizontal flipping is used by default).
|
||||
|
||||
.. Note::
|
||||
This transform returns a tuple of images and there may be a
|
||||
mismatch in the number of inputs and targets your ``Dataset`` returns.
|
||||
|
||||
Args:
|
||||
size (sequence or int): Desired output size of the crop. If size is an
|
||||
int instead of sequence like (h, w), a square crop (size, size) is
|
||||
made.
|
||||
vertical_flip (bool): Use vertical flipping instead of horizontal
|
||||
|
||||
Returns:
|
||||
tuple: tuple (tl, tr, bl, br, center, tl_flip, tr_flip, bl_flip,
|
||||
br_flip, center_flip) corresponding top left, top right,
|
||||
bottom left, bottom right and center crop and same for the
|
||||
flipped image.
|
||||
"""
|
||||
if isinstance(size, numbers.Number):
|
||||
size = (int(size), int(size))
|
||||
else:
|
||||
assert len(size) == 2, "Please provide only two dimensions (h, w) for size."
|
||||
|
||||
first_five = five_crop(img, size)
|
||||
|
||||
if vertical_flip:
|
||||
img = vflip(img)
|
||||
else:
|
||||
img = hflip(img)
|
||||
|
||||
second_five = five_crop(img, size)
|
||||
return first_five + second_five
|
||||
|
||||
|
||||
def adjust_brightness(img, brightness_factor):
|
||||
"""Adjust brightness of an Image.
|
||||
|
||||
Args:
|
||||
img (PIL Image): PIL Image to be adjusted.
|
||||
brightness_factor (float): How much to adjust the brightness. Can be
|
||||
any non negative number. 0 gives a black image, 1 gives the
|
||||
original image while 2 increases the brightness by a factor of 2.
|
||||
|
||||
Returns:
|
||||
PIL Image: Brightness adjusted image.
|
||||
"""
|
||||
if not _is_pil_image(img):
|
||||
raise TypeError('img should be PIL Image. Got {}'.format(type(img)))
|
||||
|
||||
enhancer = ImageEnhance.Brightness(img)
|
||||
img = enhancer.enhance(brightness_factor)
|
||||
return img
|
||||
|
||||
|
||||
def adjust_contrast(img, contrast_factor):
|
||||
"""Adjust contrast of an Image.
|
||||
|
||||
Args:
|
||||
img (PIL Image): PIL Image to be adjusted.
|
||||
contrast_factor (float): How much to adjust the contrast. Can be any
|
||||
non negative number. 0 gives a solid gray image, 1 gives the
|
||||
original image while 2 increases the contrast by a factor of 2.
|
||||
|
||||
Returns:
|
||||
PIL Image: Contrast adjusted image.
|
||||
"""
|
||||
if not _is_pil_image(img):
|
||||
raise TypeError('img should be PIL Image. Got {}'.format(type(img)))
|
||||
|
||||
enhancer = ImageEnhance.Contrast(img)
|
||||
img = enhancer.enhance(contrast_factor)
|
||||
return img
|
||||
|
||||
|
||||
def adjust_saturation(img, saturation_factor):
|
||||
"""Adjust color saturation of an image.
|
||||
|
||||
Args:
|
||||
img (PIL Image): PIL Image to be adjusted.
|
||||
saturation_factor (float): How much to adjust the saturation. 0 will
|
||||
give a black and white image, 1 will give the original image while
|
||||
2 will enhance the saturation by a factor of 2.
|
||||
|
||||
Returns:
|
||||
PIL Image: Saturation adjusted image.
|
||||
"""
|
||||
if not _is_pil_image(img):
|
||||
raise TypeError('img should be PIL Image. Got {}'.format(type(img)))
|
||||
|
||||
enhancer = ImageEnhance.Color(img)
|
||||
img = enhancer.enhance(saturation_factor)
|
||||
return img
|
||||
|
||||
|
||||
def adjust_hue(img, hue_factor):
|
||||
"""Adjust hue of an image.
|
||||
|
||||
The image hue is adjusted by converting the image to HSV and
|
||||
cyclically shifting the intensities in the hue channel (H).
|
||||
The image is then converted back to original image mode.
|
||||
|
||||
`hue_factor` is the amount of shift in H channel and must be in the
|
||||
interval `[-0.5, 0.5]`.
|
||||
|
||||
See https://en.wikipedia.org/wiki/Hue for more details on Hue.
|
||||
|
||||
Args:
|
||||
img (PIL Image): PIL Image to be adjusted.
|
||||
hue_factor (float): How much to shift the hue channel. Should be in
|
||||
[-0.5, 0.5]. 0.5 and -0.5 give complete reversal of hue channel in
|
||||
HSV space in positive and negative direction respectively.
|
||||
0 means no shift. Therefore, both -0.5 and 0.5 will give an image
|
||||
with complementary colors while 0 gives the original image.
|
||||
|
||||
Returns:
|
||||
PIL Image: Hue adjusted image.
|
||||
"""
|
||||
if not(-0.5 <= hue_factor <= 0.5):
|
||||
raise ValueError('hue_factor is not in [-0.5, 0.5].'.format(hue_factor))
|
||||
|
||||
if not _is_pil_image(img):
|
||||
raise TypeError('img should be PIL Image. Got {}'.format(type(img)))
|
||||
|
||||
input_mode = img.mode
|
||||
if input_mode in {'L', '1', 'I', 'F'}:
|
||||
return img
|
||||
|
||||
h, s, v = img.convert('HSV').split()
|
||||
|
||||
np_h = np.array(h, dtype=np.uint8)
|
||||
# uint8 addition take cares of rotation across boundaries
|
||||
with np.errstate(over='ignore'):
|
||||
np_h += np.uint8(hue_factor * 255)
|
||||
h = Image.fromarray(np_h, 'L')
|
||||
|
||||
img = Image.merge('HSV', (h, s, v)).convert(input_mode)
|
||||
return img
|
||||
|
||||
|
||||
def adjust_gamma(img, gamma, gain=1):
|
||||
"""Perform gamma correction on an image.
|
||||
|
||||
Also known as Power Law Transform. Intensities in RGB mode are adjusted
|
||||
based on the following equation:
|
||||
|
||||
I_out = 255 * gain * ((I_in / 255) ** gamma)
|
||||
|
||||
See https://en.wikipedia.org/wiki/Gamma_correction for more details.
|
||||
|
||||
Args:
|
||||
img (PIL Image): PIL Image to be adjusted.
|
||||
gamma (float): Non negative real number. gamma larger than 1 make the
|
||||
shadows darker, while gamma smaller than 1 make dark regions
|
||||
lighter.
|
||||
gain (float): The constant multiplier.
|
||||
"""
|
||||
if not _is_pil_image(img):
|
||||
raise TypeError('img should be PIL Image. Got {}'.format(type(img)))
|
||||
|
||||
if gamma < 0:
|
||||
raise ValueError('Gamma should be a non-negative real number')
|
||||
|
||||
input_mode = img.mode
|
||||
img = img.convert('RGB')
|
||||
|
||||
gamma_map = [255 * gain * pow(ele / 255., gamma) for ele in range(256)] * 3
|
||||
img = img.point(gamma_map) # use PIL's point-function to accelerate this part
|
||||
|
||||
img = img.convert(input_mode)
|
||||
return img
|
||||
|
||||
|
||||
def rotate(img, angle, resample=False, expand=False, center=None):
|
||||
"""Rotate the image by angle.
|
||||
|
||||
|
||||
Args:
|
||||
img (PIL Image): PIL Image to be rotated.
|
||||
angle ({float, int}): In degrees degrees counter clockwise order.
|
||||
resample ({PIL.Image.NEAREST, PIL.Image.BILINEAR, PIL.Image.BICUBIC}, optional):
|
||||
An optional resampling filter.
|
||||
See http://pillow.readthedocs.io/en/3.4.x/handbook/concepts.html#filters
|
||||
If omitted, or if the image has mode "1" or "P", it is set to PIL.Image.NEAREST.
|
||||
expand (bool, optional): Optional expansion flag.
|
||||
If true, expands the output image to make it large enough to hold the entire rotated image.
|
||||
If false or omitted, make the output image the same size as the input image.
|
||||
Note that the expand flag assumes rotation around the center and no translation.
|
||||
center (2-tuple, optional): Optional center of rotation.
|
||||
Origin is the upper left corner.
|
||||
Default is the center of the image.
|
||||
"""
|
||||
|
||||
if not _is_pil_image(img):
|
||||
raise TypeError('img should be PIL Image. Got {}'.format(type(img)))
|
||||
|
||||
return img.rotate(angle, resample, expand, center)
|
||||
|
||||
|
||||
def _get_inverse_affine_matrix(center, angle, translate, scale, shear):
|
||||
# Helper method to compute inverse matrix for affine transformation
|
||||
|
||||
# As it is explained in PIL.Image.rotate
|
||||
# We need compute INVERSE of affine transformation matrix: M = T * C * RSS * C^-1
|
||||
# where T is translation matrix: [1, 0, tx | 0, 1, ty | 0, 0, 1]
|
||||
# C is translation matrix to keep center: [1, 0, cx | 0, 1, cy | 0, 0, 1]
|
||||
# RSS is rotation with scale and shear matrix
|
||||
# RSS(a, scale, shear) = [ cos(a)*scale -sin(a + shear)*scale 0]
|
||||
# [ sin(a)*scale cos(a + shear)*scale 0]
|
||||
# [ 0 0 1]
|
||||
# Thus, the inverse is M^-1 = C * RSS^-1 * C^-1 * T^-1
|
||||
|
||||
angle = math.radians(angle)
|
||||
shear = math.radians(shear)
|
||||
scale = 1.0 / scale
|
||||
|
||||
# Inverted rotation matrix with scale and shear
|
||||
d = math.cos(angle + shear) * math.cos(angle) + math.sin(angle + shear) * math.sin(angle)
|
||||
matrix = [
|
||||
math.cos(angle + shear), math.sin(angle + shear), 0,
|
||||
-math.sin(angle), math.cos(angle), 0
|
||||
]
|
||||
matrix = [scale / d * m for m in matrix]
|
||||
|
||||
# Apply inverse of translation and of center translation: RSS^-1 * C^-1 * T^-1
|
||||
matrix[2] += matrix[0] * (-center[0] - translate[0]) + matrix[1] * (-center[1] - translate[1])
|
||||
matrix[5] += matrix[3] * (-center[0] - translate[0]) + matrix[4] * (-center[1] - translate[1])
|
||||
|
||||
# Apply center translation: C * RSS^-1 * C^-1 * T^-1
|
||||
matrix[2] += center[0]
|
||||
matrix[5] += center[1]
|
||||
return matrix
|
||||
|
||||
|
||||
def affine(img, angle, translate, scale, shear, resample=0, fillcolor=None):
|
||||
"""Apply affine transformation on the image keeping image center invariant
|
||||
|
||||
Args:
|
||||
img (PIL Image): PIL Image to be rotated.
|
||||
angle ({float, int}): rotation angle in degrees between -180 and 180, clockwise direction.
|
||||
translate (list or tuple of integers): horizontal and vertical translations (post-rotation translation)
|
||||
scale (float): overall scale
|
||||
shear (float): shear angle value in degrees between -180 to 180, clockwise direction.
|
||||
resample ({PIL.Image.NEAREST, PIL.Image.BILINEAR, PIL.Image.BICUBIC}, optional):
|
||||
An optional resampling filter.
|
||||
See http://pillow.readthedocs.io/en/3.4.x/handbook/concepts.html#filters
|
||||
If omitted, or if the image has mode "1" or "P", it is set to PIL.Image.NEAREST.
|
||||
fillcolor (int): Optional fill color for the area outside the transform in the output image. (Pillow>=5.0.0)
|
||||
"""
|
||||
if not _is_pil_image(img):
|
||||
raise TypeError('img should be PIL Image. Got {}'.format(type(img)))
|
||||
|
||||
assert isinstance(translate, (tuple, list)) and len(translate) == 2, \
|
||||
"Argument translate should be a list or tuple of length 2"
|
||||
|
||||
assert scale > 0.0, "Argument scale should be positive"
|
||||
|
||||
output_size = img.size
|
||||
center = (img.size[0] * 0.5 + 0.5, img.size[1] * 0.5 + 0.5)
|
||||
matrix = _get_inverse_affine_matrix(center, angle, translate, scale, shear)
|
||||
kwargs = {}
|
||||
return img.transform(output_size, Image.AFFINE, matrix, resample, **kwargs)
|
||||
|
||||
|
||||
def to_grayscale(img, num_output_channels=1):
|
||||
"""Convert image to grayscale version of image.
|
||||
|
||||
Args:
|
||||
img (PIL Image): Image to be converted to grayscale.
|
||||
|
||||
Returns:
|
||||
PIL Image: Grayscale version of the image.
|
||||
if num_output_channels == 1 : returned image is single channel
|
||||
if num_output_channels == 3 : returned image is 3 channel with r == g == b
|
||||
"""
|
||||
if not _is_pil_image(img):
|
||||
raise TypeError('img should be PIL Image. Got {}'.format(type(img)))
|
||||
|
||||
if num_output_channels == 1:
|
||||
img = img.convert('L')
|
||||
elif num_output_channels == 3:
|
||||
img = img.convert('L')
|
||||
np_img = np.array(img, dtype=np.uint8)
|
||||
np_img = np.dstack([np_img, np_img, np_img])
|
||||
img = Image.fromarray(np_img, 'RGB')
|
||||
else:
|
||||
raise ValueError('num_output_channels should be either 1 or 3')
|
||||
|
||||
return img
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,47 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch.autograd import Variable
|
||||
import torch.nn.functional as F
|
||||
from torch.nn import Parameter
|
||||
import math
|
||||
|
||||
import pdb
|
||||
|
||||
class AngleLoss(nn.Module):
|
||||
def __init__(self, gamma=0):
|
||||
super(AngleLoss, self).__init__()
|
||||
self.gamma = gamma
|
||||
self.it = 0
|
||||
self.LambdaMin = 50.0
|
||||
self.LambdaMax = 1500.0
|
||||
self.lamb = 1500.0
|
||||
|
||||
def forward(self, input, target, decay=None):
|
||||
self.it += 1
|
||||
cos_theta,phi_theta = input
|
||||
target = target.view(-1,1) #size=(B,1)
|
||||
|
||||
index = cos_theta.data * 0.0 #size=(B,Classnum)
|
||||
index.scatter_(1,target.data.view(-1,1),1)
|
||||
index = index.byte()
|
||||
index = Variable(index)
|
||||
|
||||
if decay is None:
|
||||
self.lamb = max(self.LambdaMin,self.LambdaMax/(1+0.1*self.it ))
|
||||
else:
|
||||
self.LambdaMax *= decay
|
||||
self.lamb = max(self.LambdaMin, self.LambdaMax)
|
||||
output = cos_theta * 1.0 #size=(B,Classnum)
|
||||
output[index] -= cos_theta[index]*(1.0+0)/(1+self.lamb)
|
||||
output[index] += phi_theta[index]*(1.0+0)/(1+self.lamb)
|
||||
|
||||
logpt = F.log_softmax(output, 1)
|
||||
logpt = logpt.gather(1,target)
|
||||
logpt = logpt.view(-1)
|
||||
pt = Variable(logpt.data.exp())
|
||||
|
||||
loss = -1 * (1-pt)**self.gamma * logpt
|
||||
loss = loss.mean()
|
||||
|
||||
return loss
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1,232 @@
|
||||
from PIL import Image, ImageEnhance, ImageOps
|
||||
import numpy as np
|
||||
import random
|
||||
|
||||
|
||||
class ImageNetPolicy(object):
|
||||
""" Randomly choose one of the best 24 Sub-policies on ImageNet.
|
||||
Example:
|
||||
>>> policy = ImageNetPolicy()
|
||||
>>> transformed = policy(image)
|
||||
Example as a PyTorch Transform:
|
||||
>>> transform=transforms.Compose([
|
||||
>>> transforms.Resize(256),
|
||||
>>> ImageNetPolicy(),
|
||||
>>> transforms.ToTensor()])
|
||||
"""
|
||||
def __init__(self, fillcolor=(128, 128, 128)):
|
||||
self.policies = [
|
||||
SubPolicy(0.4, "posterize", 8, 0.6, "rotate", 9, fillcolor),
|
||||
SubPolicy(0.6, "solarize", 5, 0.6, "autocontrast", 5, fillcolor),
|
||||
SubPolicy(0.8, "equalize", 8, 0.6, "equalize", 3, fillcolor),
|
||||
SubPolicy(0.6, "posterize", 7, 0.6, "posterize", 6, fillcolor),
|
||||
SubPolicy(0.4, "equalize", 7, 0.2, "solarize", 4, fillcolor),
|
||||
|
||||
SubPolicy(0.4, "equalize", 4, 0.8, "rotate", 8, fillcolor),
|
||||
SubPolicy(0.6, "solarize", 3, 0.6, "equalize", 7, fillcolor),
|
||||
SubPolicy(0.8, "posterize", 5, 1.0, "equalize", 2, fillcolor),
|
||||
SubPolicy(0.2, "rotate", 3, 0.6, "solarize", 8, fillcolor),
|
||||
SubPolicy(0.6, "equalize", 8, 0.4, "posterize", 6, fillcolor),
|
||||
|
||||
SubPolicy(0.8, "rotate", 8, 0.4, "color", 0, fillcolor),
|
||||
SubPolicy(0.4, "rotate", 9, 0.6, "equalize", 2, fillcolor),
|
||||
SubPolicy(0.0, "equalize", 7, 0.8, "equalize", 8, fillcolor),
|
||||
SubPolicy(0.6, "invert", 4, 1.0, "equalize", 8, fillcolor),
|
||||
SubPolicy(0.6, "color", 4, 1.0, "contrast", 8, fillcolor),
|
||||
|
||||
SubPolicy(0.8, "rotate", 8, 1.0, "color", 2, fillcolor),
|
||||
SubPolicy(0.8, "color", 8, 0.8, "solarize", 7, fillcolor),
|
||||
SubPolicy(0.4, "sharpness", 7, 0.6, "invert", 8, fillcolor),
|
||||
SubPolicy(0.6, "shearX", 5, 1.0, "equalize", 9, fillcolor),
|
||||
SubPolicy(0.4, "color", 0, 0.6, "equalize", 3, fillcolor),
|
||||
|
||||
SubPolicy(0.4, "equalize", 7, 0.2, "solarize", 4, fillcolor),
|
||||
SubPolicy(0.6, "solarize", 5, 0.6, "autocontrast", 5, fillcolor),
|
||||
SubPolicy(0.6, "invert", 4, 1.0, "equalize", 8, fillcolor),
|
||||
SubPolicy(0.6, "color", 4, 1.0, "contrast", 8, fillcolor),
|
||||
SubPolicy(0.8, "equalize", 8, 0.6, "equalize", 3, fillcolor)
|
||||
]
|
||||
|
||||
|
||||
def __call__(self, img):
|
||||
policy_idx = random.randint(0, len(self.policies) - 1)
|
||||
return self.policies[policy_idx](img)
|
||||
|
||||
def __repr__(self):
|
||||
return "AutoAugment ImageNet Policy"
|
||||
|
||||
|
||||
class CIFAR10Policy(object):
|
||||
""" Randomly choose one of the best 25 Sub-policies on CIFAR10.
|
||||
Example:
|
||||
>>> policy = CIFAR10Policy()
|
||||
>>> transformed = policy(image)
|
||||
Example as a PyTorch Transform:
|
||||
>>> transform=transforms.Compose([
|
||||
>>> transforms.Resize(256),
|
||||
>>> CIFAR10Policy(),
|
||||
>>> transforms.ToTensor()])
|
||||
"""
|
||||
def __init__(self, fillcolor=(128, 128, 128)):
|
||||
self.policies = [
|
||||
SubPolicy(0.1, "invert", 7, 0.2, "contrast", 6, fillcolor),
|
||||
SubPolicy(0.7, "rotate", 2, 0.3, "translateX", 9, fillcolor),
|
||||
SubPolicy(0.8, "sharpness", 1, 0.9, "sharpness", 3, fillcolor),
|
||||
SubPolicy(0.5, "shearY", 8, 0.7, "translateY", 9, fillcolor),
|
||||
SubPolicy(0.5, "autocontrast", 8, 0.9, "equalize", 2, fillcolor),
|
||||
|
||||
SubPolicy(0.2, "shearY", 7, 0.3, "posterize", 7, fillcolor),
|
||||
SubPolicy(0.4, "color", 3, 0.6, "brightness", 7, fillcolor),
|
||||
SubPolicy(0.3, "sharpness", 9, 0.7, "brightness", 9, fillcolor),
|
||||
SubPolicy(0.6, "equalize", 5, 0.5, "equalize", 1, fillcolor),
|
||||
SubPolicy(0.6, "contrast", 7, 0.6, "sharpness", 5, fillcolor),
|
||||
|
||||
SubPolicy(0.7, "color", 7, 0.5, "translateX", 8, fillcolor),
|
||||
SubPolicy(0.3, "equalize", 7, 0.4, "autocontrast", 8, fillcolor),
|
||||
SubPolicy(0.4, "translateY", 3, 0.2, "sharpness", 6, fillcolor),
|
||||
SubPolicy(0.9, "brightness", 6, 0.2, "color", 8, fillcolor),
|
||||
SubPolicy(0.5, "solarize", 2, 0.0, "invert", 3, fillcolor),
|
||||
|
||||
SubPolicy(0.2, "equalize", 0, 0.6, "autocontrast", 0, fillcolor),
|
||||
SubPolicy(0.2, "equalize", 8, 0.8, "equalize", 4, fillcolor),
|
||||
SubPolicy(0.9, "color", 9, 0.6, "equalize", 6, fillcolor),
|
||||
SubPolicy(0.8, "autocontrast", 4, 0.2, "solarize", 8, fillcolor),
|
||||
SubPolicy(0.1, "brightness", 3, 0.7, "color", 0, fillcolor),
|
||||
|
||||
SubPolicy(0.4, "solarize", 5, 0.9, "autocontrast", 3, fillcolor),
|
||||
SubPolicy(0.9, "translateY", 9, 0.7, "translateY", 9, fillcolor),
|
||||
SubPolicy(0.9, "autocontrast", 2, 0.8, "solarize", 3, fillcolor),
|
||||
SubPolicy(0.8, "equalize", 8, 0.1, "invert", 3, fillcolor),
|
||||
SubPolicy(0.7, "translateY", 9, 0.9, "autocontrast", 1, fillcolor)
|
||||
]
|
||||
|
||||
|
||||
def __call__(self, img):
|
||||
policy_idx = random.randint(0, len(self.policies) - 1)
|
||||
return self.policies[policy_idx](img)
|
||||
|
||||
def __repr__(self):
|
||||
return "AutoAugment CIFAR10 Policy"
|
||||
|
||||
|
||||
class SVHNPolicy(object):
|
||||
""" Randomly choose one of the best 25 Sub-policies on SVHN.
|
||||
Example:
|
||||
>>> policy = SVHNPolicy()
|
||||
>>> transformed = policy(image)
|
||||
Example as a PyTorch Transform:
|
||||
>>> transform=transforms.Compose([
|
||||
>>> transforms.Resize(256),
|
||||
>>> SVHNPolicy(),
|
||||
>>> transforms.ToTensor()])
|
||||
"""
|
||||
def __init__(self, fillcolor=(128, 128, 128)):
|
||||
self.policies = [
|
||||
SubPolicy(0.9, "shearX", 4, 0.2, "invert", 3, fillcolor),
|
||||
SubPolicy(0.9, "shearY", 8, 0.7, "invert", 5, fillcolor),
|
||||
SubPolicy(0.6, "equalize", 5, 0.6, "solarize", 6, fillcolor),
|
||||
SubPolicy(0.9, "invert", 3, 0.6, "equalize", 3, fillcolor),
|
||||
SubPolicy(0.6, "equalize", 1, 0.9, "rotate", 3, fillcolor),
|
||||
|
||||
SubPolicy(0.9, "shearX", 4, 0.8, "autocontrast", 3, fillcolor),
|
||||
SubPolicy(0.9, "shearY", 8, 0.4, "invert", 5, fillcolor),
|
||||
SubPolicy(0.9, "shearY", 5, 0.2, "solarize", 6, fillcolor),
|
||||
SubPolicy(0.9, "invert", 6, 0.8, "autocontrast", 1, fillcolor),
|
||||
SubPolicy(0.6, "equalize", 3, 0.9, "rotate", 3, fillcolor),
|
||||
|
||||
SubPolicy(0.9, "shearX", 4, 0.3, "solarize", 3, fillcolor),
|
||||
SubPolicy(0.8, "shearY", 8, 0.7, "invert", 4, fillcolor),
|
||||
SubPolicy(0.9, "equalize", 5, 0.6, "translateY", 6, fillcolor),
|
||||
SubPolicy(0.9, "invert", 4, 0.6, "equalize", 7, fillcolor),
|
||||
SubPolicy(0.3, "contrast", 3, 0.8, "rotate", 4, fillcolor),
|
||||
|
||||
SubPolicy(0.8, "invert", 5, 0.0, "translateY", 2, fillcolor),
|
||||
SubPolicy(0.7, "shearY", 6, 0.4, "solarize", 8, fillcolor),
|
||||
SubPolicy(0.6, "invert", 4, 0.8, "rotate", 4, fillcolor),
|
||||
SubPolicy(0.3, "shearY", 7, 0.9, "translateX", 3, fillcolor),
|
||||
SubPolicy(0.1, "shearX", 6, 0.6, "invert", 5, fillcolor),
|
||||
|
||||
SubPolicy(0.7, "solarize", 2, 0.6, "translateY", 7, fillcolor),
|
||||
SubPolicy(0.8, "shearY", 4, 0.8, "invert", 8, fillcolor),
|
||||
SubPolicy(0.7, "shearX", 9, 0.8, "translateY", 3, fillcolor),
|
||||
SubPolicy(0.8, "shearY", 5, 0.7, "autocontrast", 3, fillcolor),
|
||||
SubPolicy(0.7, "shearX", 2, 0.1, "invert", 5, fillcolor)
|
||||
]
|
||||
|
||||
|
||||
def __call__(self, img):
|
||||
policy_idx = random.randint(0, len(self.policies) - 1)
|
||||
return self.policies[policy_idx](img)
|
||||
|
||||
def __repr__(self):
|
||||
return "AutoAugment SVHN Policy"
|
||||
|
||||
|
||||
class SubPolicy(object):
|
||||
def __init__(self, p1, operation1, magnitude_idx1, p2, operation2, magnitude_idx2, fillcolor=(128, 128, 128)):
|
||||
ranges = {
|
||||
"shearX": np.linspace(0, 0.3, 10),
|
||||
"shearY": np.linspace(0, 0.3, 10),
|
||||
"translateX": np.linspace(0, 150 / 331, 10),
|
||||
"translateY": np.linspace(0, 150 / 331, 10),
|
||||
"rotate": np.linspace(0, 30, 10),
|
||||
"color": np.linspace(0.0, 0.9, 10),
|
||||
"posterize": np.round(np.linspace(8, 4, 10), 0).astype(np.int),
|
||||
"solarize": np.linspace(256, 0, 10),
|
||||
"contrast": np.linspace(0.0, 0.9, 10),
|
||||
"sharpness": np.linspace(0.0, 0.9, 10),
|
||||
"brightness": np.linspace(0.0, 0.9, 10),
|
||||
"autocontrast": [0] * 10,
|
||||
"equalize": [0] * 10,
|
||||
"invert": [0] * 10
|
||||
}
|
||||
|
||||
# from https://stackoverflow.com/questions/5252170/specify-image-filling-color-when-rotating-in-python-with-pil-and-setting-expand
|
||||
def rotate_with_fill(img, magnitude):
|
||||
rot = img.convert("RGBA").rotate(magnitude)
|
||||
return Image.composite(rot, Image.new("RGBA", rot.size, (128,) * 4), rot).convert(img.mode)
|
||||
|
||||
func = {
|
||||
"shearX": lambda img, magnitude: img.transform(
|
||||
img.size, Image.AFFINE, (1, magnitude * random.choice([-1, 1]), 0, 0, 1, 0),
|
||||
Image.BICUBIC, fillcolor=fillcolor),
|
||||
"shearY": lambda img, magnitude: img.transform(
|
||||
img.size, Image.AFFINE, (1, 0, 0, magnitude * random.choice([-1, 1]), 1, 0),
|
||||
Image.BICUBIC, fillcolor=fillcolor),
|
||||
"translateX": lambda img, magnitude: img.transform(
|
||||
img.size, Image.AFFINE, (1, 0, magnitude * img.size[0] * random.choice([-1, 1]), 0, 1, 0),
|
||||
fillcolor=fillcolor),
|
||||
"translateY": lambda img, magnitude: img.transform(
|
||||
img.size, Image.AFFINE, (1, 0, 0, 0, 1, magnitude * img.size[1] * random.choice([-1, 1])),
|
||||
fillcolor=fillcolor),
|
||||
"rotate": lambda img, magnitude: rotate_with_fill(img, magnitude),
|
||||
# "rotate": lambda img, magnitude: img.rotate(magnitude * random.choice([-1, 1])),
|
||||
"color": lambda img, magnitude: ImageEnhance.Color(img).enhance(1 + magnitude * random.choice([-1, 1])),
|
||||
"posterize": lambda img, magnitude: ImageOps.posterize(img, magnitude),
|
||||
"solarize": lambda img, magnitude: ImageOps.solarize(img, magnitude),
|
||||
"contrast": lambda img, magnitude: ImageEnhance.Contrast(img).enhance(
|
||||
1 + magnitude * random.choice([-1, 1])),
|
||||
"sharpness": lambda img, magnitude: ImageEnhance.Sharpness(img).enhance(
|
||||
1 + magnitude * random.choice([-1, 1])),
|
||||
"brightness": lambda img, magnitude: ImageEnhance.Brightness(img).enhance(
|
||||
1 + magnitude * random.choice([-1, 1])),
|
||||
"autocontrast": lambda img, magnitude: ImageOps.autocontrast(img),
|
||||
"equalize": lambda img, magnitude: ImageOps.equalize(img),
|
||||
"invert": lambda img, magnitude: ImageOps.invert(img)
|
||||
}
|
||||
|
||||
# self.name = "{}_{:.2f}_and_{}_{:.2f}".format(
|
||||
# operation1, ranges[operation1][magnitude_idx1],
|
||||
# operation2, ranges[operation2][magnitude_idx2])
|
||||
self.p1 = p1
|
||||
self.operation1 = func[operation1]
|
||||
self.magnitude1 = ranges[operation1][magnitude_idx1]
|
||||
self.p2 = p2
|
||||
self.operation2 = func[operation2]
|
||||
self.magnitude2 = ranges[operation2][magnitude_idx2]
|
||||
|
||||
|
||||
def __call__(self, img):
|
||||
if random.random() < self.p1: img = self.operation1(img, self.magnitude1)
|
||||
if random.random() < self.p2: img = self.operation2(img, self.magnitude2)
|
||||
return img
|
||||
@@ -0,0 +1,183 @@
|
||||
# coding=utf8
|
||||
from __future__ import division
|
||||
import os
|
||||
import torch
|
||||
import torch.utils.data as data
|
||||
import pandas
|
||||
import random
|
||||
import PIL.Image as Image
|
||||
from PIL import ImageStat
|
||||
|
||||
import pdb
|
||||
|
||||
def random_sample(img_names, labels):
|
||||
anno_dict = {}
|
||||
img_list = []
|
||||
anno_list = []
|
||||
for img, anno in zip(img_names, labels):
|
||||
if not anno in anno_dict:
|
||||
anno_dict[anno] = [img]
|
||||
else:
|
||||
anno_dict[anno].append(img)
|
||||
|
||||
for anno in anno_dict.keys():
|
||||
anno_len = len(anno_dict[anno])
|
||||
fetch_keys = random.sample(list(range(anno_len)), anno_len//10)
|
||||
img_list.extend([anno_dict[anno][x] for x in fetch_keys])
|
||||
anno_list.extend([anno for x in fetch_keys])
|
||||
return img_list, anno_list
|
||||
|
||||
|
||||
|
||||
class dataset(data.Dataset):
|
||||
def __init__(self, Config, anno, swap_size=[7,7], common_aug=None, swap=None, totensor=None, train=False, train_val=False, test=False):
|
||||
self.root_path = Config.rawdata_root
|
||||
self.numcls = Config.numcls
|
||||
self.dataset = Config.dataset
|
||||
self.use_cls_2 = Config.cls_2
|
||||
self.use_cls_mul = Config.cls_2xmul
|
||||
if isinstance(anno, pandas.core.frame.DataFrame): # 判断类型
|
||||
self.paths = anno['ImageName'].tolist()
|
||||
self.labels = anno['label'].tolist()
|
||||
elif isinstance(anno, dict):
|
||||
self.paths = anno['img_name']
|
||||
self.labels = anno['label']
|
||||
|
||||
if train_val:
|
||||
self.paths, self.labels = random_sample(self.paths, self.labels)
|
||||
self.common_aug = common_aug
|
||||
self.swap = swap
|
||||
self.totensor = totensor
|
||||
self.cfg = Config
|
||||
self.train = train
|
||||
self.swap_size = swap_size
|
||||
self.test = test
|
||||
|
||||
def __len__(self):
|
||||
return len(self.paths)
|
||||
|
||||
def __getitem__(self, item):
|
||||
img_path = os.path.join(self.root_path, self.paths[item])
|
||||
img = self.pil_loader(img_path)
|
||||
if self.test:
|
||||
img = self.totensor(img)
|
||||
label = self.labels[item]
|
||||
return img, label, self.paths[item]
|
||||
img_unswap = self.common_aug(img) if not self.common_aug is None else img # 原图数据
|
||||
|
||||
image_unswap_list = self.crop_image(img_unswap, self.swap_size)
|
||||
|
||||
swap_range = self.swap_size[0] * self.swap_size[1]
|
||||
swap_law1 = [(i-(swap_range//2))/swap_range for i in range(swap_range)]
|
||||
# 在训练的时候 self.train是true
|
||||
if self.train:
|
||||
img_swap = self.swap(img_unswap) # RCM机制处理后的照片
|
||||
image_swap_list = self.crop_image(img_swap, self.swap_size)
|
||||
unswap_stats = [sum(ImageStat.Stat(im).mean) for im in image_unswap_list]
|
||||
swap_stats = [sum(ImageStat.Stat(im).mean) for im in image_swap_list]
|
||||
swap_law2 = []
|
||||
for swap_im in swap_stats:
|
||||
distance = [abs(swap_im - unswap_im) for unswap_im in unswap_stats]
|
||||
index = distance.index(min(distance))
|
||||
swap_law2.append((index-(swap_range//2))/swap_range)
|
||||
img_swap = self.totensor(img_swap)
|
||||
label = self.labels[item]
|
||||
if self.use_cls_mul:
|
||||
label_swap = label + self.numcls
|
||||
if self.use_cls_2:
|
||||
label_swap = -1
|
||||
img_unswap = self.totensor(img_unswap)
|
||||
return img_unswap, img_swap, label, label_swap, swap_law1, swap_law2, self.paths[item]
|
||||
else:
|
||||
label = self.labels[item]
|
||||
swap_law2 = [(i-(swap_range//2))/swap_range for i in range(swap_range)]
|
||||
label_swap = label
|
||||
img_unswap = self.totensor(img_unswap)
|
||||
return img_unswap, label, label_swap, swap_law1, swap_law2, self.paths[item]
|
||||
#inputs, labels, labels_swap, swap_law, img_names
|
||||
def pil_loader(self,imgpath):
|
||||
with open(imgpath, 'rb') as f:
|
||||
with Image.open(f) as img:
|
||||
return img.convert('RGB')
|
||||
|
||||
def crop_image(self, image, cropnum):
|
||||
width, high = image.size
|
||||
crop_x = [int((width / cropnum[0]) * i) for i in range(cropnum[0] + 1)]
|
||||
crop_y = [int((high / cropnum[1]) * i) for i in range(cropnum[1] + 1)]
|
||||
im_list = []
|
||||
for j in range(len(crop_y) - 1):
|
||||
for i in range(len(crop_x) - 1):
|
||||
im_list.append(image.crop((crop_x[i], crop_y[j], min(crop_x[i + 1], width), min(crop_y[j + 1], high))))
|
||||
return im_list
|
||||
|
||||
|
||||
def get_weighted_sampler(self):
|
||||
img_nums = len(self.labels)
|
||||
weights = [self.labels.count(x) for x in range(self.numcls)]
|
||||
return torch.utils.data.sampler.WeightedRandomSampler(weights, num_samples=img_nums)
|
||||
|
||||
|
||||
def collate_fn4train(batch):
|
||||
imgs = []
|
||||
label = []
|
||||
label_swap = []
|
||||
law_swap = []
|
||||
img_name = []
|
||||
for sample in batch:
|
||||
imgs.append(sample[0]) # 原始图片
|
||||
imgs.append(sample[1]) # RCM混淆机制打乱后的图片
|
||||
label.append(sample[2])# 类别标签
|
||||
label.append(sample[2])
|
||||
# 给每张图片贴上标签以区分是否被破坏过
|
||||
if sample[3] == -1:
|
||||
label_swap.append(1)
|
||||
label_swap.append(0)
|
||||
else:
|
||||
label_swap.append(sample[2])
|
||||
label_swap.append(sample[3])
|
||||
law_swap.append(sample[4]) # 未打乱时每个小块的位置
|
||||
law_swap.append(sample[5]) # 打乱后每个小块的的位置
|
||||
img_name.append(sample[-1])# 图片名字
|
||||
|
||||
return torch.stack(imgs, 0), label, label_swap, law_swap, img_name # 张量0维度扩展
|
||||
|
||||
def collate_fn4val(batch):
|
||||
imgs = []
|
||||
label = []
|
||||
label_swap = []
|
||||
law_swap = []
|
||||
img_name = []
|
||||
for sample in batch:
|
||||
imgs.append(sample[0])
|
||||
label.append(sample[1])
|
||||
if sample[3] == -1:
|
||||
label_swap.append(1)
|
||||
else:
|
||||
label_swap.append(sample[2])
|
||||
law_swap.append(sample[3])
|
||||
img_name.append(sample[-1])
|
||||
return torch.stack(imgs, 0), label, label_swap, law_swap, img_name
|
||||
|
||||
def collate_fn4backbone(batch):
|
||||
imgs = []
|
||||
label = []
|
||||
img_name = []
|
||||
for sample in batch:
|
||||
imgs.append(sample[0])
|
||||
if len(sample) == 7:
|
||||
label.append(sample[2])
|
||||
else:
|
||||
label.append(sample[1])
|
||||
img_name.append(sample[-1])
|
||||
return torch.stack(imgs, 0), label, img_name
|
||||
|
||||
|
||||
def collate_fn4test(batch):
|
||||
imgs = []
|
||||
label = []
|
||||
img_name = []
|
||||
for sample in batch:
|
||||
imgs.append(sample[0])
|
||||
label.append(sample[1])
|
||||
img_name.append(sample[-1])
|
||||
return torch.stack(imgs, 0), label, img_name
|
||||
@@ -0,0 +1,81 @@
|
||||
#coding=utf8
|
||||
from __future__ import print_function, division
|
||||
import os,time,datetime
|
||||
import numpy as np
|
||||
import datetime
|
||||
from math import ceil
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
from torch.autograd import Variable
|
||||
import torch.nn.functional as F
|
||||
|
||||
from util.utils import LossRecord
|
||||
|
||||
import pdb
|
||||
|
||||
def dt():
|
||||
return datetime.datetime.now().strftime("%Y-%m-%d-%H_%M_%S")
|
||||
|
||||
def eval_turn(Config, model, data_loader, val_version, epoch_num, log_file):
|
||||
|
||||
model.train(False)
|
||||
|
||||
val_corrects1 = 0
|
||||
val_corrects2 = 0
|
||||
val_corrects3 = 0
|
||||
val_size = data_loader.__len__()
|
||||
item_count = data_loader.total_item_len
|
||||
t0 = time.time()
|
||||
get_l1_loss = nn.L1Loss()
|
||||
get_ce_loss = nn.CrossEntropyLoss()
|
||||
|
||||
val_batch_size = data_loader.batch_size
|
||||
val_epoch_step = data_loader.__len__()
|
||||
num_cls = data_loader.num_cls
|
||||
|
||||
val_loss_recorder = LossRecord(val_batch_size)
|
||||
val_celoss_recorder = LossRecord(val_batch_size)
|
||||
print('evaluating %s ...'%val_version, flush=True)
|
||||
with torch.no_grad():
|
||||
for batch_cnt_val, data_val in enumerate(data_loader):
|
||||
inputs = Variable(data_val[0].cuda())
|
||||
labels = Variable(torch.from_numpy(np.array(data_val[1])).long().cuda())
|
||||
outputs = model(inputs)
|
||||
loss = 0
|
||||
# 前向预测的时候只做分类
|
||||
ce_loss = get_ce_loss(outputs[0], labels).item() # 获取tensor的值不包括类型
|
||||
loss += ce_loss
|
||||
|
||||
val_loss_recorder.update(loss)
|
||||
val_celoss_recorder.update(ce_loss)
|
||||
|
||||
if Config.use_dcl and Config.cls_2xmul:
|
||||
outputs_pred = outputs[0] + outputs[1][:,0:num_cls] + outputs[1][:,num_cls:2*num_cls]
|
||||
else:
|
||||
outputs_pred = outputs[0]
|
||||
top3_val, top3_pos = torch.topk(outputs_pred, 3) # 获取某一维度上 的前k个最大值(张量 k的值)
|
||||
|
||||
print('{:s} eval_batch: {:-6d} / {:d} loss: {:8.4f}'.format(val_version, batch_cnt_val, val_epoch_step, loss), flush=True)
|
||||
|
||||
batch_corrects1 = torch.sum((top3_pos[:, 0] == labels)).data.item() # .data返回的是tensor
|
||||
val_corrects1 += batch_corrects1
|
||||
batch_corrects2 = torch.sum((top3_pos[:, 1] == labels)).data.item()
|
||||
val_corrects2 += (batch_corrects2 + batch_corrects1)
|
||||
batch_corrects3 = torch.sum((top3_pos[:, 2] == labels)).data.item()
|
||||
val_corrects3 += (batch_corrects3 + batch_corrects2 + batch_corrects1)
|
||||
|
||||
val_acc1 = val_corrects1 / item_count
|
||||
val_acc2 = val_corrects2 / item_count
|
||||
val_acc3 = val_corrects3 / item_count
|
||||
|
||||
log_file.write(val_version + '\t' +str(val_loss_recorder.get_val())+'\t' + str(val_celoss_recorder.get_val()) + '\t' + str(val_acc1) + '\t' + str(val_acc3) + '\n')
|
||||
|
||||
t1 = time.time()
|
||||
since = t1-t0
|
||||
print('--'*30, flush=True)
|
||||
print('% 3d %s %s %s-loss: %.4f ||%s-acc@1: %.4f %s-acc@2: %.4f %s-acc@3: %.4f ||time: %d' % (epoch_num, val_version, dt(), val_version, val_loss_recorder.get_val(init=True), val_version, val_acc1,val_version, val_acc2, val_version, val_acc3, since), flush=True)
|
||||
print('--' * 30, flush=True)
|
||||
|
||||
return val_acc1, val_acc2, val_acc3
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import os
|
||||
import math
|
||||
import numpy as np
|
||||
import cv2
|
||||
import datetime
|
||||
|
||||
import torch
|
||||
from torchvision.utils import save_image, make_grid
|
||||
|
||||
import pdb
|
||||
|
||||
def dt():
|
||||
return datetime.datetime.now().strftime("%Y-%m-%d-%H_%M_%S")
|
||||
|
||||
def set_text(text, img):
|
||||
font = cv2.FONT_HERSHEY_SIMPLEX
|
||||
if isinstance(text, str):
|
||||
cont = text
|
||||
cv2.putText(img, cont, (20, 50), font, 0.5, (0, 0, 255), 1, cv2.LINE_AA)
|
||||
if isinstance(text, float):
|
||||
cont = '%.4f'%text
|
||||
cv2.putText(img, cont, (20, 50), font, 0.5, (0, 0, 255), 1, cv2.LINE_AA)
|
||||
if isinstance(text, list):
|
||||
for count in range(len(img)):
|
||||
cv2.putText(img[count], text[count], (20, 50), font, 0.5, (0, 0, 255), 1, cv2.LINE_AA)
|
||||
return img
|
||||
|
||||
def save_multi_img(img_list, text_list, grid_size=[5,5], sub_size=200, save_dir='./', save_name=None):
|
||||
if len(img_list) > grid_size[0]*grid_size[1]:
|
||||
merge_height = math.ceil(len(img_list) / grid_size[0]) * sub_size
|
||||
else:
|
||||
merge_height = grid_size[1]*sub_size
|
||||
merged_img = np.zeros((merge_height, grid_size[0]*sub_size, 3))
|
||||
|
||||
if isinstance(img_list[0], str):
|
||||
img_name_list = img_list
|
||||
img_list = []
|
||||
for img_name in img_name_list:
|
||||
img_list.append(cv2.imread(img_name))
|
||||
|
||||
img_counter = 0
|
||||
for img, txt in zip(img_list, text_list):
|
||||
img = cv2.resize(img, (sub_size, sub_size))
|
||||
img = set_text(txt, img)
|
||||
pos = [img_counter // grid_size[1], img_counter % grid_size[1]]
|
||||
sub_pos = [pos[0]*sub_size, (pos[0]+1)*sub_size,
|
||||
pos[1]*sub_size, (pos[1]+1)*sub_size]
|
||||
merged_img[sub_pos[0]:sub_pos[1], sub_pos[2]:sub_pos[3], :] = img
|
||||
img_counter += 1
|
||||
|
||||
if save_name is None:
|
||||
img_save_path = os.path.join(save_dir, dt()+'.png')
|
||||
else:
|
||||
img_save_path = os.path.join(save_dir, save_name+'.png')
|
||||
cv2.imwrite(img_save_path, merged_img)
|
||||
print('saved img in %s ...'%img_save_path)
|
||||
|
||||
|
||||
def cls_base_acc(result_gather):
|
||||
top1_acc = {}
|
||||
top3_acc = {}
|
||||
cls_count = {}
|
||||
for img_item in result_gather.keys():
|
||||
acc_case = result_gather[img_item]
|
||||
|
||||
if acc_case['label'] in cls_count:
|
||||
cls_count[acc_case['label']] += 1
|
||||
if acc_case['top1_cat'] == acc_case['label']:
|
||||
top1_acc[acc_case['label']] += 1
|
||||
if acc_case['label'] in [acc_case['top1_cat'], acc_case['top2_cat'], acc_case['top3_cat']]:
|
||||
top3_acc[acc_case['label']] += 1
|
||||
else:
|
||||
cls_count[acc_case['label']] = 1
|
||||
if acc_case['top1_cat'] == acc_case['label']:
|
||||
top1_acc[acc_case['label']] = 1
|
||||
else:
|
||||
top1_acc[acc_case['label']] = 0
|
||||
|
||||
if acc_case['label'] in [acc_case['top1_cat'], acc_case['top2_cat'], acc_case['top3_cat']]:
|
||||
top3_acc[acc_case['label']] = 1
|
||||
else:
|
||||
top3_acc[acc_case['label']] = 0
|
||||
|
||||
for label_item in cls_count:
|
||||
top1_acc[label_item] /= max(1.0*cls_count[label_item], 0.001)
|
||||
top3_acc[label_item] /= max(1.0*cls_count[label_item], 0.001)
|
||||
|
||||
print('top1_acc:', top1_acc)
|
||||
print('top3_acc:', top3_acc)
|
||||
print('cls_count', cls_count)
|
||||
|
||||
return top1_acc, top3_acc, cls_count
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
#coding=utf8
|
||||
from __future__ import print_function, division
|
||||
|
||||
import os,time,datetime
|
||||
import numpy as np
|
||||
from math import ceil
|
||||
import datetime
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
from torch.autograd import Variable
|
||||
#from torchvision.utils import make_grid, save_image
|
||||
|
||||
from util.utils import LossRecord, clip_gradient
|
||||
from models.focal_loss import FocalLoss
|
||||
from util.eval_model import eval_turn
|
||||
from util.Asoftmax_loss import AngleLoss
|
||||
|
||||
import pdb
|
||||
|
||||
def dt():
|
||||
return datetime.datetime.now().strftime("%Y-%m-%d-%H_%M_%S")
|
||||
|
||||
|
||||
def train(Config,
|
||||
model,
|
||||
epoch_num,
|
||||
start_epoch,
|
||||
optimizer,
|
||||
exp_lr_scheduler,
|
||||
data_loader,
|
||||
save_dir,
|
||||
data_size=448,
|
||||
savepoint=500,
|
||||
checkpoint=1000
|
||||
):
|
||||
# savepoint: save without evalution
|
||||
# checkpoint: save with evaluation
|
||||
# print(epoch_num, start_epoch) # 360 0
|
||||
step = 0
|
||||
eval_train_flag = False
|
||||
rec_loss = []
|
||||
checkpoint_list = []
|
||||
|
||||
train_batch_size = data_loader['train'].batch_size
|
||||
train_epoch_step = data_loader['train'].__len__()
|
||||
train_loss_recorder = LossRecord(train_batch_size)
|
||||
if savepoint > train_epoch_step:
|
||||
savepoint = 1*train_epoch_step
|
||||
checkpoint = savepoint
|
||||
|
||||
date_suffix = dt()
|
||||
log_file = open(os.path.join(Config.log_folder, 'formal_log_r50_dcl_%s_%s.log'%(str(data_size), date_suffix)), 'a')
|
||||
|
||||
add_loss = nn.L1Loss()
|
||||
get_ce_loss = nn.CrossEntropyLoss()
|
||||
get_focal_loss = FocalLoss()
|
||||
get_angle_loss = AngleLoss()
|
||||
|
||||
for epoch in range(start_epoch,epoch_num-1):
|
||||
exp_lr_scheduler.step(epoch)
|
||||
model.train(True)
|
||||
|
||||
save_grad = []
|
||||
step = 0
|
||||
for batch_cnt, data in enumerate(data_loader['train']):
|
||||
step += 1
|
||||
loss = 0
|
||||
model.train(True)
|
||||
if Config.use_backbone:
|
||||
inputs, labels, img_names = data
|
||||
|
||||
inputs = Variable(inputs.cuda())
|
||||
labels = Variable(torch.from_numpy(np.array(labels)).cuda())
|
||||
|
||||
if Config.use_dcl:
|
||||
inputs, labels, labels_swap, swap_law, img_names = data
|
||||
inputs = Variable(inputs.cuda())
|
||||
labels = Variable(torch.from_numpy(np.array(labels)).long().cuda())
|
||||
labels_swap = Variable(torch.from_numpy(np.array(labels_swap)).long().cuda())
|
||||
swap_law = Variable(torch.from_numpy(np.array(swap_law)).float().cuda())
|
||||
optimizer.zero_grad()
|
||||
# print(inputs.size())
|
||||
if inputs.size(0) < 2*train_batch_size:
|
||||
outputs = model(inputs, inputs[0:-1:2])
|
||||
else:
|
||||
outputs = model(inputs, None)
|
||||
if Config.use_focal_loss:
|
||||
ce_loss = get_focal_loss(outputs[0], labels)
|
||||
else:
|
||||
ce_loss = get_ce_loss(outputs[0], labels) #交叉熵损失函数 用来分类
|
||||
|
||||
# if Config.use_Asoftmax:
|
||||
# fetch_batch = labels.size(0)
|
||||
# if batch_cnt % (train_epoch_step // 5) == 0:
|
||||
# angle_loss = get_angle_loss(outputs[3], labels[0:fetch_batch:2], decay=0.9)
|
||||
# else:
|
||||
# angle_loss = get_angle_loss(outputs[3], labels[0:fetch_batch:2])
|
||||
# loss += angle_loss
|
||||
|
||||
loss += ce_loss
|
||||
alpha_ = 1
|
||||
beta_ = 1
|
||||
gamma_ = 0.01 if Config.dataset == 'STCAR' or Config.dataset == 'AIR' else 1
|
||||
if Config.use_dcl:
|
||||
swap_loss = get_ce_loss(outputs[1], labels_swap) * beta_ # 对抗学习
|
||||
loss += swap_loss
|
||||
law_loss = add_loss(outputs[2], swap_law) * gamma_ # 重建学习损失
|
||||
loss += law_loss
|
||||
|
||||
loss.backward()
|
||||
torch.cuda.synchronize() #统计时间
|
||||
|
||||
optimizer.step()
|
||||
torch.cuda.synchronize()
|
||||
|
||||
if Config.use_dcl:
|
||||
# 8d不足就右对齐 加上-左对齐
|
||||
print('epoch: {} step: {:-8d} / {:d} loss=ce_loss+swap_loss+law_loss: {:6.4f} = {:6.4f} + {:6.4f} + {:6.4f} '.format(epoch, step, train_epoch_step, loss.detach().item(), ce_loss.detach().item(), swap_loss.detach().item(), law_loss.detach().item()), flush=True)
|
||||
# print(savepoint)
|
||||
if Config.use_backbone:
|
||||
print('step: {:-8d} / {:d} loss=ce_loss+swap_loss+law_loss: {:6.4f} = {:6.4f} '.format(step, train_epoch_step, loss.detach().item(), ce_loss.detach().item()), flush=True)
|
||||
rec_loss.append(loss.detach().item())# 对loss进行叠加的时候一定要加detach,返回的是叶子节点,不然会进行计算图的叠加,
|
||||
|
||||
train_loss_recorder.update(loss.detach().item())
|
||||
# evaluation & save
|
||||
# checkpoint = 5000
|
||||
if step % checkpoint == 0:
|
||||
rec_loss = []
|
||||
print(32*'-', flush=True)
|
||||
print('step: {:d} / {:d} global_step: {:8.2f} train_epoch: {:04d} rec_train_loss: {:6.4f}'.format(step, train_epoch_step, 1.0*step/train_epoch_step, epoch, train_loss_recorder.get_val()), flush=True)
|
||||
print('current lr:%s' % exp_lr_scheduler.get_lr(), flush=True)
|
||||
if eval_train_flag:
|
||||
trainval_acc1, trainval_acc2, trainval_acc3 = eval_turn(Config, model, data_loader['trainval'], 'trainval', epoch, log_file)
|
||||
if abs(trainval_acc1 - trainval_acc3) < 0.01:
|
||||
eval_train_flag = False
|
||||
|
||||
val_acc1, val_acc2, val_acc3 = eval_turn(Config, model, data_loader['val'], 'val', epoch, log_file)
|
||||
|
||||
save_path = os.path.join(save_dir, 'weights_%d_%d_%.4f_%.4f.pth'%(epoch, batch_cnt, val_acc1, val_acc3))
|
||||
torch.cuda.synchronize()
|
||||
torch.save(model.state_dict(), save_path)
|
||||
print('saved model to %s' % (save_path), flush=True)
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
# save only
|
||||
# elif step % savepoint == 0:
|
||||
# print('step')
|
||||
# train_loss_recorder.update(rec_loss)
|
||||
# rec_loss = []
|
||||
# save_path = os.path.join(save_dir, 'savepoint_weights-%d-%s.pth'%(step, dt()))
|
||||
#
|
||||
# checkpoint_list.append(save_path)
|
||||
# if len(checkpoint_list) == 6:
|
||||
# os.remove(checkpoint_list[0])
|
||||
# del checkpoint_list[0]
|
||||
# torch.save(model.state_dict(), save_path)
|
||||
# torch.cuda.empty_cache()
|
||||
|
||||
|
||||
log_file.close()
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
import pdb
|
||||
|
||||
|
||||
class LossRecord(object):
|
||||
def __init__(self, batch_size):
|
||||
self.rec_loss = 0
|
||||
self.count = 0
|
||||
self.batch_size = batch_size
|
||||
|
||||
def update(self, loss):
|
||||
if isinstance(loss, list):
|
||||
avg_loss = sum(loss)
|
||||
avg_loss /= (len(loss)*self.batch_size)
|
||||
self.rec_loss += avg_loss
|
||||
self.count += 1
|
||||
if isinstance(loss, float):
|
||||
self.rec_loss += loss/self.batch_size
|
||||
self.count += 1
|
||||
|
||||
def get_val(self, init=False):
|
||||
pop_loss = self.rec_loss / self.count
|
||||
if init:
|
||||
self.rec_loss = 0
|
||||
self.count = 0
|
||||
return pop_loss
|
||||
|
||||
|
||||
def weights_normal_init(model, dev=0.01):
|
||||
if isinstance(model, list):
|
||||
for m in model:
|
||||
weights_normal_init(m, dev)
|
||||
else:
|
||||
for m in model.modules():
|
||||
if isinstance(m, nn.Conv2d):
|
||||
m.weight.data.normal_(0.0, dev)
|
||||
elif isinstance(m, nn.Linear):
|
||||
m.weight.data.normal_(0.0, dev)
|
||||
|
||||
|
||||
def clip_gradient(model, clip_norm):
|
||||
"""Computes a gradient clipping coefficient based on gradient norm."""
|
||||
totalnorm = 0
|
||||
for p in model.parameters():
|
||||
if p.requires_grad:
|
||||
modulenorm = p.grad.data.norm()
|
||||
totalnorm += modulenorm ** 2
|
||||
totalnorm = torch.sqrt(totalnorm).item()
|
||||
norm = (clip_norm / max(totalnorm, clip_norm))
|
||||
for p in model.parameters():
|
||||
if p.requires_grad:
|
||||
p.grad.mul_(norm)
|
||||
|
||||
|
||||
def Linear(in_features, out_features, bias=True):
|
||||
"""Weight-normalized Linear layer (input: N x T x C)"""
|
||||
m = nn.Linear(in_features, out_features, bias=bias)
|
||||
m.weight.data.uniform_(-0.1, 0.1)
|
||||
if bias:
|
||||
m.bias.data.uniform_(-0.1, 0.1)
|
||||
return m
|
||||
|
||||
|
||||
class convolution(nn.Module):
|
||||
def __init__(self, k, inp_dim, out_dim, stride=1, with_bn=True):
|
||||
super(convolution, self).__init__()
|
||||
|
||||
pad = (k - 1) // 2
|
||||
self.conv = nn.Conv2d(inp_dim, out_dim, (k, k), padding=(pad, pad), stride=(stride, stride), bias=not with_bn)
|
||||
self.bn = nn.BatchNorm2d(out_dim) if with_bn else nn.Sequential()
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
|
||||
def forward(self, x):
|
||||
conv = self.conv(x)
|
||||
bn = self.bn(conv)
|
||||
relu = self.relu(bn)
|
||||
return relu
|
||||
|
||||
class fully_connected(nn.Module):
|
||||
def __init__(self, inp_dim, out_dim, with_bn=True):
|
||||
super(fully_connected, self).__init__()
|
||||
self.with_bn = with_bn
|
||||
|
||||
self.linear = nn.Linear(inp_dim, out_dim)
|
||||
if self.with_bn:
|
||||
self.bn = nn.BatchNorm1d(out_dim)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
|
||||
def forward(self, x):
|
||||
linear = self.linear(x)
|
||||
bn = self.bn(linear) if self.with_bn else linear
|
||||
relu = self.relu(bn)
|
||||
return relu
|
||||
|
||||
class residual(nn.Module):
|
||||
def __init__(self, k, inp_dim, out_dim, stride=1, with_bn=True):
|
||||
super(residual, self).__init__()
|
||||
|
||||
self.conv1 = nn.Conv2d(inp_dim, out_dim, (3, 3), padding=(1, 1), stride=(stride, stride), bias=False)
|
||||
self.bn1 = nn.BatchNorm2d(out_dim)
|
||||
self.relu1 = nn.ReLU(inplace=True)
|
||||
|
||||
self.conv2 = nn.Conv2d(out_dim, out_dim, (3, 3), padding=(1, 1), bias=False)
|
||||
self.bn2 = nn.BatchNorm2d(out_dim)
|
||||
|
||||
self.skip = nn.Sequential(
|
||||
nn.Conv2d(inp_dim, out_dim, (1, 1), stride=(stride, stride), bias=False),
|
||||
nn.BatchNorm2d(out_dim)
|
||||
) if stride != 1 or inp_dim != out_dim else nn.Sequential()
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
|
||||
def forward(self, x):
|
||||
conv1 = self.conv1(x)
|
||||
bn1 = self.bn1(conv1)
|
||||
relu1 = self.relu1(bn1)
|
||||
|
||||
conv2 = self.conv2(relu1)
|
||||
bn2 = self.bn2(conv2)
|
||||
|
||||
skip = self.skip(x)
|
||||
return self.relu(bn2 + skip)
|
||||
Reference in new issue
Block a user