mirror of
https://github.com/storytold/storyteller-ml.git
synced 2026-10-18 07:41:03 +00:00
Add SadTalker @ ae0b26359d44c9379450a8e02a38b45eeec15549
MIT license Commit: https://github.com/OpenTalker/SadTalker commit ae0b26359d44c9379450a8e02a38b45eeec15549 (HEAD -> main, origin/main, origin/HEAD) Author: shadowcun <shadowcun@tencent.com> Date: Mon Jul 3 13:48:35 2023 +0800 fixed bug: hacking facexlib of FAN
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
"""This package includes all the modules related to data loading and preprocessing
|
||||
|
||||
To add a custom dataset class called 'dummy', you need to add a file called 'dummy_dataset.py' and define a subclass 'DummyDataset' inherited from BaseDataset.
|
||||
You need to implement four functions:
|
||||
-- <__init__>: initialize the class, first call BaseDataset.__init__(self, opt).
|
||||
-- <__len__>: return the size of dataset.
|
||||
-- <__getitem__>: get a data point from data loader.
|
||||
-- <modify_commandline_options>: (optionally) add dataset-specific options and set default options.
|
||||
|
||||
Now you can use the dataset class by specifying flag '--dataset_mode dummy'.
|
||||
See our template dataset class 'template_dataset.py' for more details.
|
||||
"""
|
||||
import numpy as np
|
||||
import importlib
|
||||
import torch.utils.data
|
||||
from face3d.data.base_dataset import BaseDataset
|
||||
|
||||
|
||||
def find_dataset_using_name(dataset_name):
|
||||
"""Import the module "data/[dataset_name]_dataset.py".
|
||||
|
||||
In the file, the class called DatasetNameDataset() will
|
||||
be instantiated. It has to be a subclass of BaseDataset,
|
||||
and it is case-insensitive.
|
||||
"""
|
||||
dataset_filename = "data." + dataset_name + "_dataset"
|
||||
datasetlib = importlib.import_module(dataset_filename)
|
||||
|
||||
dataset = None
|
||||
target_dataset_name = dataset_name.replace('_', '') + 'dataset'
|
||||
for name, cls in datasetlib.__dict__.items():
|
||||
if name.lower() == target_dataset_name.lower() \
|
||||
and issubclass(cls, BaseDataset):
|
||||
dataset = cls
|
||||
|
||||
if dataset is None:
|
||||
raise NotImplementedError("In %s.py, there should be a subclass of BaseDataset with class name that matches %s in lowercase." % (dataset_filename, target_dataset_name))
|
||||
|
||||
return dataset
|
||||
|
||||
|
||||
def get_option_setter(dataset_name):
|
||||
"""Return the static method <modify_commandline_options> of the dataset class."""
|
||||
dataset_class = find_dataset_using_name(dataset_name)
|
||||
return dataset_class.modify_commandline_options
|
||||
|
||||
|
||||
def create_dataset(opt, rank=0):
|
||||
"""Create a dataset given the option.
|
||||
|
||||
This function wraps the class CustomDatasetDataLoader.
|
||||
This is the main interface between this package and 'train.py'/'test.py'
|
||||
|
||||
Example:
|
||||
>>> from data import create_dataset
|
||||
>>> dataset = create_dataset(opt)
|
||||
"""
|
||||
data_loader = CustomDatasetDataLoader(opt, rank=rank)
|
||||
dataset = data_loader.load_data()
|
||||
return dataset
|
||||
|
||||
class CustomDatasetDataLoader():
|
||||
"""Wrapper class of Dataset class that performs multi-threaded data loading"""
|
||||
|
||||
def __init__(self, opt, rank=0):
|
||||
"""Initialize this class
|
||||
|
||||
Step 1: create a dataset instance given the name [dataset_mode]
|
||||
Step 2: create a multi-threaded data loader.
|
||||
"""
|
||||
self.opt = opt
|
||||
dataset_class = find_dataset_using_name(opt.dataset_mode)
|
||||
self.dataset = dataset_class(opt)
|
||||
self.sampler = None
|
||||
print("rank %d %s dataset [%s] was created" % (rank, self.dataset.name, type(self.dataset).__name__))
|
||||
if opt.use_ddp and opt.isTrain:
|
||||
world_size = opt.world_size
|
||||
self.sampler = torch.utils.data.distributed.DistributedSampler(
|
||||
self.dataset,
|
||||
num_replicas=world_size,
|
||||
rank=rank,
|
||||
shuffle=not opt.serial_batches
|
||||
)
|
||||
self.dataloader = torch.utils.data.DataLoader(
|
||||
self.dataset,
|
||||
sampler=self.sampler,
|
||||
num_workers=int(opt.num_threads / world_size),
|
||||
batch_size=int(opt.batch_size / world_size),
|
||||
drop_last=True)
|
||||
else:
|
||||
self.dataloader = torch.utils.data.DataLoader(
|
||||
self.dataset,
|
||||
batch_size=opt.batch_size,
|
||||
shuffle=(not opt.serial_batches) and opt.isTrain,
|
||||
num_workers=int(opt.num_threads),
|
||||
drop_last=True
|
||||
)
|
||||
|
||||
def set_epoch(self, epoch):
|
||||
self.dataset.current_epoch = epoch
|
||||
if self.sampler is not None:
|
||||
self.sampler.set_epoch(epoch)
|
||||
|
||||
def load_data(self):
|
||||
return self
|
||||
|
||||
def __len__(self):
|
||||
"""Return the number of data in the dataset"""
|
||||
return min(len(self.dataset), self.opt.max_dataset_size)
|
||||
|
||||
def __iter__(self):
|
||||
"""Return a batch of data"""
|
||||
for i, data in enumerate(self.dataloader):
|
||||
if i * self.opt.batch_size >= self.opt.max_dataset_size:
|
||||
break
|
||||
yield data
|
||||
@@ -0,0 +1,125 @@
|
||||
"""This module implements an abstract base class (ABC) 'BaseDataset' for datasets.
|
||||
|
||||
It also includes common transformation functions (e.g., get_transform, __scale_width), which can be later used in subclasses.
|
||||
"""
|
||||
import random
|
||||
import numpy as np
|
||||
import torch.utils.data as data
|
||||
from PIL import Image
|
||||
import torchvision.transforms as transforms
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class BaseDataset(data.Dataset, ABC):
|
||||
"""This class is an abstract base class (ABC) for datasets.
|
||||
|
||||
To create a subclass, you need to implement the following four functions:
|
||||
-- <__init__>: initialize the class, first call BaseDataset.__init__(self, opt).
|
||||
-- <__len__>: return the size of dataset.
|
||||
-- <__getitem__>: get a data point.
|
||||
-- <modify_commandline_options>: (optionally) add dataset-specific options and set default options.
|
||||
"""
|
||||
|
||||
def __init__(self, opt):
|
||||
"""Initialize the class; save the options in the class
|
||||
|
||||
Parameters:
|
||||
opt (Option class)-- stores all the experiment flags; needs to be a subclass of BaseOptions
|
||||
"""
|
||||
self.opt = opt
|
||||
# self.root = opt.dataroot
|
||||
self.current_epoch = 0
|
||||
|
||||
@staticmethod
|
||||
def modify_commandline_options(parser, is_train):
|
||||
"""Add new dataset-specific options, and rewrite default values for existing options.
|
||||
|
||||
Parameters:
|
||||
parser -- original option parser
|
||||
is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options.
|
||||
|
||||
Returns:
|
||||
the modified parser.
|
||||
"""
|
||||
return parser
|
||||
|
||||
@abstractmethod
|
||||
def __len__(self):
|
||||
"""Return the total number of images in the dataset."""
|
||||
return 0
|
||||
|
||||
@abstractmethod
|
||||
def __getitem__(self, index):
|
||||
"""Return a data point and its metadata information.
|
||||
|
||||
Parameters:
|
||||
index - - a random integer for data indexing
|
||||
|
||||
Returns:
|
||||
a dictionary of data with their names. It ususally contains the data itself and its metadata information.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def get_transform(grayscale=False):
|
||||
transform_list = []
|
||||
if grayscale:
|
||||
transform_list.append(transforms.Grayscale(1))
|
||||
transform_list += [transforms.ToTensor()]
|
||||
return transforms.Compose(transform_list)
|
||||
|
||||
def get_affine_mat(opt, size):
|
||||
shift_x, shift_y, scale, rot_angle, flip = 0., 0., 1., 0., False
|
||||
w, h = size
|
||||
|
||||
if 'shift' in opt.preprocess:
|
||||
shift_pixs = int(opt.shift_pixs)
|
||||
shift_x = random.randint(-shift_pixs, shift_pixs)
|
||||
shift_y = random.randint(-shift_pixs, shift_pixs)
|
||||
if 'scale' in opt.preprocess:
|
||||
scale = 1 + opt.scale_delta * (2 * random.random() - 1)
|
||||
if 'rot' in opt.preprocess:
|
||||
rot_angle = opt.rot_angle * (2 * random.random() - 1)
|
||||
rot_rad = -rot_angle * np.pi/180
|
||||
if 'flip' in opt.preprocess:
|
||||
flip = random.random() > 0.5
|
||||
|
||||
shift_to_origin = np.array([1, 0, -w//2, 0, 1, -h//2, 0, 0, 1]).reshape([3, 3])
|
||||
flip_mat = np.array([-1 if flip else 1, 0, 0, 0, 1, 0, 0, 0, 1]).reshape([3, 3])
|
||||
shift_mat = np.array([1, 0, shift_x, 0, 1, shift_y, 0, 0, 1]).reshape([3, 3])
|
||||
rot_mat = np.array([np.cos(rot_rad), np.sin(rot_rad), 0, -np.sin(rot_rad), np.cos(rot_rad), 0, 0, 0, 1]).reshape([3, 3])
|
||||
scale_mat = np.array([scale, 0, 0, 0, scale, 0, 0, 0, 1]).reshape([3, 3])
|
||||
shift_to_center = np.array([1, 0, w//2, 0, 1, h//2, 0, 0, 1]).reshape([3, 3])
|
||||
|
||||
affine = shift_to_center @ scale_mat @ rot_mat @ shift_mat @ flip_mat @ shift_to_origin
|
||||
affine_inv = np.linalg.inv(affine)
|
||||
return affine, affine_inv, flip
|
||||
|
||||
def apply_img_affine(img, affine_inv, method=Image.BICUBIC):
|
||||
return img.transform(img.size, Image.AFFINE, data=affine_inv.flatten()[:6], resample=Image.BICUBIC)
|
||||
|
||||
def apply_lm_affine(landmark, affine, flip, size):
|
||||
_, h = size
|
||||
lm = landmark.copy()
|
||||
lm[:, 1] = h - 1 - lm[:, 1]
|
||||
lm = np.concatenate((lm, np.ones([lm.shape[0], 1])), -1)
|
||||
lm = lm @ np.transpose(affine)
|
||||
lm[:, :2] = lm[:, :2] / lm[:, 2:]
|
||||
lm = lm[:, :2]
|
||||
lm[:, 1] = h - 1 - lm[:, 1]
|
||||
if flip:
|
||||
lm_ = lm.copy()
|
||||
lm_[:17] = lm[16::-1]
|
||||
lm_[17:22] = lm[26:21:-1]
|
||||
lm_[22:27] = lm[21:16:-1]
|
||||
lm_[31:36] = lm[35:30:-1]
|
||||
lm_[36:40] = lm[45:41:-1]
|
||||
lm_[40:42] = lm[47:45:-1]
|
||||
lm_[42:46] = lm[39:35:-1]
|
||||
lm_[46:48] = lm[41:39:-1]
|
||||
lm_[48:55] = lm[54:47:-1]
|
||||
lm_[55:60] = lm[59:54:-1]
|
||||
lm_[60:65] = lm[64:59:-1]
|
||||
lm_[65:68] = lm[67:64:-1]
|
||||
lm = lm_
|
||||
return lm
|
||||
@@ -0,0 +1,125 @@
|
||||
"""This script defines the custom dataset for Deep3DFaceRecon_pytorch
|
||||
"""
|
||||
|
||||
import os.path
|
||||
from data.base_dataset import BaseDataset, get_transform, get_affine_mat, apply_img_affine, apply_lm_affine
|
||||
from data.image_folder import make_dataset
|
||||
from PIL import Image
|
||||
import random
|
||||
import util.util as util
|
||||
import numpy as np
|
||||
import json
|
||||
import torch
|
||||
from scipy.io import loadmat, savemat
|
||||
import pickle
|
||||
from util.preprocess import align_img, estimate_norm
|
||||
from util.load_mats import load_lm3d
|
||||
|
||||
|
||||
def default_flist_reader(flist):
|
||||
"""
|
||||
flist format: impath label\nimpath label\n ...(same to caffe's filelist)
|
||||
"""
|
||||
imlist = []
|
||||
with open(flist, 'r') as rf:
|
||||
for line in rf.readlines():
|
||||
impath = line.strip()
|
||||
imlist.append(impath)
|
||||
|
||||
return imlist
|
||||
|
||||
def jason_flist_reader(flist):
|
||||
with open(flist, 'r') as fp:
|
||||
info = json.load(fp)
|
||||
return info
|
||||
|
||||
def parse_label(label):
|
||||
return torch.tensor(np.array(label).astype(np.float32))
|
||||
|
||||
|
||||
class FlistDataset(BaseDataset):
|
||||
"""
|
||||
It requires one directories to host training images '/path/to/data/train'
|
||||
You can train the model with the dataset flag '--dataroot /path/to/data'.
|
||||
"""
|
||||
|
||||
def __init__(self, opt):
|
||||
"""Initialize this dataset class.
|
||||
|
||||
Parameters:
|
||||
opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions
|
||||
"""
|
||||
BaseDataset.__init__(self, opt)
|
||||
|
||||
self.lm3d_std = load_lm3d(opt.bfm_folder)
|
||||
|
||||
msk_names = default_flist_reader(opt.flist)
|
||||
self.msk_paths = [os.path.join(opt.data_root, i) for i in msk_names]
|
||||
|
||||
self.size = len(self.msk_paths)
|
||||
self.opt = opt
|
||||
|
||||
self.name = 'train' if opt.isTrain else 'val'
|
||||
if '_' in opt.flist:
|
||||
self.name += '_' + opt.flist.split(os.sep)[-1].split('_')[0]
|
||||
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Return a data point and its metadata information.
|
||||
|
||||
Parameters:
|
||||
index (int) -- a random integer for data indexing
|
||||
|
||||
Returns a dictionary that contains A, B, A_paths and B_paths
|
||||
img (tensor) -- an image in the input domain
|
||||
msk (tensor) -- its corresponding attention mask
|
||||
lm (tensor) -- its corresponding 3d landmarks
|
||||
im_paths (str) -- image paths
|
||||
aug_flag (bool) -- a flag used to tell whether its raw or augmented
|
||||
"""
|
||||
msk_path = self.msk_paths[index % self.size] # make sure index is within then range
|
||||
img_path = msk_path.replace('mask/', '')
|
||||
lm_path = '.'.join(msk_path.replace('mask', 'landmarks').split('.')[:-1]) + '.txt'
|
||||
|
||||
raw_img = Image.open(img_path).convert('RGB')
|
||||
raw_msk = Image.open(msk_path).convert('RGB')
|
||||
raw_lm = np.loadtxt(lm_path).astype(np.float32)
|
||||
|
||||
_, img, lm, msk = align_img(raw_img, raw_lm, self.lm3d_std, raw_msk)
|
||||
|
||||
aug_flag = self.opt.use_aug and self.opt.isTrain
|
||||
if aug_flag:
|
||||
img, lm, msk = self._augmentation(img, lm, self.opt, msk)
|
||||
|
||||
_, H = img.size
|
||||
M = estimate_norm(lm, H)
|
||||
transform = get_transform()
|
||||
img_tensor = transform(img)
|
||||
msk_tensor = transform(msk)[:1, ...]
|
||||
lm_tensor = parse_label(lm)
|
||||
M_tensor = parse_label(M)
|
||||
|
||||
|
||||
return {'imgs': img_tensor,
|
||||
'lms': lm_tensor,
|
||||
'msks': msk_tensor,
|
||||
'M': M_tensor,
|
||||
'im_paths': img_path,
|
||||
'aug_flag': aug_flag,
|
||||
'dataset': self.name}
|
||||
|
||||
def _augmentation(self, img, lm, opt, msk=None):
|
||||
affine, affine_inv, flip = get_affine_mat(opt, img.size)
|
||||
img = apply_img_affine(img, affine_inv)
|
||||
lm = apply_lm_affine(lm, affine, flip, img.size)
|
||||
if msk is not None:
|
||||
msk = apply_img_affine(msk, affine_inv, method=Image.BILINEAR)
|
||||
return img, lm, msk
|
||||
|
||||
|
||||
|
||||
|
||||
def __len__(self):
|
||||
"""Return the total number of images in the dataset.
|
||||
"""
|
||||
return self.size
|
||||
@@ -0,0 +1,66 @@
|
||||
"""A modified image folder class
|
||||
|
||||
We modify the official PyTorch image folder (https://github.com/pytorch/vision/blob/master/torchvision/datasets/folder.py)
|
||||
so that this class can load images from both current directory and its subdirectories.
|
||||
"""
|
||||
import numpy as np
|
||||
import torch.utils.data as data
|
||||
|
||||
from PIL import Image
|
||||
import os
|
||||
import os.path
|
||||
|
||||
IMG_EXTENSIONS = [
|
||||
'.jpg', '.JPG', '.jpeg', '.JPEG',
|
||||
'.png', '.PNG', '.ppm', '.PPM', '.bmp', '.BMP',
|
||||
'.tif', '.TIF', '.tiff', '.TIFF',
|
||||
]
|
||||
|
||||
|
||||
def is_image_file(filename):
|
||||
return any(filename.endswith(extension) for extension in IMG_EXTENSIONS)
|
||||
|
||||
|
||||
def make_dataset(dir, max_dataset_size=float("inf")):
|
||||
images = []
|
||||
assert os.path.isdir(dir) or os.path.islink(dir), '%s is not a valid directory' % dir
|
||||
|
||||
for root, _, fnames in sorted(os.walk(dir, followlinks=True)):
|
||||
for fname in fnames:
|
||||
if is_image_file(fname):
|
||||
path = os.path.join(root, fname)
|
||||
images.append(path)
|
||||
return images[:min(max_dataset_size, len(images))]
|
||||
|
||||
|
||||
def default_loader(path):
|
||||
return Image.open(path).convert('RGB')
|
||||
|
||||
|
||||
class ImageFolder(data.Dataset):
|
||||
|
||||
def __init__(self, root, transform=None, return_paths=False,
|
||||
loader=default_loader):
|
||||
imgs = make_dataset(root)
|
||||
if len(imgs) == 0:
|
||||
raise(RuntimeError("Found 0 images in: " + root + "\n"
|
||||
"Supported image extensions are: " + ",".join(IMG_EXTENSIONS)))
|
||||
|
||||
self.root = root
|
||||
self.imgs = imgs
|
||||
self.transform = transform
|
||||
self.return_paths = return_paths
|
||||
self.loader = loader
|
||||
|
||||
def __getitem__(self, index):
|
||||
path = self.imgs[index]
|
||||
img = self.loader(path)
|
||||
if self.transform is not None:
|
||||
img = self.transform(img)
|
||||
if self.return_paths:
|
||||
return img, path
|
||||
else:
|
||||
return img
|
||||
|
||||
def __len__(self):
|
||||
return len(self.imgs)
|
||||
@@ -0,0 +1,75 @@
|
||||
"""Dataset class template
|
||||
|
||||
This module provides a template for users to implement custom datasets.
|
||||
You can specify '--dataset_mode template' to use this dataset.
|
||||
The class name should be consistent with both the filename and its dataset_mode option.
|
||||
The filename should be <dataset_mode>_dataset.py
|
||||
The class name should be <Dataset_mode>Dataset.py
|
||||
You need to implement the following functions:
|
||||
-- <modify_commandline_options>: Add dataset-specific options and rewrite default values for existing options.
|
||||
-- <__init__>: Initialize this dataset class.
|
||||
-- <__getitem__>: Return a data point and its metadata information.
|
||||
-- <__len__>: Return the number of images.
|
||||
"""
|
||||
from data.base_dataset import BaseDataset, get_transform
|
||||
# from data.image_folder import make_dataset
|
||||
# from PIL import Image
|
||||
|
||||
|
||||
class TemplateDataset(BaseDataset):
|
||||
"""A template dataset class for you to implement custom datasets."""
|
||||
@staticmethod
|
||||
def modify_commandline_options(parser, is_train):
|
||||
"""Add new dataset-specific options, and rewrite default values for existing options.
|
||||
|
||||
Parameters:
|
||||
parser -- original option parser
|
||||
is_train (bool) -- whether training phase or test phase. You can use this flag to add training-specific or test-specific options.
|
||||
|
||||
Returns:
|
||||
the modified parser.
|
||||
"""
|
||||
parser.add_argument('--new_dataset_option', type=float, default=1.0, help='new dataset option')
|
||||
parser.set_defaults(max_dataset_size=10, new_dataset_option=2.0) # specify dataset-specific default values
|
||||
return parser
|
||||
|
||||
def __init__(self, opt):
|
||||
"""Initialize this dataset class.
|
||||
|
||||
Parameters:
|
||||
opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions
|
||||
|
||||
A few things can be done here.
|
||||
- save the options (have been done in BaseDataset)
|
||||
- get image paths and meta information of the dataset.
|
||||
- define the image transformation.
|
||||
"""
|
||||
# save the option and dataset root
|
||||
BaseDataset.__init__(self, opt)
|
||||
# get the image paths of your dataset;
|
||||
self.image_paths = [] # You can call sorted(make_dataset(self.root, opt.max_dataset_size)) to get all the image paths under the directory self.root
|
||||
# define the default transform function. You can use <base_dataset.get_transform>; You can also define your custom transform function
|
||||
self.transform = get_transform(opt)
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Return a data point and its metadata information.
|
||||
|
||||
Parameters:
|
||||
index -- a random integer for data indexing
|
||||
|
||||
Returns:
|
||||
a dictionary of data with their names. It usually contains the data itself and its metadata information.
|
||||
|
||||
Step 1: get a random image path: e.g., path = self.image_paths[index]
|
||||
Step 2: load your data from the disk: e.g., image = Image.open(path).convert('RGB').
|
||||
Step 3: convert your data to a PyTorch tensor. You can use helpder functions such as self.transform. e.g., data = self.transform(image)
|
||||
Step 4: return a data point as a dictionary.
|
||||
"""
|
||||
path = 'temp' # needs to be a string
|
||||
data_A = None # needs to be a tensor
|
||||
data_B = None # needs to be a tensor
|
||||
return {'data_A': data_A, 'data_B': data_B, 'path': path}
|
||||
|
||||
def __len__(self):
|
||||
"""Return the total number of images."""
|
||||
return len(self.image_paths)
|
||||
Reference in New Issue
Block a user