mirror of
https://github.com/deepfakes/faceswap
synced 2025-06-07 10:43:27 -04:00
* model_refactor (#571) * original model to new structure * IAE model to new structure * OriginalHiRes to new structure * Fix trainer for different resolutions * Initial config implementation * Configparse library added * improved training data loader * dfaker model working * Add logging to training functions * Non blocking input for cli training * Add error handling to threads. Add non-mp queues to queue_handler * Improved Model Building and NNMeta * refactor lib/models * training refactor. DFL H128 model Implementation * Dfaker - use hashes * Move timelapse. Remove perceptual loss arg * Update INSTALL.md. Add logger formatting. Update Dfaker training * DFL h128 partially ported * Add mask to dfaker (#573) * Remove old models. Add mask to dfaker * dfl mask. Make masks selectable in config (#575) * DFL H128 Mask. Mask type selectable in config. * remove gan_v2_2 * Creating Input Size config for models Creating Input Size config for models Will be used downstream in converters. Also name change of image_shape to input_shape to clarify ( for future models with potentially different output_shapes) * Add mask loss options to config * MTCNN options to config.ini. Remove GAN config. Update USAGE.md * Add sliders for numerical values in GUI * Add config plugins menu to gui. Validate config * Only backup model if loss has dropped. Get training working again * bugfixes * Standardise loss printing * GUI idle cpu fixes. Graph loss fix. * mutli-gpu logging bugfix * Merge branch 'staging' into train_refactor * backup state file * Crash protection: Only backup if both total losses have dropped * Port OriginalHiRes_RC4 to train_refactor (OriginalHiRes) * Load and save model structure with weights * Slight code update * Improve config loader. Add subpixel opt to all models. Config to state * Show samples... wrong input * Remove AE topology. Add input/output shapes to State * Port original_villain (birb/VillainGuy) model to faceswap * Add plugin info to GUI config pages * Load input shape from state. IAE Config options. * Fix transform_kwargs. Coverage to ratio. Bugfix mask detection * Suppress keras userwarnings. Automate zoom. Coverage_ratio to model def. * Consolidation of converters & refactor (#574) * Consolidation of converters & refactor Initial Upload of alpha Items - consolidate convert_mased & convert_adjust into one converter -add average color adjust to convert_masked -allow mask transition blur size to be a fixed integer of pixels and a fraction of the facial mask size -allow erosion/dilation size to be a fixed integer of pixels and a fraction of the facial mask size -eliminate redundant type conversions to avoid multiple round-off errors -refactor loops for vectorization/speed -reorganize for clarity & style changes TODO - bug/issues with warping the new face onto a transparent old image...use a cleanup mask for now - issues with mask border giving black ring at zero erosion .. investigate - remove GAN ?? - test enlargment factors of umeyama standard face .. match to coverage factor - make enlargment factor a model parameter - remove convert_adjusted and referencing code when finished * Update Convert_Masked.py default blur size of 2 to match original... description of enlargement tests breakout matrxi scaling into def * Enlargment scale as a cli parameter * Update cli.py * dynamic interpolation algorithm Compute x & y scale factors from the affine matrix on the fly by QR decomp. Choose interpolation alogrithm for the affine warp based on an upsample or downsample for each image * input size input size from config * fix issues with <1.0 erosion * Update convert.py * Update Convert_Adjust.py more work on the way to merginf * Clean up help note on sharpen * cleanup seamless * Delete Convert_Adjust.py * Update umeyama.py * Update training_data.py * swapping * segmentation stub * changes to convert.str * Update masked.py * Backwards compatibility fix for models Get converter running * Convert: Move masks to class. bugfix blur_size some linting * mask fix * convert fixes - missing facehull_rect re-added - coverage to % - corrected coverage logic - cleanup of gui option ordering * Update cli.py * default for blur * Update masked.py * added preliminary low_mem version of OriginalHighRes model plugin * Code cleanup, minor fixes * Update masked.py * Update masked.py * Add dfl mask to convert * histogram fix & seamless location * update * revert * bugfix: Load actual configuration in gui * Standardize nn_blocks * Update cli.py * Minor code amends * Fix Original HiRes model * Add masks to preview output for mask trainers refactor trainer.__base.py * Masked trainers converter support * convert bugfix * Bugfix: Converter for masked (dfl/dfaker) trainers * Additional Losses (#592) * initial upload * Delete blur.py * default initializer = He instead of Glorot (#588) * Allow kernel_initializer to be overridable * Add ICNR Initializer option for upscale on all models. * Hopefully fixes RSoDs with original-highres model plugin * remove debug line * Original-HighRes model plugin Red Screen of Death fix, take #2 * Move global options to _base. Rename Villain model * clipnorm and res block biases * scale the end of res block * res block * dfaker pre-activation res * OHRES pre-activation * villain pre-activation * tabs/space in nn_blocks * fix for histogram with mask all set to zero * fix to prevent two networks with same name * GUI: Wider tooltips. Improve TQDM capture * Fix regex bug * Convert padding=48 to ratio of image size * Add size option to alignments tool extract * Pass through training image size to convert from model * Convert: Pull training coverage from model * convert: coverage, blur and erode to percent * simplify matrix scaling * ordering of sliders in train * Add matrix scaling to utils. Use interpolation in lib.aligner transform * masked.py Import get_matrix_scaling from utils * fix circular import * Update masked.py * quick fix for matrix scaling * testing thus for now * tqdm regex capture bugfix * Minor ammends * blur size cleanup * Remove coverage option from convert (Now cascades from model) * Implement convert for all model types * Add mask option and coverage option to all existing models * bugfix for model loading on convert * debug print removal * Bugfix for masks in dfl_h128 and iae * Update preview display. Add preview scaling to cli * mask notes * Delete training_data_v2.py errant file * training data variables * Fix timelapse function * Add new config items to state file for legacy purposes * Slight GUI tweak * Raise exception if problem with loaded model * Add Tensorboard support (Logs stored in model directory) * ICNR fix * loss bugfix * convert bugfix * Move ini files to config folder. Make TensorBoard optional * Fix training data for unbalanced inputs/outputs * Fix config "none" test * Keep helptext in .ini files when saving config from GUI * Remove frame_dims from alignments * Add no-flip and warp-to-landmarks cli options * Revert OHR to RC4_fix version * Fix lowmem mode on OHR model * padding to variable * Save models in parallel threads * Speed-up of res_block stability * Automated Reflection Padding * Reflect Padding as a training option Includes auto-calculation of proper padding shapes, input_shapes, output_shapes Flag included in config now * rest of reflect padding * Move TB logging to cli. Session info to state file * Add session iterations to state file * Add recent files to menu. GUI code tidy up * [GUI] Fix recent file list update issue * Add correct loss names to TensorBoard logs * Update live graph to use TensorBoard and remove animation * Fix analysis tab. GUI optimizations * Analysis Graph popup to Tensorboard Logs * [GUI] Bug fix for graphing for models with hypens in name * [GUI] Correctly split loss to tabs during training * [GUI] Add loss type selection to analysis graph * Fix store command name in recent files. Switch to correct tab on open * [GUI] Disable training graph when 'no-logs' is selected * Fix graphing race condition * rename original_hires model to unbalanced
338 lines
13 KiB
Python
338 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
""" Custom Layers for faceswap.py
|
|
Layers from:
|
|
the original https://www.reddit.com/r/deepfakes/ code sample + contribs
|
|
shoanlu GAN: https://github.com/shaoanlu/faceswap-GAN"""
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import sys
|
|
import inspect
|
|
|
|
import tensorflow as tf
|
|
import keras.backend as K
|
|
|
|
from keras.engine import InputSpec, Layer
|
|
from keras.utils import conv_utils
|
|
from keras.utils.generic_utils import get_custom_objects
|
|
from keras import initializers
|
|
from keras.layers import ZeroPadding2D
|
|
|
|
|
|
class PixelShuffler(Layer):
|
|
""" PixelShuffler layer for Keras
|
|
by t-ae: https://gist.github.com/t-ae/6e1016cc188104d123676ccef3264981 """
|
|
# pylint: disable=C0103
|
|
def __init__(self, size=(2, 2), data_format=None, **kwargs):
|
|
super(PixelShuffler, self).__init__(**kwargs)
|
|
self.data_format = K.normalize_data_format(data_format)
|
|
self.size = conv_utils.normalize_tuple(size, 2, 'size')
|
|
|
|
def call(self, inputs, **kwargs):
|
|
|
|
input_shape = K.int_shape(inputs)
|
|
if len(input_shape) != 4:
|
|
raise ValueError('Inputs should have rank ' +
|
|
str(4) +
|
|
'; Received input shape:', str(input_shape))
|
|
|
|
if self.data_format == 'channels_first':
|
|
batch_size, c, h, w = input_shape
|
|
if batch_size is None:
|
|
batch_size = -1
|
|
rh, rw = self.size
|
|
oh, ow = h * rh, w * rw
|
|
oc = c // (rh * rw)
|
|
|
|
out = K.reshape(inputs, (batch_size, rh, rw, oc, h, w))
|
|
out = K.permute_dimensions(out, (0, 3, 4, 1, 5, 2))
|
|
out = K.reshape(out, (batch_size, oc, oh, ow))
|
|
elif self.data_format == 'channels_last':
|
|
batch_size, h, w, c = input_shape
|
|
if batch_size is None:
|
|
batch_size = -1
|
|
rh, rw = self.size
|
|
oh, ow = h * rh, w * rw
|
|
oc = c // (rh * rw)
|
|
|
|
out = K.reshape(inputs, (batch_size, h, w, rh, rw, oc))
|
|
out = K.permute_dimensions(out, (0, 1, 3, 2, 4, 5))
|
|
out = K.reshape(out, (batch_size, oh, ow, oc))
|
|
return out
|
|
|
|
def compute_output_shape(self, input_shape):
|
|
|
|
if len(input_shape) != 4:
|
|
raise ValueError('Inputs should have rank ' +
|
|
str(4) +
|
|
'; Received input shape:', str(input_shape))
|
|
|
|
if self.data_format == 'channels_first':
|
|
height = None
|
|
width = None
|
|
if input_shape[2] is not None:
|
|
height = input_shape[2] * self.size[0]
|
|
if input_shape[3] is not None:
|
|
width = input_shape[3] * self.size[1]
|
|
channels = input_shape[1] // self.size[0] // self.size[1]
|
|
|
|
if channels * self.size[0] * self.size[1] != input_shape[1]:
|
|
raise ValueError('channels of input and size are incompatible')
|
|
|
|
retval = (input_shape[0],
|
|
channels,
|
|
height,
|
|
width)
|
|
elif self.data_format == 'channels_last':
|
|
height = None
|
|
width = None
|
|
if input_shape[1] is not None:
|
|
height = input_shape[1] * self.size[0]
|
|
if input_shape[2] is not None:
|
|
width = input_shape[2] * self.size[1]
|
|
channels = input_shape[3] // self.size[0] // self.size[1]
|
|
|
|
if channels * self.size[0] * self.size[1] != input_shape[3]:
|
|
raise ValueError('channels of input and size are incompatible')
|
|
|
|
retval = (input_shape[0],
|
|
height,
|
|
width,
|
|
channels)
|
|
return retval
|
|
|
|
def get_config(self):
|
|
config = {'size': self.size,
|
|
'data_format': self.data_format}
|
|
base_config = super(PixelShuffler, self).get_config()
|
|
|
|
return dict(list(base_config.items()) + list(config.items()))
|
|
|
|
|
|
class Scale(Layer):
|
|
"""
|
|
GAN Custom Scal Layer
|
|
Code borrows from https://github.com/flyyufelix/cnn_finetune
|
|
"""
|
|
def __init__(self, weights=None, axis=-1, gamma_init='zero', **kwargs):
|
|
self.axis = axis
|
|
self.gamma_init = initializers.get(gamma_init)
|
|
self.initial_weights = weights
|
|
super(Scale, self).__init__(**kwargs)
|
|
|
|
def build(self, input_shape):
|
|
self.input_spec = [InputSpec(shape=input_shape)]
|
|
|
|
# Compatibility with TensorFlow >= 1.0.0
|
|
self.gamma = K.variable(self.gamma_init((1,)), name='{}_gamma'.format(self.name))
|
|
self.trainable_weights = [self.gamma]
|
|
|
|
if self.initial_weights is not None:
|
|
self.set_weights(self.initial_weights)
|
|
del self.initial_weights
|
|
|
|
def call(self, x, mask=None):
|
|
return self.gamma * x
|
|
|
|
def get_config(self):
|
|
config = {"axis": self.axis}
|
|
base_config = super(Scale, self).get_config()
|
|
return dict(list(base_config.items()) + list(config.items()))
|
|
|
|
|
|
class SubPixelUpscaling(Layer):
|
|
# pylint: disable=C0103
|
|
""" Sub-pixel convolutional upscaling layer based on the paper "Real-Time
|
|
Single Image and Video Super-Resolution Using an Efficient Sub-Pixel
|
|
Convolutional Neural Network" (https://arxiv.org/abs/1609.05158).
|
|
This layer requires a Convolution2D prior to it, having output filters
|
|
computed according to the formula :
|
|
filters = k * (scale_factor * scale_factor)
|
|
where k = a user defined number of filters (generally larger than 32)
|
|
scale_factor = the upscaling factor (generally 2)
|
|
This layer performs the depth to space operation on the convolution
|
|
filters, and returns a tensor with the size as defined below.
|
|
# Example :
|
|
```python
|
|
# A standard subpixel upscaling block
|
|
x = Convolution2D(256, 3, 3, padding="same", activation="relu")(...)
|
|
u = SubPixelUpscaling(scale_factor=2)(x)
|
|
[Optional]
|
|
x = Convolution2D(256, 3, 3, padding="same", activation="relu")(u)
|
|
```
|
|
In practice, it is useful to have a second convolution layer after the
|
|
SubPixelUpscaling layer to speed up the learning process.
|
|
However, if you are stacking multiple SubPixelUpscaling blocks,
|
|
it may increase the number of parameters greatly, so the Convolution
|
|
layer after SubPixelUpscaling layer can be removed.
|
|
# Arguments
|
|
scale_factor: Upscaling factor.
|
|
data_format: Can be None, "channels_first" or "channels_last".
|
|
# Input shape
|
|
4D tensor with shape:
|
|
`(samples, k * (scale_factor * scale_factor) channels, rows, cols)`
|
|
if data_format="channels_first"
|
|
or 4D tensor with shape:
|
|
`(samples, rows, cols, k * (scale_factor * scale_factor) channels)`
|
|
if data_format="channels_last".
|
|
# Output shape
|
|
4D tensor with shape:
|
|
`(samples, k channels, rows * scale_factor, cols * scale_factor))`
|
|
if data_format="channels_first"
|
|
or 4D tensor with shape:
|
|
`(samples, rows * scale_factor, cols * scale_factor, k channels)`
|
|
if data_format="channels_last".
|
|
"""
|
|
|
|
def __init__(self, scale_factor=2, data_format=None, **kwargs):
|
|
super(SubPixelUpscaling, self).__init__(**kwargs)
|
|
|
|
self.scale_factor = scale_factor
|
|
self.data_format = K.normalize_data_format(data_format)
|
|
|
|
def build(self, input_shape):
|
|
pass
|
|
|
|
def call(self, x, mask=None):
|
|
y = self.depth_to_space(x, self.scale_factor, self.data_format)
|
|
return y
|
|
|
|
def compute_output_shape(self, input_shape):
|
|
if self.data_format == "channels_first":
|
|
b, k, r, c = input_shape
|
|
return (b,
|
|
k // (self.scale_factor ** 2),
|
|
r * self.scale_factor,
|
|
c * self.scale_factor)
|
|
b, r, c, k = input_shape
|
|
return (b,
|
|
r * self.scale_factor,
|
|
c * self.scale_factor,
|
|
k // (self.scale_factor ** 2))
|
|
|
|
@classmethod
|
|
def depth_to_space(cls, ipt, scale, data_format=None):
|
|
""" Uses phase shift algorithm to convert channels/depth
|
|
for spatial resolution """
|
|
if data_format is None:
|
|
data_format = K.image_data_format()
|
|
data_format = data_format.lower()
|
|
ipt = cls._preprocess_conv2d_input(ipt, data_format)
|
|
out = tf.depth_to_space(ipt, scale)
|
|
out = cls._postprocess_conv2d_output(out, data_format)
|
|
return out
|
|
|
|
@staticmethod
|
|
def _postprocess_conv2d_output(x, data_format):
|
|
"""Transpose and cast the output from conv2d if needed.
|
|
# Arguments
|
|
x: A tensor.
|
|
data_format: string, `"channels_last"` or `"channels_first"`.
|
|
# Returns
|
|
A tensor.
|
|
"""
|
|
|
|
if data_format == "channels_first":
|
|
x = tf.transpose(x, (0, 3, 1, 2))
|
|
|
|
if K.floatx() == "float64":
|
|
x = tf.cast(x, "float64")
|
|
return x
|
|
|
|
@staticmethod
|
|
def _preprocess_conv2d_input(x, data_format):
|
|
"""Transpose and cast the input before the conv2d.
|
|
# Arguments
|
|
x: input tensor.
|
|
data_format: string, `"channels_last"` or `"channels_first"`.
|
|
# Returns
|
|
A tensor.
|
|
"""
|
|
if K.dtype(x) == "float64":
|
|
x = tf.cast(x, "float32")
|
|
if data_format == "channels_first":
|
|
# TF uses the last dimension as channel dimension,
|
|
# instead of the 2nd one.
|
|
# TH input shape: (samples, input_depth, rows, cols)
|
|
# TF input shape: (samples, rows, cols, input_depth)
|
|
x = tf.transpose(x, (0, 2, 3, 1))
|
|
return x
|
|
|
|
def get_config(self):
|
|
config = {"scale_factor": self.scale_factor,
|
|
"data_format": self.data_format}
|
|
base_config = super(SubPixelUpscaling, self).get_config()
|
|
return dict(list(base_config.items()) + list(config.items()))
|
|
|
|
|
|
class ReflectionPadding2D(Layer):
|
|
def __init__(self, stride=2, kernel_size=5, **kwargs):
|
|
'''
|
|
# Arguments
|
|
stride: stride of following convolution (2)
|
|
kernel_size: kernel size of following convolution (5,5)
|
|
'''
|
|
self.stride = stride
|
|
self.kernel_size = kernel_size
|
|
super(ReflectionPadding2D, self).__init__(**kwargs)
|
|
|
|
def build(self, input_shape):
|
|
self.input_spec = [InputSpec(shape=input_shape)]
|
|
super(ReflectionPadding2D, self).build(input_shape)
|
|
|
|
def compute_output_shape(self, input_shape):
|
|
""" If you are using "channels_last" configuration"""
|
|
input_shape = self.input_spec[0].shape
|
|
in_width, in_height = input_shape[2], input_shape[1]
|
|
kernel_width, kernel_height = self.kernel_size, self.kernel_size
|
|
|
|
if (in_height % self.stride == 0):
|
|
padding_height = max(kernel_height - self.stride, 0)
|
|
else:
|
|
padding_height = max(kernel_height - (in_height % self.stride), 0)
|
|
if (in_width % self.stride == 0):
|
|
padding_width = max(kernel_width - self.stride, 0)
|
|
else:
|
|
padding_width = max(kernel_width- (in_width % self.stride), 0)
|
|
|
|
return (input_shape[0],
|
|
input_shape[1] + padding_height,
|
|
input_shape[2] + padding_width,
|
|
input_shape[3])
|
|
|
|
def call(self, x, mask=None):
|
|
input_shape = self.input_spec[0].shape
|
|
in_width, in_height = input_shape[2], input_shape[1]
|
|
kernel_width, kernel_height = self.kernel_size, self.kernel_size
|
|
|
|
if (in_height % self.stride == 0):
|
|
padding_height = max(kernel_height - self.stride, 0)
|
|
else:
|
|
padding_height = max(kernel_height - (in_height % self.stride), 0)
|
|
if (in_width % self.stride == 0):
|
|
padding_width = max(kernel_width - self.stride, 0)
|
|
else:
|
|
padding_width = max(kernel_width- (in_width % self.stride), 0)
|
|
|
|
padding_top = padding_height // 2
|
|
padding_bot = padding_height - padding_top
|
|
padding_left = padding_width // 2
|
|
padding_right = padding_width - padding_left
|
|
|
|
return tf.pad(x, [[0,0],
|
|
[padding_top, padding_bot],
|
|
[padding_left, padding_right],
|
|
[0,0] ],
|
|
'REFLECT')
|
|
|
|
def get_config(self):
|
|
config = {'stride': self.stride,
|
|
'kernel_size': self.kernel_size}
|
|
base_config = super(ReflectionPadding2D, self).get_config()
|
|
return dict(list(base_config.items()) + list(config.items()))
|
|
|
|
|
|
# Update layers into Keras custom objects
|
|
for name, obj in inspect.getmembers(sys.modules[__name__]):
|
|
if inspect.isclass(obj) and obj.__module__ == __name__:
|
|
get_custom_objects().update({name: obj})
|