mirror of
https://github.com/deepfakes/faceswap
synced 2025-06-07 10:37:19 -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
869 lines
43 KiB
Python
869 lines
43 KiB
Python
#!/usr/bin/env python3
|
|
""" Command Line Arguments """
|
|
import argparse
|
|
import logging
|
|
import os
|
|
import platform
|
|
import sys
|
|
|
|
from importlib import import_module
|
|
|
|
from lib.logger import crash_log, log_setup
|
|
from lib.utils import safe_shutdown
|
|
from plugins.plugin_loader import PluginLoader
|
|
|
|
logger = logging.getLogger(__name__) # pylint: disable=invalid-name
|
|
|
|
|
|
class ScriptExecutor():
|
|
""" Loads the relevant script modules and executes the script.
|
|
This class is initialised in each of the argparsers for the relevant
|
|
command, then execute script is called within their set_default
|
|
function. """
|
|
|
|
def __init__(self, command, subparsers=None):
|
|
self.command = command.lower()
|
|
self.subparsers = subparsers
|
|
|
|
def import_script(self):
|
|
""" Only import a script's modules when running that script."""
|
|
self.test_for_gui()
|
|
cmd = os.path.basename(sys.argv[0])
|
|
src = "tools" if cmd == "tools.py" else "scripts"
|
|
mod = ".".join((src, self.command.lower()))
|
|
module = import_module(mod)
|
|
script = getattr(module, self.command.title())
|
|
return script
|
|
|
|
def test_for_gui(self):
|
|
""" If running the gui, check the prerequisites """
|
|
if self.command != "gui":
|
|
return
|
|
self.test_tkinter()
|
|
self.check_display()
|
|
|
|
@staticmethod
|
|
def test_tkinter():
|
|
""" If the user is running the GUI, test whether the
|
|
tkinter app is available on their machine. If not
|
|
exit gracefully.
|
|
|
|
This avoids having to import every tk function
|
|
within the GUI in a wrapper and potentially spamming
|
|
traceback errors to console """
|
|
|
|
try:
|
|
# pylint: disable=unused-variable
|
|
import tkinter # noqa pylint: disable=unused-import
|
|
except ImportError:
|
|
logger.warning(
|
|
"It looks like TkInter isn't installed for your OS, so "
|
|
"the GUI has been disabled. To enable the GUI please "
|
|
"install the TkInter application. You can try:")
|
|
logger.info("Anaconda: conda install tk")
|
|
logger.info("Windows/macOS: Install ActiveTcl Community Edition from "
|
|
"http://www.activestate.com")
|
|
logger.info("Ubuntu/Mint/Debian: sudo apt install python3-tk")
|
|
logger.info("Arch: sudo pacman -S tk")
|
|
logger.info("CentOS/Redhat: sudo yum install tkinter")
|
|
logger.info("Fedora: sudo dnf install python3-tkinter")
|
|
exit(1)
|
|
|
|
@staticmethod
|
|
def check_display():
|
|
""" Check whether there is a display to output the GUI. If running on
|
|
Windows then assume not running in headless mode """
|
|
if not os.environ.get("DISPLAY", None) and os.name != "nt":
|
|
logger.warning("No display detected. GUI mode has been disabled.")
|
|
if platform.system() == "Darwin":
|
|
logger.info("macOS users need to install XQuartz. "
|
|
"See https://support.apple.com/en-gb/HT201341")
|
|
exit(1)
|
|
|
|
def execute_script(self, arguments):
|
|
""" Run the script for called command """
|
|
log_setup(arguments.loglevel, arguments.logfile, self.command)
|
|
logger.debug("Executing: %s. PID: %s", self.command, os.getpid())
|
|
try:
|
|
script = self.import_script()
|
|
process = script(arguments)
|
|
process.process()
|
|
except KeyboardInterrupt: # pylint: disable=try-except-raise
|
|
raise
|
|
except SystemExit:
|
|
pass
|
|
except Exception: # pylint: disable=broad-except
|
|
crash_file = crash_log()
|
|
logger.exception("Got Exception on main handler:")
|
|
logger.critical("An unexpected crash has occurred. Crash report written to %s. "
|
|
"Please verify you are running the latest version of faceswap "
|
|
"before reporting", crash_file)
|
|
|
|
finally:
|
|
safe_shutdown()
|
|
|
|
|
|
class Slider(argparse.Action): # pylint: disable=too-few-public-methods
|
|
""" Adds support for the GUI slider
|
|
|
|
An additional option 'min_max' must be provided containing tuple of min and max accepted
|
|
values.
|
|
|
|
'rounding' sets the decimal places for floats or the step interval for ints.
|
|
"""
|
|
def __init__(self, option_strings, dest, nargs=None, min_max=None, rounding=None, **kwargs):
|
|
if nargs is not None:
|
|
raise ValueError("nargs not allowed")
|
|
super().__init__(option_strings, dest, **kwargs)
|
|
self.min_max = min_max
|
|
self.rounding = rounding
|
|
|
|
def _get_kwargs(self):
|
|
names = ["option_strings",
|
|
"dest",
|
|
"nargs",
|
|
"const",
|
|
"default",
|
|
"type",
|
|
"choices",
|
|
"help",
|
|
"metavar",
|
|
"min_max", # Tuple containing min and max values of scale
|
|
"rounding"] # Decimal places to round floats to or step interval for ints
|
|
return [(name, getattr(self, name)) for name in names]
|
|
|
|
def __call__(self, parser, namespace, values, option_string=None):
|
|
setattr(namespace, self.dest, values)
|
|
|
|
|
|
class FullPaths(argparse.Action): # pylint: disable=too-few-public-methods
|
|
""" Expand user- and relative-paths """
|
|
def __call__(self, parser, namespace, values, option_string=None):
|
|
setattr(namespace, self.dest, os.path.abspath(
|
|
os.path.expanduser(values)))
|
|
|
|
|
|
class DirFullPaths(FullPaths):
|
|
""" Class that gui uses to determine if you need to open a directory """
|
|
# pylint: disable=too-few-public-methods,unnecessary-pass
|
|
pass
|
|
|
|
|
|
class FileFullPaths(FullPaths):
|
|
"""
|
|
Class that gui uses to determine if you need to open a file.
|
|
|
|
see lib/gui/utils.py FileHandler for current GUI filetypes
|
|
"""
|
|
# pylint: disable=too-few-public-methods
|
|
def __init__(self, option_strings, dest, nargs=None, filetypes=None, **kwargs):
|
|
super(FileFullPaths, self).__init__(option_strings, dest, **kwargs)
|
|
if nargs is not None:
|
|
raise ValueError("nargs not allowed")
|
|
self.filetypes = filetypes
|
|
|
|
def _get_kwargs(self):
|
|
names = ["option_strings",
|
|
"dest",
|
|
"nargs",
|
|
"const",
|
|
"default",
|
|
"type",
|
|
"choices",
|
|
"help",
|
|
"metavar",
|
|
"filetypes"]
|
|
return [(name, getattr(self, name)) for name in names]
|
|
|
|
|
|
class DirOrFileFullPaths(FileFullPaths): # pylint: disable=too-few-public-methods
|
|
""" Class that the gui uses to determine that the input can take a folder or a filename.
|
|
Inherits functionality from FileFullPaths
|
|
Has the effect of giving the user 2 Open Dialogue buttons in the gui """
|
|
pass
|
|
|
|
|
|
class SaveFileFullPaths(FileFullPaths):
|
|
"""
|
|
Class that gui uses to determine if you need to save a file.
|
|
|
|
see lib/gui/utils.py FileHandler for current GUI filetypes
|
|
"""
|
|
# pylint: disable=too-few-public-methods,unnecessary-pass
|
|
pass
|
|
|
|
|
|
class ContextFullPaths(FileFullPaths):
|
|
"""
|
|
Class that gui uses to determine if you need to open a file or a
|
|
directory based on which action you are choosing
|
|
|
|
To use ContextFullPaths the action_option item should indicate which
|
|
cli option dictates the context of the filesystem dialogue
|
|
|
|
Bespoke actions are then set in lib/gui/utils.py FileHandler
|
|
"""
|
|
# pylint: disable=too-few-public-methods, too-many-arguments
|
|
def __init__(self, option_strings, dest, nargs=None, filetypes=None,
|
|
action_option=None, **kwargs):
|
|
if nargs is not None:
|
|
raise ValueError("nargs not allowed")
|
|
super(ContextFullPaths, self).__init__(option_strings, dest,
|
|
filetypes=None, **kwargs)
|
|
self.action_option = action_option
|
|
self.filetypes = filetypes
|
|
|
|
def _get_kwargs(self):
|
|
names = ["option_strings",
|
|
"dest",
|
|
"nargs",
|
|
"const",
|
|
"default",
|
|
"type",
|
|
"choices",
|
|
"help",
|
|
"metavar",
|
|
"filetypes",
|
|
"action_option"]
|
|
return [(name, getattr(self, name)) for name in names]
|
|
|
|
|
|
class FullHelpArgumentParser(argparse.ArgumentParser):
|
|
""" Identical to the built-in argument parser, but on error it
|
|
prints full help message instead of just usage information """
|
|
def error(self, message):
|
|
self.print_help(sys.stderr)
|
|
args = {"prog": self.prog, "message": message}
|
|
self.exit(2, "%(prog)s: error: %(message)s\n" % args)
|
|
|
|
|
|
class SmartFormatter(argparse.HelpFormatter):
|
|
""" Smart formatter for allowing raw formatting in help
|
|
text.
|
|
|
|
To use prefix the help item with "R|" to overide
|
|
default formatting
|
|
|
|
from: https://stackoverflow.com/questions/3853722 """
|
|
|
|
def _split_lines(self, text, width):
|
|
if text.startswith("R|"):
|
|
return text[2:].splitlines()
|
|
# this is the RawTextHelpFormatter._split_lines
|
|
return argparse.HelpFormatter._split_lines(self, text, width)
|
|
|
|
|
|
class FaceSwapArgs():
|
|
""" Faceswap argument parser functions that are universal
|
|
to all commands. Should be the parent function of all
|
|
subsequent argparsers """
|
|
def __init__(self, subparser, command,
|
|
description="default", subparsers=None):
|
|
|
|
self.global_arguments = self.get_global_arguments()
|
|
self.argument_list = self.get_argument_list()
|
|
self.optional_arguments = self.get_optional_arguments()
|
|
if not subparser:
|
|
return
|
|
|
|
self.parser = self.create_parser(subparser, command, description)
|
|
|
|
self.add_arguments()
|
|
|
|
script = ScriptExecutor(command, subparsers)
|
|
self.parser.set_defaults(func=script.execute_script)
|
|
|
|
@staticmethod
|
|
def get_argument_list():
|
|
""" Put the arguments in a list so that they are accessible from both
|
|
argparse and gui override for command specific arguments """
|
|
argument_list = []
|
|
return argument_list
|
|
|
|
@staticmethod
|
|
def get_optional_arguments():
|
|
""" Put the arguments in a list so that they are accessible from both
|
|
argparse and gui. This is used for when there are sub-children
|
|
(e.g. convert and extract) Override this for custom arguments """
|
|
argument_list = []
|
|
return argument_list
|
|
|
|
@staticmethod
|
|
def get_global_arguments():
|
|
""" Arguments that are used in ALL parts of Faceswap
|
|
DO NOT override this """
|
|
global_args = list()
|
|
global_args.append({"opts": ("-L", "--loglevel"),
|
|
"type": str.upper,
|
|
"dest": "loglevel",
|
|
"default": "INFO",
|
|
"choices": ("INFO", "VERBOSE", "DEBUG", "TRACE"),
|
|
"help": "Log level. Stick with INFO or VERBOSE unless you need to "
|
|
"file an error report. Be careful with TRACE as it will "
|
|
"generate a lot of data"})
|
|
global_args.append({"opts": ("-LF", "--logfile"),
|
|
"action": FileFullPaths,
|
|
"filetypes": 'log',
|
|
"type": str,
|
|
"dest": "logfile",
|
|
"help": "Path to store the logfile. Leave blank to store in the "
|
|
"faceswap folder",
|
|
"default": None})
|
|
# This is a hidden argument to indicate that the GUI is being used,
|
|
# so the preview window should be redirected Accordingly
|
|
global_args.append({"opts": ("-gui", "--gui"),
|
|
"action": "store_true",
|
|
"dest": "redirect_gui",
|
|
"default": False,
|
|
"help": argparse.SUPPRESS})
|
|
return global_args
|
|
|
|
@staticmethod
|
|
def create_parser(subparser, command, description):
|
|
""" Create the parser for the selected command """
|
|
parser = subparser.add_parser(
|
|
command,
|
|
help=description,
|
|
description=description,
|
|
epilog="Questions and feedback: \
|
|
https://github.com/deepfakes/faceswap-playground",
|
|
formatter_class=SmartFormatter)
|
|
return parser
|
|
|
|
def add_arguments(self):
|
|
""" Parse the arguments passed in from argparse """
|
|
options = self.global_arguments + self.argument_list + self.optional_arguments
|
|
for option in options:
|
|
args = option["opts"]
|
|
kwargs = {key: option[key]
|
|
for key in option.keys() if key != "opts"}
|
|
self.parser.add_argument(*args, **kwargs)
|
|
|
|
|
|
class ExtractConvertArgs(FaceSwapArgs):
|
|
""" This class is used as a parent class to capture arguments that
|
|
will be used in both the extract and convert process.
|
|
|
|
Arguments that can be used in both of these processes should be
|
|
placed here, but no further processing should be done. This class
|
|
just captures arguments """
|
|
|
|
@staticmethod
|
|
def get_argument_list():
|
|
""" Put the arguments in a list so that they are accessible from both
|
|
argparse and gui """
|
|
argument_list = list()
|
|
argument_list.append({"opts": ("-i", "--input-dir"),
|
|
"action": DirOrFileFullPaths,
|
|
"filetypes": "video",
|
|
"dest": "input_dir",
|
|
"default": "input",
|
|
"help": "Input directory or video. Either a "
|
|
"directory containing the image files "
|
|
"you wish to process or path to a "
|
|
"video file. Defaults to 'input'"})
|
|
argument_list.append({"opts": ("-o", "--output-dir"),
|
|
"action": DirFullPaths,
|
|
"dest": "output_dir",
|
|
"default": "output",
|
|
"help": "Output directory. This is where the "
|
|
"converted files will be stored. "
|
|
"Defaults to 'output'"})
|
|
argument_list.append({"opts": ("-al", "--alignments"),
|
|
"action": FileFullPaths,
|
|
"filetypes": 'alignments',
|
|
"type": str,
|
|
"dest": "alignments_path",
|
|
"help": "Optional path to an alignments file."})
|
|
argument_list.append({"opts": ("-l", "--ref_threshold"),
|
|
"action": Slider,
|
|
"min_max": (0.01, 0.99),
|
|
"rounding": 2,
|
|
"type": float,
|
|
"dest": "ref_threshold",
|
|
"default": 0.6,
|
|
"help": "Threshold for positive face recognition. For use with "
|
|
"nfilter or filter. Lower values are stricter."})
|
|
argument_list.append({"opts": ("-n", "--nfilter"),
|
|
"type": str,
|
|
"dest": "nfilter",
|
|
"nargs": "+",
|
|
"default": None,
|
|
"help": "Reference image for the persons you do "
|
|
"not want to process. Should be a front "
|
|
"portrait. Multiple images can be added "
|
|
"space separated"})
|
|
argument_list.append({"opts": ("-f", "--filter"),
|
|
"type": str,
|
|
"dest": "filter",
|
|
"nargs": "+",
|
|
"default": None,
|
|
"help": "Reference images for the person you "
|
|
"want to process. Should be a front "
|
|
"portrait. Multiple images can be added "
|
|
"space separated"})
|
|
return argument_list
|
|
|
|
|
|
class ExtractArgs(ExtractConvertArgs):
|
|
""" Class to parse the command line arguments for extraction.
|
|
Inherits base options from ExtractConvertArgs where arguments
|
|
that are used for both extract and convert should be placed """
|
|
|
|
@staticmethod
|
|
def get_optional_arguments():
|
|
""" Put the arguments in a list so that they are accessible from both
|
|
argparse and gui """
|
|
argument_list = []
|
|
argument_list.append({"opts": ("--serializer", ),
|
|
"type": str.lower,
|
|
"dest": "serializer",
|
|
"default": "json",
|
|
"choices": ("json", "pickle", "yaml"),
|
|
"help": "Serializer for alignments file. If "
|
|
"yaml is chosen and not available, then "
|
|
"json will be used as the default "
|
|
"fallback."})
|
|
argument_list.append({
|
|
"opts": ("-D", "--detector"),
|
|
"type": str.lower,
|
|
"choices": PluginLoader.get_available_extractors(
|
|
"detect"),
|
|
"default": "mtcnn",
|
|
"help": "R|Detector to use."
|
|
"\n'dlib-hog': uses least resources, but is the"
|
|
"\n\tleast reliable."
|
|
"\n'dlib-cnn': faster than mtcnn but detects"
|
|
"\n\tfewer faces and fewer false positives."
|
|
"\n'mtcnn': slower than dlib, but uses fewer"
|
|
"\n\tresources whilst detecting more faces and"
|
|
"\n\tmore false positives. Has superior"
|
|
"\n\talignment to dlib"})
|
|
argument_list.append({
|
|
"opts": ("-A", "--aligner"),
|
|
"type": str.lower,
|
|
"choices": PluginLoader.get_available_extractors(
|
|
"align"),
|
|
"default": "fan",
|
|
"help": "R|Aligner to use."
|
|
"\n'dlib': Dlib Pose Predictor. Faster, less "
|
|
"\n\tresource intensive, but less accurate."
|
|
"\n'fan': Face Alignment Network. Best aligner."
|
|
"\n\tGPU heavy."})
|
|
argument_list.append({"opts": ("-r", "--rotate-images"),
|
|
"type": str,
|
|
"dest": "rotate_images",
|
|
"default": None,
|
|
"help": "If a face isn't found, rotate the "
|
|
"images to try to find a face. Can find "
|
|
"more faces at the cost of extraction "
|
|
"speed. Pass in a single number to use "
|
|
"increments of that size up to 360, or "
|
|
"pass in a list of numbers to enumerate "
|
|
"exactly what angles to check"})
|
|
argument_list.append({"opts": ("-bt", "--blur-threshold"),
|
|
"type": float,
|
|
"action": Slider,
|
|
"min_max": (0.0, 100.0),
|
|
"rounding": 1,
|
|
"dest": "blur_thresh",
|
|
"default": 0.0,
|
|
"help": "Automatically discard images blurrier than the specified "
|
|
"threshold. Discarded images are moved into a \"blurry\" "
|
|
"sub-folder. Lower values allow more blur. Set to 0.0 to "
|
|
"turn off."})
|
|
argument_list.append({"opts": ("-mp", "--multiprocess"),
|
|
"action": "store_true",
|
|
"default": False,
|
|
"help": "Run extraction in parallel. Offers "
|
|
"speed up for some extractor/detector "
|
|
"combinations, less so for others. "
|
|
"Only has an effect if both the "
|
|
"aligner and detector use the GPU, "
|
|
"otherwise this is automatic."})
|
|
argument_list.append({"opts": ("-sz", "--size"),
|
|
"type": int,
|
|
"action": Slider,
|
|
"min_max": (128, 512),
|
|
"default": 256,
|
|
"rounding": 64,
|
|
"help": "The output size of extracted faces. Make sure that the "
|
|
"model you intend to train supports your required size. "
|
|
"This will only need to be changed for hi-res models."})
|
|
argument_list.append({"opts": ("-s", "--skip-existing"),
|
|
"action": "store_true",
|
|
"dest": "skip_existing",
|
|
"default": False,
|
|
"help": "Skips frames that have already been "
|
|
"extracted and exist in the alignments "
|
|
"file"})
|
|
argument_list.append({"opts": ("-sf", "--skip-existing-faces"),
|
|
"action": "store_true",
|
|
"dest": "skip_faces",
|
|
"default": False,
|
|
"help": "Skip frames that already have "
|
|
"detected faces in the alignments "
|
|
"file"})
|
|
argument_list.append({"opts": ("-dl", "--debug-landmarks"),
|
|
"action": "store_true",
|
|
"dest": "debug_landmarks",
|
|
"default": False,
|
|
"help": "Draw landmarks on the ouput faces for "
|
|
"debug"})
|
|
argument_list.append({"opts": ("-ae", "--align-eyes"),
|
|
"action": "store_true",
|
|
"dest": "align_eyes",
|
|
"default": False,
|
|
"help": "Perform extra alignment to ensure "
|
|
"left/right eyes are at the same "
|
|
"height"})
|
|
argument_list.append({"opts": ("-si", "--save-interval"),
|
|
"dest": "save_interval",
|
|
"type": int,
|
|
"action": Slider,
|
|
"min_max": (0, 1000),
|
|
"rounding": 10,
|
|
"default": 0,
|
|
"help": "Automatically save the alignments file after a set amount "
|
|
"of frames. Will only save at the end of extracting by "
|
|
"default. WARNING: Don't interrupt the script when writing "
|
|
"the file because it might get corrupted. Set to 0 to turn "
|
|
"off"})
|
|
return argument_list
|
|
|
|
|
|
class ConvertArgs(ExtractConvertArgs):
|
|
""" Class to parse the command line arguments for conversion.
|
|
Inherits base options from ExtractConvertArgs where arguments
|
|
that are used for both extract and convert should be placed """
|
|
|
|
@staticmethod
|
|
def get_optional_arguments():
|
|
""" Put the arguments in a list so that they are accessible from both
|
|
argparse and gui """
|
|
argument_list = []
|
|
argument_list.append({"opts": ("-m", "--model-dir"),
|
|
"action": DirFullPaths,
|
|
"dest": "model_dir",
|
|
"default": "models",
|
|
"help": "Model directory. A directory "
|
|
"containing the trained model you wish "
|
|
"to process. Defaults to 'models'"})
|
|
argument_list.append({"opts": ("-a", "--input-aligned-dir"),
|
|
"action": DirFullPaths,
|
|
"dest": "input_aligned_dir",
|
|
"default": None,
|
|
"help": "Input \"aligned directory\". A "
|
|
"directory that should contain the "
|
|
"aligned faces extracted from the input "
|
|
"files. If you delete faces from this "
|
|
"folder, they'll be skipped during "
|
|
"conversion. If no aligned dir is "
|
|
"specified, all faces will be "
|
|
"converted"})
|
|
argument_list.append({"opts": ("-t", "--trainer"),
|
|
"type": str.lower,
|
|
"choices": PluginLoader.get_available_models(),
|
|
"default": PluginLoader.get_default_model(),
|
|
"help": "Select the trainer that was used to "
|
|
"create the model"})
|
|
argument_list.append({"opts": ("-c", "--converter"),
|
|
"type": str.lower,
|
|
"choices": PluginLoader.get_available_converters(),
|
|
"default": "masked",
|
|
"help": "Converter to use"})
|
|
argument_list.append({
|
|
"opts": ("-M", "--mask-type"),
|
|
"type": str.lower,
|
|
"dest": "mask_type",
|
|
"choices": ["rect",
|
|
"ellipse",
|
|
"smoothed",
|
|
"facehull",
|
|
"facehull_rect",
|
|
"dfl",
|
|
"cnn"],
|
|
"default": "facehull_rect",
|
|
"help": "R|Mask to use to replace faces."
|
|
"\nrect: Rectangle around face."
|
|
"\nellipse: Oval around face."
|
|
"\nsmoothed: Rectangle around face with smoothing."
|
|
"\nfacehull: Face cutout based on landmarks."
|
|
"\nfacehull_rect: Rectangle around faces with facehull"
|
|
"\n\tbetween the edges of the face and the background."
|
|
"\ndfl: A Face Hull mask from DeepFaceLabs."
|
|
"\ncnn: Not yet implemented"})
|
|
argument_list.append({"opts": ("-b", "--blur-size"),
|
|
"type": float,
|
|
"action": Slider,
|
|
"min_max": (0.0, 100.0),
|
|
"rounding": 2,
|
|
"default": 5.0,
|
|
"help": "Blur kernel size as a percentage of the swap area. Smooths "
|
|
"the transition between the swapped face and the background "
|
|
"image."})
|
|
argument_list.append({"opts": ("-e", "--erosion-size"),
|
|
"dest": "erosion_size",
|
|
"type": float,
|
|
"action": Slider,
|
|
"min_max": (-100.0, 100.0),
|
|
"rounding": 2,
|
|
"default": 0.0,
|
|
"help": "Erosion kernel size as a percentage of the mask radius "
|
|
"area. Positive values apply erosion which reduces the size "
|
|
"of the swapped area. Negative values apply dilation which "
|
|
"increases the swapped area"})
|
|
argument_list.append({"opts": ("-g", "--gpus"),
|
|
"type": int,
|
|
"action": Slider,
|
|
"min_max": (1, 10),
|
|
"rounding": 1,
|
|
"default": 1,
|
|
"help": "Number of GPUs to use for conversion"})
|
|
argument_list.append({"opts": ("-sh", "--sharpen"),
|
|
"type": str.lower,
|
|
"dest": "sharpen_image",
|
|
"choices": ["box_filter", "gaussian_filter"],
|
|
"default": None,
|
|
"help": "Sharpen the masked facial region of "
|
|
"the converted images. Choice of filter "
|
|
"to use in sharpening process -- box"
|
|
"filter or gaussian filter."})
|
|
argument_list.append({"opts": ("-fr", "--frame-ranges"),
|
|
"nargs": "+",
|
|
"type": str,
|
|
"help": "frame ranges to apply transfer to e.g. "
|
|
"For frames 10 to 50 and 90 to 100 use "
|
|
"--frame-ranges 10-50 90-100. Files "
|
|
"must have the frame-number as the last "
|
|
"number in the name!"})
|
|
argument_list.append({"opts": ("-d", "--discard-frames"),
|
|
"action": "store_true",
|
|
"dest": "discard_frames",
|
|
"default": False,
|
|
"help": "When used with --frame-ranges discards "
|
|
"frames that are not processed instead "
|
|
"of writing them out unchanged"})
|
|
argument_list.append({"opts": ("-s", "--swap-model"),
|
|
"action": "store_true",
|
|
"dest": "swap_model",
|
|
"default": False,
|
|
"help": "Swap the model. Instead of A -> B, "
|
|
"swap B -> A"})
|
|
argument_list.append({"opts": ("-S", "--seamless"),
|
|
"action": "store_true",
|
|
"dest": "seamless_clone",
|
|
"default": False,
|
|
"help": "Use cv2's seamless clone function to "
|
|
"remove extreme gradients at the mask "
|
|
"seam by smoothing colors."})
|
|
argument_list.append({"opts": ("-mh", "--match-histogram"),
|
|
"action": "store_true",
|
|
"dest": "match_histogram",
|
|
"default": False,
|
|
"help": "Adjust the histogram of each color "
|
|
"channel in the swapped reconstruction "
|
|
"to equal the histogram of the masked "
|
|
"area in the orginal image"})
|
|
argument_list.append({"opts": ("-aca", "--avg-color-adjust"),
|
|
"action": "store_true",
|
|
"dest": "avg_color_adjust",
|
|
"default": False,
|
|
"help": "Adjust the mean of each color channel "
|
|
" in the swapped reconstruction to "
|
|
"equal the mean of the masked area in "
|
|
"the orginal image"})
|
|
argument_list.append({"opts": ("-dt", "--draw-transparent"),
|
|
"action": "store_true",
|
|
"dest": "draw_transparent",
|
|
"default": False,
|
|
"help": "Place the swapped face on a "
|
|
"transparent layer rather than the "
|
|
"original frame."})
|
|
return argument_list
|
|
|
|
|
|
class TrainArgs(FaceSwapArgs):
|
|
""" Class to parse the command line arguments for training """
|
|
|
|
@staticmethod
|
|
def get_argument_list():
|
|
""" Put the arguments in a list so that they are accessible from both
|
|
argparse and gui """
|
|
argument_list = list()
|
|
argument_list.append({"opts": ("-A", "--input-A"),
|
|
"action": DirFullPaths,
|
|
"dest": "input_a",
|
|
"default": "input_a",
|
|
"help": "Input directory. A directory "
|
|
"containing training images for face A. "
|
|
"Defaults to 'input'"})
|
|
argument_list.append({"opts": ("-B", "--input-B"),
|
|
"action": DirFullPaths,
|
|
"dest": "input_b",
|
|
"default": "input_b",
|
|
"help": "Input directory. A directory "
|
|
"containing training images for face B. "
|
|
"Defaults to 'input'"})
|
|
argument_list.append({"opts": ("-ala", "--alignments-A"),
|
|
"action": FileFullPaths,
|
|
"filetypes": 'alignments',
|
|
"type": str,
|
|
"dest": "alignments_path_a",
|
|
"default": None,
|
|
"help": "Path to alignments file for training set A. Only required "
|
|
"if you are using a masked model or warp-to-landmarks is "
|
|
"enabled. Defaults to <input-A>/alignments.json if not "
|
|
"provided."})
|
|
argument_list.append({"opts": ("-alb", "--alignments-B"),
|
|
"action": FileFullPaths,
|
|
"filetypes": 'alignments',
|
|
"type": str,
|
|
"dest": "alignments_path_b",
|
|
"default": None,
|
|
"help": "Path to alignments file for training set B. Only required "
|
|
"if you are using a masked model or warp-to-landmarks is "
|
|
"enabled. Defaults to <input-B>/alignments.json if not "
|
|
"provided."})
|
|
argument_list.append({"opts": ("-m", "--model-dir"),
|
|
"action": DirFullPaths,
|
|
"dest": "model_dir",
|
|
"default": "models",
|
|
"help": "Model directory. This is where the "
|
|
"training data will be stored. "
|
|
"Defaults to 'model'"})
|
|
argument_list.append({"opts": ("-t", "--trainer"),
|
|
"type": str.lower,
|
|
"choices": PluginLoader.get_available_models(),
|
|
"default": PluginLoader.get_default_model(),
|
|
"help": "Select which trainer to use, Use "
|
|
"LowMem for cards with less than 2GB of "
|
|
"VRAM"})
|
|
argument_list.append({"opts": ("-s", "--save-interval"),
|
|
"type": int,
|
|
"action": Slider,
|
|
"min_max": (10, 1000),
|
|
"rounding": 10,
|
|
"dest": "save_interval",
|
|
"default": 100,
|
|
"help": "Sets the number of iterations before saving the model"})
|
|
argument_list.append({"opts": ("-bs", "--batch-size"),
|
|
"type": int,
|
|
"action": Slider,
|
|
"min_max": (2, 256),
|
|
"rounding": 2,
|
|
"dest": "batch_size",
|
|
"default": 64,
|
|
"help": "Batch size, as a power of 2 (64, 128, 256, etc)"})
|
|
argument_list.append({"opts": ("-it", "--iterations"),
|
|
"type": int,
|
|
"action": Slider,
|
|
"min_max": (0, 5000000),
|
|
"rounding": 20000,
|
|
"default": 1000000,
|
|
"help": "Length of training in iterations."})
|
|
argument_list.append({"opts": ("-g", "--gpus"),
|
|
"type": int,
|
|
"action": Slider,
|
|
"min_max": (1, 10),
|
|
"rounding": 1,
|
|
"default": 1,
|
|
"help": "Number of GPUs to use for training"})
|
|
argument_list.append({"opts": ("-ps", "--preview-scale"),
|
|
"type": int,
|
|
"action": Slider,
|
|
"dest": "preview_scale",
|
|
"min_max": (25, 200),
|
|
"rounding": 25,
|
|
"default": 100,
|
|
"help": "Percentage amount to scale the preview by."})
|
|
argument_list.append({"opts": ("-p", "--preview"),
|
|
"action": "store_true",
|
|
"dest": "preview",
|
|
"default": False,
|
|
"help": "Show preview output. If not specified, "
|
|
"write progress to file"})
|
|
argument_list.append({"opts": ("-w", "--write-image"),
|
|
"action": "store_true",
|
|
"dest": "write_image",
|
|
"default": False,
|
|
"help": "Writes the training result to a file "
|
|
"even on preview mode"})
|
|
argument_list.append({"opts": ("-ag", "--allow-growth"),
|
|
"action": "store_true",
|
|
"dest": "allow_growth",
|
|
"default": False,
|
|
"help": "Sets allow_growth option of Tensorflow "
|
|
"to spare memory on some configs"})
|
|
argument_list.append({"opts": ("-nl", "--no-logs"),
|
|
"action": "store_true",
|
|
"dest": "no_logs",
|
|
"default": False,
|
|
"help": "Disables TensorBoard logging. NB: Disabling logs means "
|
|
"that you will not be able to use the graph or analysis "
|
|
"for this session in the GUI."})
|
|
argument_list.append({"opts": ("-wl", "--warp-to-landmarks"),
|
|
"action": "store_true",
|
|
"dest": "warp_to_landmarks",
|
|
"default": False,
|
|
"help": "Warps training faces to closely matched Landmarks from the "
|
|
"opposite face-set rather than randomly warping the face. "
|
|
"This is the 'dfaker' way of doing warping. Alignments "
|
|
"files for both sets of faces must be provided if using "
|
|
"this option."})
|
|
argument_list.append({"opts": ("-nf", "--no-flip"),
|
|
"action": "store_true",
|
|
"dest": "no_flip",
|
|
"default": False,
|
|
"help": "To effectively learn, a random set of images are flipped "
|
|
"horizontally. Sometimes it is desirable for this not to "
|
|
"occur. Generally this should be left off except for "
|
|
"during 'fit training'."})
|
|
argument_list.append({"opts": ("-tia", "--timelapse-input-A"),
|
|
"action": DirFullPaths,
|
|
"dest": "timelapse_input_a",
|
|
"default": None,
|
|
"help": "For if you want a timelapse: "
|
|
"The input folder for the timelapse. "
|
|
"This folder should contain faces of A "
|
|
"which will be converted for the "
|
|
"timelapse. You must supply a "
|
|
"--timelapse-output and a "
|
|
"--timelapse-input-B parameter."})
|
|
argument_list.append({"opts": ("-tib", "--timelapse-input-B"),
|
|
"action": DirFullPaths,
|
|
"dest": "timelapse_input_b",
|
|
"default": None,
|
|
"help": "For if you want a timelapse: "
|
|
"The input folder for the timelapse. "
|
|
"This folder should contain faces of B "
|
|
"which will be converted for the "
|
|
"timelapse. You must supply a "
|
|
"--timelapse-output and a "
|
|
"--timelapse-input-A parameter."})
|
|
argument_list.append({"opts": ("-to", "--timelapse-output"),
|
|
"action": DirFullPaths,
|
|
"dest": "timelapse_output",
|
|
"default": None,
|
|
"help": "The output folder for the timelapse. "
|
|
"If the input folders are supplied but "
|
|
"no output folder, it will default to "
|
|
"your model folder /timelapse/"})
|
|
return argument_list
|
|
|
|
|
|
class GuiArgs(FaceSwapArgs):
|
|
""" Class to parse the command line arguments for training """
|
|
|
|
@staticmethod
|
|
def get_argument_list():
|
|
""" Put the arguments in a list so that they are accessible from both
|
|
argparse and gui """
|
|
argument_list = []
|
|
argument_list.append({"opts": ("-d", "--debug"),
|
|
"action": "store_true",
|
|
"dest": "debug",
|
|
"default": False,
|
|
"help": "Output to Shell console instead of "
|
|
"GUI console"})
|
|
return argument_list
|