├── config └── .keep ├── lib ├── __init__.py ├── model │ ├── __init__.py │ └── optimizers.py ├── gui │ ├── .cache │ │ ├── preview │ │ │ └── .keep │ │ └── icons │ │ │ ├── logo.png │ │ │ ├── move.png │ │ │ ├── save.png │ │ │ ├── zoom.png │ │ │ ├── clear.png │ │ │ ├── graph.png │ │ │ ├── reset.png │ │ │ ├── open_file.png │ │ │ └── open_folder.png │ ├── __init__.py │ ├── statusbar.py │ ├── _config.py │ └── display.py ├── plaidml_utils.py ├── Serializer.py ├── align_eyes.py ├── keypress.py ├── queue_manager.py ├── umeyama.py ├── vgg_face2_keras.py └── vgg_face.py ├── tools ├── __init__.py ├── lib_alignments │ ├── __init__.py │ └── annotate.py ├── restore.py └── alignments.py ├── plugins ├── __init__.py ├── convert │ ├── __init__.py │ ├── color │ │ ├── __init__.py │ │ ├── avg_color.py │ │ ├── match_hist.py │ │ ├── seamless_clone.py │ │ ├── manual_balance.py │ │ ├── _base.py │ │ ├── match_hist_defaults.py │ │ └── color_transfer_defaults.py │ ├── mask │ │ ├── __init__.py │ │ ├── box_blend.py │ │ ├── mask_blend.py │ │ ├── mask_blend_defaults.py │ │ └── box_blend_defaults.py │ ├── scaling │ │ ├── __init__.py │ │ ├── _base.py │ │ └── sharpen.py │ ├── writer │ │ ├── __init__.py │ │ ├── opencv.py │ │ ├── pillow.py │ │ ├── _base.py │ │ ├── gif.py │ │ ├── gif_defaults.py │ │ └── opencv_defaults.py │ └── _config.py ├── extract │ ├── .cache │ │ └── .keep │ ├── __init__.py │ ├── align │ │ ├── __init__.py │ │ └── .cache │ │ │ └── .keep │ ├── detect │ │ ├── .cache │ │ │ └── .keep │ │ ├── __init__.py │ │ ├── manual.py │ │ ├── s3fd_defaults.py │ │ ├── cv2_dnn_defaults.py │ │ ├── s3fd_amd_defaults.py │ │ ├── cv2_dnn.py │ │ └── mtcnn_defaults.py │ └── _config.py ├── train │ ├── model │ │ ├── __init__.py │ │ ├── dfaker.py │ │ ├── lightweight.py │ │ ├── dfl_h128.py │ │ ├── original_defaults.py │ │ ├── dfl_h128_defaults.py │ │ ├── villain_defaults.py │ │ ├── original.py │ │ ├── iae.py │ │ ├── villain.py │ │ ├── realface_defaults.py │ │ └── dfl_sae_defaults.py │ └── trainer │ │ ├── __init__.py │ │ └── original.py └── plugin_loader.py ├── scripts └── __init__.py ├── .dockerignore ├── _config.yml ├── setup.cfg ├── .install └── windows │ ├── fs_logo.ico │ ├── fs_logo_32.ico │ ├── git_install.inf │ └── MultiDetailPrint.nsi ├── Dockerfile.cpu ├── .gitignore ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── ISSUE_TEMPLATE.md ├── update_deps.py ├── Dockerfile.gpu ├── requirements.txt ├── faceswap.py ├── tools.py ├── .travis.yml └── CODE_OF_CONDUCT.md /config/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/model/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/gui/.cache/preview/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/convert/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/extract/.cache/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/extract/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !requirements* -------------------------------------------------------------------------------- /plugins/convert/color/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/convert/mask/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/extract/align/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/train/model/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/train/trainer/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /plugins/convert/scaling/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/convert/writer/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/extract/align/.cache/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/extract/detect/.cache/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /plugins/extract/detect/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 99 3 | exclude = .git, __pycache__ 4 | -------------------------------------------------------------------------------- /.install/windows/fs_logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowkeys/faceswap/master/.install/windows/fs_logo.ico -------------------------------------------------------------------------------- /lib/gui/.cache/icons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowkeys/faceswap/master/lib/gui/.cache/icons/logo.png -------------------------------------------------------------------------------- /lib/gui/.cache/icons/move.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowkeys/faceswap/master/lib/gui/.cache/icons/move.png -------------------------------------------------------------------------------- /lib/gui/.cache/icons/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowkeys/faceswap/master/lib/gui/.cache/icons/save.png -------------------------------------------------------------------------------- /lib/gui/.cache/icons/zoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowkeys/faceswap/master/lib/gui/.cache/icons/zoom.png -------------------------------------------------------------------------------- /.install/windows/fs_logo_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowkeys/faceswap/master/.install/windows/fs_logo_32.ico -------------------------------------------------------------------------------- /lib/gui/.cache/icons/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowkeys/faceswap/master/lib/gui/.cache/icons/clear.png -------------------------------------------------------------------------------- /lib/gui/.cache/icons/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowkeys/faceswap/master/lib/gui/.cache/icons/graph.png -------------------------------------------------------------------------------- /lib/gui/.cache/icons/reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowkeys/faceswap/master/lib/gui/.cache/icons/reset.png -------------------------------------------------------------------------------- /lib/gui/.cache/icons/open_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowkeys/faceswap/master/lib/gui/.cache/icons/open_file.png -------------------------------------------------------------------------------- /lib/gui/.cache/icons/open_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cowkeys/faceswap/master/lib/gui/.cache/icons/open_folder.png -------------------------------------------------------------------------------- /tools/lib_alignments/__init__.py: -------------------------------------------------------------------------------- 1 | from tools.lib_alignments.media import AlignmentData, ExtractedFaces, Faces, Frames 2 | from tools.lib_alignments.annotate import Annotate 3 | from tools.lib_alignments.jobs import Check, Draw, Extract, Legacy, Merge, Reformat, RemoveAlignments, Rename, Sort, Spatial, UpdateHashes 4 | from tools.lib_alignments.jobs_manual import Manual 5 | -------------------------------------------------------------------------------- /plugins/train/trainer/original.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Original Trainer """ 3 | 4 | from ._base import TrainerBase 5 | 6 | 7 | class Trainer(TrainerBase): 8 | """ Original is currently identical to Base """ 9 | def __init__(self, *args, **kwargs): # pylint:disable=useless-super-delegation 10 | super().__init__(*args, **kwargs) 11 | -------------------------------------------------------------------------------- /lib/gui/__init__.py: -------------------------------------------------------------------------------- 1 | from lib.gui.command import CommandNotebook 2 | from lib.gui.display import DisplayNotebook 3 | from lib.gui.options import CliOptions 4 | from lib.gui.menu import MainMenuBar 5 | from lib.gui.popup_configure import popup_config 6 | from lib.gui.stats import Session 7 | from lib.gui.statusbar import StatusBar 8 | from lib.gui.utils import ConsoleOut, get_config, get_images, initialize_config, initialize_images 9 | from lib.gui.wrapper import ProcessWrapper 10 | -------------------------------------------------------------------------------- /.install/windows/git_install.inf: -------------------------------------------------------------------------------- 1 | [Setup] 2 | Lang=default 3 | Dir=C:\Program Files\Git 4 | Group=Git 5 | NoIcons=0 6 | SetupType=default 7 | Components=ext,ext\shellhere,ext\guihere,gitlfs,assoc,assoc_sh 8 | Tasks= 9 | EditorOption=VisualStudioCode 10 | CustomEditorPath= 11 | PathOption=Cmd 12 | SSHOption=OpenSSH 13 | CURLOption=OpenSSL 14 | CRLFOption=CRLFAlways 15 | BashTerminalOption=MinTTY 16 | PerformanceTweaksFSCache=Enabled 17 | UseCredentialManager=Enabled 18 | EnableSymlinks=Disabled 19 | -------------------------------------------------------------------------------- /Dockerfile.cpu: -------------------------------------------------------------------------------- 1 | FROM tensorflow/tensorflow:1.12.0-py3 2 | 3 | RUN add-apt-repository -y ppa:jonathonf/ffmpeg-4 \ 4 | && apt-get update -qq -y \ 5 | && apt-get install -y libsm6 libxrender1 libxext-dev python3-tk ffmpeg git \ 6 | && apt-get clean \ 7 | && rm -rf /var/lib/apt/lists/* 8 | 9 | COPY requirements.txt /opt/ 10 | RUN pip3 install --upgrade pip 11 | RUN pip3 --no-cache-dir install -r /opt/requirements.txt && rm /opt/requirements.txt 12 | 13 | WORKDIR "/srv" 14 | CMD ["/bin/bash"] 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !setup.cfg 3 | !*.ico 4 | !*.inf 5 | !*.keep 6 | !*.md 7 | !*.nsi 8 | !*.png 9 | !*.py 10 | !*.txt 11 | !.cache 12 | !Dockerfile* 13 | !requirements* 14 | !.install/ 15 | !.install/windows 16 | !config/ 17 | !lib/ 18 | !lib/* 19 | !lib/gui 20 | !lib/gui/.cache/preview 21 | !lib/gui/.cache/icons 22 | !scripts 23 | !plugins/ 24 | !plugins/* 25 | !plugins/extract/* 26 | !plugins/train/* 27 | !plugins/convert/* 28 | !tools 29 | !tools/lib* 30 | !.travis.yml 31 | *.ini 32 | *.pyc 33 | __pycache__/ 34 | -------------------------------------------------------------------------------- /plugins/convert/color/avg_color.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Average colour adjustment color matching adjustment plugin for faceswap.py converter """ 3 | 4 | import numpy as np 5 | from ._base import Adjustment 6 | 7 | 8 | class Color(Adjustment): 9 | """ Adjust the mean of the color channels to be the same for the swap and old frame """ 10 | 11 | @staticmethod 12 | def process(old_face, new_face, raw_mask): 13 | for _ in [0, 1]: 14 | diff = old_face - new_face 15 | avg_diff = np.sum(diff * raw_mask, axis=(0, 1)) 16 | adjustment = avg_diff / np.sum(raw_mask, axis=(0, 1)) 17 | new_face += adjustment 18 | return new_face 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /lib/plaidml_utils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Multiple plaidml implementation. 3 | ''' 4 | 5 | import plaidml 6 | 7 | 8 | def pad(data, paddings, mode="CONSTANT", name=None, constant_value=0): 9 | """ PlaidML Pad """ 10 | # TODO: use / impl other padding method when required 11 | # CONSTANT -> SpatialPadding ? | Doesn't support first and last axis + 12 | # no support for constant_value 13 | # SYMMETRIC -> Requires impl ? 14 | if mode.upper() != "REFLECT": 15 | raise NotImplementedError("pad only supports mode == 'REFLECT'") 16 | if constant_value != 0: 17 | raise NotImplementedError("pad does not support constant_value != 0") 18 | return plaidml.op.reflection_padding(data, paddings) 19 | -------------------------------------------------------------------------------- /update_deps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Installs any required third party libs for faceswap.py 3 | 4 | Checks for installed Conda / Pip packages and updates accordingly 5 | """ 6 | 7 | from setup import Environment, Install, Output 8 | 9 | _LOGGER = None 10 | 11 | 12 | def output(msg): 13 | """ Output to print or logger """ 14 | if _LOGGER is not None: 15 | _LOGGER.info(msg) 16 | else: 17 | Output().info(msg) 18 | 19 | 20 | def main(logger=None): 21 | """ Check for and update dependencies """ 22 | if logger is not None: 23 | global _LOGGER # pylint:disable=global-statement 24 | _LOGGER = logger 25 | output("Updating dependencies...") 26 | update = Environment(logger=logger, updater=True) 27 | Install(update) 28 | output("Dependencies updated") 29 | 30 | 31 | if __name__ == "__main__": 32 | main() 33 | -------------------------------------------------------------------------------- /Dockerfile.gpu: -------------------------------------------------------------------------------- 1 | FROM tensorflow/tensorflow:1.12.0-gpu-py3 2 | 3 | RUN add-apt-repository -y ppa:jonathonf/ffmpeg-4 \ 4 | && apt-get update -qq -y \ 5 | && apt-get install -y libsm6 libxrender1 libxext-dev python3-tk ffmpeg git \ 6 | && apt-get clean \ 7 | && rm -rf /var/lib/apt/lists/* 8 | 9 | COPY requirements.txt /opt/ 10 | RUN pip3 install --upgrade pip 11 | RUN pip3 --no-cache-dir install -r /opt/requirements.txt && rm /opt/requirements.txt 12 | RUN pip3 install jupyter matplotlib 13 | RUN pip3 install jupyter_http_over_ws 14 | RUN jupyter serverextension enable --py jupyter_http_over_ws 15 | # patch for tensorflow:latest-gpu-py3 image 16 | RUN cd /usr/local/cuda/lib64 \ 17 | && mv stubs/libcuda.so ./ \ 18 | && ln -s libcuda.so libcuda.so.1 \ 19 | && ldconfig 20 | 21 | WORKDIR "/notebooks" 22 | CMD ["jupyter-notebook", "--allow-root" ,"--port=8888" ,"--no-browser" ,"--ip=0.0.0.0"] 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | tqdm 2 | psutil 3 | pathlib 4 | numpy==1.16.2 5 | opencv-python 6 | scikit-image 7 | Pillow==6.1.0 8 | scikit-learn 9 | toposort 10 | fastcluster 11 | matplotlib==2.2.2 12 | imageio==2.5.0 13 | imageio-ffmpeg 14 | ffmpy==0.2.2 15 | # Revert back to nvidia-ml-py3 when windows/system32 patch is implemented 16 | git+https://github.com/deepfakes/nvidia-ml-py3.git 17 | #nvidia-ml-py3 18 | h5py==2.9.0 19 | Keras==2.2.4 20 | pywin32 ; sys_platform == "win32" 21 | pynvx==0.0.4 ; sys_platform == "darwin" 22 | 23 | # tensorflow is included within the docker image. 24 | # If you are looking for dependencies for a manual install, 25 | 26 | # NB: Tensorflow version 1.12 is the minimum supported version of Tensorflow. 27 | # If your graphics card support is below Cuda 9.0 you will need to either 28 | # compile tensorflow yourself or download a custom version. 29 | # Install 1.12.0<=tensorflow-gpu<=1.13.0 for CUDA 9.0 30 | # or 1.13.1<=tensorflow-gpu<1.15 for CUDA 10.0 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /plugins/extract/detect/manual.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Manual face detection plugin """ 3 | 4 | from ._base import Detector, logger 5 | 6 | 7 | class Detect(Detector): 8 | """ Manual Detector """ 9 | def __init__(self, **kwargs): 10 | super().__init__(**kwargs) 11 | 12 | def initialize(self, *args, **kwargs): 13 | """ Create the mtcnn detector """ 14 | super().initialize(*args, **kwargs) 15 | logger.info("Initializing Manual Detector...") 16 | self.init.set() 17 | logger.info("Initialized Manual Detector.") 18 | 19 | def detect_faces(self, *args, **kwargs): 20 | """ Return the given bounding box in a bounding box dict """ 21 | super().detect_faces(*args, **kwargs) 22 | while True: 23 | item = self.get_item() 24 | if item == "EOF": 25 | break 26 | face = item["face"] 27 | 28 | bounding_box = [self.to_bounding_box_dict(face[0], face[1], face[2], face[3])] 29 | item["detected_faces"] = bounding_box 30 | self.finalize(item) 31 | 32 | self.queues["out"].put("EOF") 33 | -------------------------------------------------------------------------------- /faceswap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ The master faceswap.py script """ 3 | import sys 4 | 5 | import lib.cli as cli 6 | 7 | if sys.version_info[0] < 3: 8 | raise Exception("This program requires at least python3.2") 9 | if sys.version_info[0] == 3 and sys.version_info[1] < 2: 10 | raise Exception("This program requires at least python3.2") 11 | 12 | 13 | def bad_args(args): 14 | """ Print help on bad arguments """ 15 | PARSER.print_help() 16 | exit(0) 17 | 18 | 19 | if __name__ == "__main__": 20 | PARSER = cli.FullHelpArgumentParser() 21 | SUBPARSER = PARSER.add_subparsers() 22 | EXTRACT = cli.ExtractArgs(SUBPARSER, 23 | "extract", 24 | "Extract the faces from pictures") 25 | TRAIN = cli.TrainArgs(SUBPARSER, 26 | "train", 27 | "This command trains the model for the two faces A and B") 28 | CONVERT = cli.ConvertArgs(SUBPARSER, 29 | "convert", 30 | "Convert a source image to a new one with the face swapped") 31 | GUI = cli.GuiArgs(SUBPARSER, 32 | "gui", 33 | "Launch the Faceswap Graphical User Interface") 34 | PARSER.set_defaults(func=bad_args) 35 | ARGUMENTS = PARSER.parse_args() 36 | ARGUMENTS.func(ARGUMENTS) 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Note: Please only report bugs in this repository. Just because you are getting an error message does not automatically mean you have discovered a bug. If you don't have a lot of experience with this type of project, or if you need for setup help and other issues in using the faceswap tool, please refer to the [faceswap Forum](https://faceswap.dev/forum) instead. The [faceswap Forum](https://faceswap.dev/forum) is also an excellent place to ask questions and submit feedback. Non-bugs are likely to be closed without response.** 2 | 3 | **Please always attach your generated crash_report.log to any bug report. Failure to attach this report may lead to your issue being closed without response.** 4 | 5 | ## Expected behavior 6 | 7 | *Describe, in some detail, what you are trying to do and what the output is that you expect from the program.* 8 | 9 | ## Actual behavior 10 | 11 | *Describe, in some detail, what the program does instead. Be sure to include any error message or screenshots.* 12 | 13 | ## Steps to reproduce 14 | 15 | *Describe, in some detail, the steps you tried that resulted in the behavior described above.* 16 | 17 | ## Other relevant information 18 | - **Command lined used (if not specified in steps to reproduce)**: faceswap.py ... 19 | - **Operating system and version:** Windows, macOS, Linux 20 | - **Python version:** 2.7, 3.5, 3.6.4, ... 21 | - **Faceswap version:** commit hash or version number 22 | - **Faceswap method:** CPU/GPU 23 | - **Other related issues:** #123, #124... 24 | - ... (for example, installed packages that you can see with `pip freeze`) 25 | -------------------------------------------------------------------------------- /plugins/convert/color/match_hist.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Match histogram colour adjustment color matching adjustment plugin 3 | for faceswap.py converter """ 4 | 5 | import numpy as np 6 | from ._base import Adjustment 7 | 8 | 9 | class Color(Adjustment): 10 | """ Match the histogram of the color intensity of each channel """ 11 | 12 | def process(self, old_face, new_face, raw_mask): 13 | mask_indices = np.nonzero(raw_mask.squeeze()) 14 | new_face = [self.hist_match(old_face[:, :, c], 15 | new_face[:, :, c], 16 | mask_indices, 17 | self.config["threshold"] / 100) 18 | for c in range(3)] 19 | new_face = np.stack(new_face, axis=-1) 20 | return new_face 21 | 22 | @staticmethod 23 | def hist_match(old_channel, new_channel, mask_indices, threshold): 24 | """ Construct the histogram of the color intensity of a channel 25 | for the swap and the original. Match the histogram of the original 26 | by interpolation 27 | """ 28 | if mask_indices[0].size == 0: 29 | return new_channel 30 | 31 | old_masked = old_channel[mask_indices] 32 | new_masked = new_channel[mask_indices] 33 | _, bin_idx, s_counts = np.unique(new_masked, return_inverse=True, return_counts=True) 34 | t_values, t_counts = np.unique(old_masked, return_counts=True) 35 | s_quants = np.cumsum(s_counts, dtype='float32') 36 | t_quants = np.cumsum(t_counts, dtype='float32') 37 | s_quants = threshold * s_quants / s_quants[-1] # cdf 38 | t_quants /= t_quants[-1] # cdf 39 | interp_s_values = np.interp(s_quants, t_quants, t_values) 40 | new_channel[mask_indices] = interp_s_values[bin_idx] 41 | return new_channel 42 | -------------------------------------------------------------------------------- /tools/restore.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Tool to restore models from backup """ 3 | 4 | import logging 5 | import os 6 | 7 | from lib.model.backup_restore import Backup 8 | 9 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 10 | 11 | 12 | class Restore(): 13 | """ Restore a model from backup """ 14 | 15 | def __init__(self, arguments): 16 | logger.debug("Initializing %s: (arguments: '%s'", self.__class__.__name__, arguments) 17 | self.model_dir = arguments.model_dir 18 | self.model_name = None 19 | 20 | def process(self): 21 | """ Perform the Restore process """ 22 | logger.info("Starting Model Restore...") 23 | self.validate() 24 | backup = Backup(self.model_dir, self.model_name) 25 | backup.restore() 26 | logger.info("Completed Model Restore") 27 | 28 | def validate(self): 29 | """ Make sure there is only one model in the target folder """ 30 | if not os.path.exists(self.model_dir): 31 | logger.error("Folder does not exist: '%s'", self.model_dir) 32 | exit(1) 33 | chkfiles = [fname for fname in os.listdir(self.model_dir) if fname.endswith("_state.json")] 34 | bkfiles = [fname for fname in os.listdir(self.model_dir) if fname.endswith(".bk")] 35 | if not chkfiles: 36 | logger.error("Could not find a model in the supplied folder: '%s'", self.model_dir) 37 | exit(1) 38 | if len(chkfiles) > 1: 39 | logger.error("More than one model found in the supplied folder: '%s'", self.model_dir) 40 | exit(1) 41 | if not bkfiles: 42 | logger.error("Could not find any backup files in the supplied folder: '%s'", 43 | self.model_dir) 44 | exit(1) 45 | self.model_name = chkfiles[0].replace("_state.json", "") 46 | logger.info("%s Model found", self.model_name.title()) 47 | logger.verbose("Backup files: %s)", bkfiles) 48 | -------------------------------------------------------------------------------- /plugins/extract/_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Default configurations for extract """ 3 | 4 | import logging 5 | import os 6 | import sys 7 | 8 | from importlib import import_module 9 | from lib.config import FaceswapConfig 10 | from lib.utils import full_path_split 11 | 12 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 13 | 14 | 15 | class Config(FaceswapConfig): 16 | """ Config File for Models """ 17 | 18 | def set_defaults(self): 19 | """ Set the default values for config """ 20 | logger.debug("Setting defaults") 21 | current_dir = os.path.dirname(__file__) 22 | for dirpath, _, filenames in os.walk(current_dir): 23 | default_files = [fname for fname in filenames if fname.endswith("_defaults.py")] 24 | if not default_files: 25 | continue 26 | base_path = os.path.dirname(os.path.realpath(sys.argv[0])) 27 | import_path = ".".join(full_path_split(dirpath.replace(base_path, ""))[1:]) 28 | plugin_type = import_path.split(".")[-1] 29 | for filename in default_files: 30 | self.load_module(filename, import_path, plugin_type) 31 | 32 | def load_module(self, filename, module_path, plugin_type): 33 | """ Load the defaults module and add defaults """ 34 | logger.debug("Adding defaults: (filename: %s, module_path: %s, plugin_type: %s", 35 | filename, module_path, plugin_type) 36 | module = os.path.splitext(filename)[0] 37 | section = ".".join((plugin_type, module.replace("_defaults", ""))) 38 | logger.debug("Importing defaults module: %s.%s", module_path, module) 39 | mod = import_module("{}.{}".format(module_path, module)) 40 | self.add_section(title=section, info=mod._HELPTEXT) # pylint:disable=protected-access 41 | for key, val in mod._DEFAULTS.items(): # pylint:disable=protected-access 42 | self.add_item(section=section, title=key, **val) 43 | logger.debug("Added defaults: %s", section) 44 | -------------------------------------------------------------------------------- /plugins/convert/_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Default configurations for convert """ 3 | 4 | import logging 5 | import os 6 | import sys 7 | 8 | from importlib import import_module 9 | 10 | from lib.config import FaceswapConfig 11 | from lib.utils import full_path_split 12 | 13 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 14 | 15 | 16 | class Config(FaceswapConfig): 17 | """ Config File for Convert """ 18 | 19 | def set_defaults(self): 20 | """ Set the default values for config """ 21 | logger.debug("Setting defaults") 22 | current_dir = os.path.dirname(__file__) 23 | for dirpath, _, filenames in os.walk(current_dir): 24 | default_files = [fname for fname in filenames if fname.endswith("_defaults.py")] 25 | if not default_files: 26 | continue 27 | base_path = os.path.dirname(os.path.realpath(sys.argv[0])) 28 | import_path = ".".join(full_path_split(dirpath.replace(base_path, ""))[1:]) 29 | plugin_type = import_path.split(".")[-1] 30 | for filename in default_files: 31 | self.load_module(filename, import_path, plugin_type) 32 | 33 | def load_module(self, filename, module_path, plugin_type): 34 | """ Load the defaults module and add defaults """ 35 | logger.debug("Adding defaults: (filename: %s, module_path: %s, plugin_type: %s", 36 | filename, module_path, plugin_type) 37 | module = os.path.splitext(filename)[0] 38 | section = ".".join((plugin_type, module.replace("_defaults", ""))) 39 | logger.debug("Importing defaults module: %s.%s", module_path, module) 40 | mod = import_module("{}.{}".format(module_path, module)) 41 | self.add_section(title=section, info=mod._HELPTEXT) # pylint:disable=protected-access 42 | for key, val in mod._DEFAULTS.items(): # pylint:disable=protected-access 43 | self.add_item(section=section, title=key, **val) 44 | logger.debug("Added defaults: %s", section) 45 | -------------------------------------------------------------------------------- /tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ The master tools.py script """ 3 | import sys 4 | # Importing the various tools 5 | import tools.cli as cli 6 | from lib.cli import FullHelpArgumentParser 7 | 8 | # Python version check 9 | if sys.version_info[0] < 3: 10 | raise Exception("This program requires at least python3.2") 11 | if sys.version_info[0] == 3 and sys.version_info[1] < 2: 12 | raise Exception("This program requires at least python3.2") 13 | 14 | 15 | def bad_args(args): # pylint:disable=unused-argument 16 | """ Print help on bad arguments """ 17 | PARSER.print_help() 18 | exit(0) 19 | 20 | 21 | if __name__ == "__main__": 22 | _TOOLS_WARNING = "Please backup your data and/or test the tool you want " 23 | _TOOLS_WARNING += "to use with a smaller data set to make sure you " 24 | _TOOLS_WARNING += "understand how it works." 25 | print(_TOOLS_WARNING) 26 | 27 | PARSER = FullHelpArgumentParser() 28 | SUBPARSER = PARSER.add_subparsers() 29 | ALIGN = cli.AlignmentsArgs(SUBPARSER, 30 | "alignments", 31 | "This command lets you perform various tasks pertaining to an " 32 | "alignments file.") 33 | PREVIEW = cli.PreviewArgs(SUBPARSER, 34 | "preview", 35 | "This command allows you to preview swaps to tweak convert " 36 | "settings.") 37 | EFFMPEG = cli.EffmpegArgs(SUBPARSER, 38 | "effmpeg", 39 | "This command allows you to easily execute common ffmpeg tasks.") 40 | RESTORE = cli.RestoreArgs(SUBPARSER, 41 | "restore", 42 | "This command lets you restore models from backup.") 43 | SORT = cli.SortArgs(SUBPARSER, 44 | "sort", 45 | "This command lets you sort images using various methods.") 46 | PARSER.set_defaults(func=bad_args) 47 | ARGUMENTS = PARSER.parse_args() 48 | ARGUMENTS.func(ARGUMENTS) 49 | -------------------------------------------------------------------------------- /plugins/convert/color/seamless_clone.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Seamless clone adjustment plugin for faceswap.py converter 3 | NB: This probably isn't the best place for this, but it is independent of 4 | color adjustments and does not have a natural home, so here for now 5 | and called as an extra plugin from lib/convert.py 6 | """ 7 | 8 | import cv2 9 | import numpy as np 10 | from ._base import Adjustment 11 | 12 | 13 | class Color(Adjustment): 14 | """ Seamless clone the swapped face into the old face with cv2 15 | NB: This probably isn't the best place for this, but it doesn't work well and 16 | and does not have a natural home, so here for now. 17 | """ 18 | 19 | @staticmethod 20 | def process(old_face, new_face, raw_mask): 21 | height, width, _ = old_face.shape 22 | height = height // 2 23 | width = width // 2 24 | 25 | y_indices, x_indices, _ = np.nonzero(raw_mask) 26 | y_crop = slice(np.min(y_indices), np.max(y_indices)) 27 | x_crop = slice(np.min(x_indices), np.max(x_indices)) 28 | y_center = int(np.rint((np.max(y_indices) + np.min(y_indices)) / 2 + height)) 29 | x_center = int(np.rint((np.max(x_indices) + np.min(x_indices)) / 2 + width)) 30 | 31 | insertion = np.rint(new_face[y_crop, x_crop] * 255.0).astype("uint8") 32 | insertion_mask = np.rint(raw_mask[y_crop, x_crop] * 255.0).astype("uint8") 33 | insertion_mask[insertion_mask != 0] = 255 34 | prior = np.rint(np.pad(old_face * 255.0, 35 | ((height, height), (width, width), (0, 0)), 36 | 'constant')).astype("uint8") 37 | 38 | blended = cv2.seamlessClone(insertion, # pylint: disable=no-member 39 | prior, 40 | insertion_mask, 41 | (x_center, y_center), 42 | cv2.NORMAL_CLONE) # pylint: disable=no-member 43 | blended = blended[height:-height, width:-width] 44 | 45 | return blended.astype("float32") / 255.0 46 | -------------------------------------------------------------------------------- /plugins/convert/mask/box_blend.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Adjustments for the swap box for faceswap.py converter """ 3 | 4 | import numpy as np 5 | 6 | from ._base import Adjustment, BlurMask, logger 7 | 8 | 9 | class Mask(Adjustment): 10 | """ Manipulations that occur on the swap box 11 | Actions performed here occur prior to warping the face back to the background frame 12 | 13 | For actions that occur identically for each frame (e.g. blend_box), constants can 14 | be placed into self.func_constants to be compiled at launch, then referenced for 15 | each face. """ 16 | def __init__(self, mask_type, output_size, predicted_available=False, **kwargs): 17 | super().__init__(mask_type, output_size, predicted_available, **kwargs) 18 | self.mask = self.get_mask() if not self.skip else None 19 | 20 | def get_mask(self): 21 | """ The box for every face will be identical, so set the mask just once 22 | As gaussian blur technically blurs both sides of the mask, reduce the mask ratio by 23 | half to give a more expected box """ 24 | logger.debug("Building box mask") 25 | mask_ratio = self.config["distance"] / 200 26 | facesize = self.dummy.shape[0] 27 | erode = slice(round(facesize * mask_ratio), -round(facesize * mask_ratio)) 28 | mask = self.dummy[:, :, -1] 29 | mask[erode, erode] = 1.0 30 | 31 | mask = BlurMask(self.config["type"], 32 | mask, 33 | self.config["radius"], 34 | self.config["passes"]).blurred 35 | logger.debug("Built box mask. Shape: %s", mask.shape) 36 | return mask 37 | 38 | def process(self, new_face): 39 | """ The blend box function. Adds the created mask to the alpha channel """ 40 | if self.skip: 41 | logger.trace("Skipping blend box") 42 | return new_face 43 | 44 | logger.trace("Blending box") 45 | mask = np.expand_dims(self.mask, axis=-1) 46 | new_face = np.clip(np.concatenate((new_face, mask), axis=-1), 0.0, 1.0) 47 | logger.trace("Blended box") 48 | return new_face 49 | -------------------------------------------------------------------------------- /plugins/convert/color/manual_balance.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Manual Balance colour adjustment plugin for faceswap.py converter """ 3 | 4 | import cv2 5 | import numpy as np 6 | from ._base import Adjustment 7 | 8 | 9 | class Color(Adjustment): 10 | """ Adjust the mean of the color channels to be the same for the swap and old frame """ 11 | 12 | def process(self, old_face, new_face, raw_mask): 13 | image = self.convert_colorspace(new_face * 255.0) 14 | adjustment = np.array([self.config["balance_1"] / 100.0, 15 | self.config["balance_2"] / 100.0, 16 | self.config["balance_3"] / 100.0]).astype("float32") 17 | for idx in range(3): 18 | if adjustment[idx] >= 0: 19 | image[:, :, idx] = ((1 - image[:, :, idx]) * adjustment[idx]) + image[:, :, idx] 20 | else: 21 | image[:, :, idx] = image[:, :, idx] * (1 + adjustment[idx]) 22 | 23 | image = self.convert_colorspace(image * 255.0, to_bgr=True) 24 | image = self.adjust_contrast(image) 25 | return image 26 | 27 | def adjust_contrast(self, image): 28 | """ 29 | Adjust image contrast and brightness. 30 | """ 31 | contrast = max(-126, int(round(self.config["contrast"] * 1.27))) 32 | brightness = max(-126, int(round(self.config["brightness"] * 1.27))) 33 | 34 | if not contrast and not brightness: 35 | return image 36 | 37 | image = np.rint(image * 255.0).astype("uint8") 38 | image = np.clip(image * (contrast/127+1) - contrast + brightness, 0, 255) 39 | image = np.clip(np.divide(image, 255, dtype=np.float32), .0, 1.0) 40 | return image 41 | 42 | def convert_colorspace(self, new_face, to_bgr=False): 43 | """ Convert colorspace based on mode or back to bgr """ 44 | mode = self.config["colorspace"].lower() 45 | colorspace = "YCrCb" if mode == "ycrcb" else mode.upper() 46 | conversion = "{}2BGR".format(colorspace) if to_bgr else "BGR2{}".format(colorspace) 47 | image = cv2.cvtColor(new_face.astype("uint8"), # pylint: disable=no-member 48 | getattr(cv2, "COLOR_{}".format(conversion))).astype("float32") / 255.0 49 | return image 50 | -------------------------------------------------------------------------------- /.install/windows/MultiDetailPrint.nsi: -------------------------------------------------------------------------------- 1 | ; Function: MultiDetailPrint 2 | ; Written by: M. Mims 3 | ; Date: 9.29.2006 4 | ; Description: Parses strings with return, newline, and tab characters to print 5 | ; correctly to the details screen. 6 | Function MultiDetailPrint 7 | Exch $R0 8 | Push $R1 9 | Push $R2 10 | Push $R3 11 | Push $R4 12 | StrLen $R1 $R0 ; Get length of string 13 | StrCpy $R2 -1 ; Set character index 14 | 15 | loop: 16 | IntOp $R2 $R2 + 1 ; Increase character index 17 | StrCmp $R1 $R2 finish_trim ; Finish if at end of string 18 | StrCpy $R3 $R0 1 $R2 ; Get character at given index 19 | StrCmp $R3 "$\r" r_trim_needed 20 | StrCmp $R3 "$\t" t_trim_needed 21 | StrCmp $R3 "$\n" n_trim_needed loop 22 | 23 | r_trim_needed: 24 | StrCpy $R3 $R0 $R2 ; Copy left side of string 25 | IntOp $R4 $R2 + 1 ; Get index of next character 26 | StrCpy $R0 $R0 $R1 $R4 ; Copy right side of string 27 | StrCpy $R0 "$R3$R0" ; Merge string without \r 28 | IntOp $R1 $R1 - 1 ; Decrease string length 29 | IntOp $R2 $R2 - 1 ; Decrease index 30 | goto loop 31 | 32 | t_trim_needed: 33 | StrCpy $R3 $R0 $R2 ; Copy left side 34 | IntOp $R4 $R2 + 1 ; Index of next character 35 | StrCpy $R0 $R0 $R1 $R4 ; Copy right side 36 | StrCpy $R0 "$R3 $R0" ; Merge with spaces 37 | IntOp $R1 $R1 + 7 ; Increase string length 38 | IntOp $R2 $R2 + 7 ; Increase index 39 | goto loop 40 | 41 | n_trim_needed: 42 | StrCpy $R3 $R0 $R2 ; Copy left side 43 | IntOp $R4 $R2 + 1 ; Index of next character 44 | StrCpy $R0 $R0 $R1 $R4 ; Copy right side 45 | DetailPrint $R3 ; Print line 46 | IntOp $R1 $R1 - $R2 ; Adjust string length 47 | IntOp $R1 $R1 - 1 48 | StrCpy $R2 -1 ; Adjust index 49 | goto loop 50 | 51 | finish_trim: 52 | DetailPrint $R0 ; Print final line 53 | Pop $R4 54 | Pop $R3 55 | Pop $R2 56 | Pop $R1 57 | Exch $R0 58 | FunctionEnd 59 | -------------------------------------------------------------------------------- /plugins/convert/color/_base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Parent class for color Adjustments for faceswap.py converter """ 3 | 4 | import logging 5 | import numpy as np 6 | 7 | from plugins.convert._config import Config 8 | 9 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 10 | 11 | 12 | def get_config(plugin_name, configfile=None): 13 | """ Return the config for the requested model """ 14 | return Config(plugin_name, configfile=configfile).config_dict 15 | 16 | 17 | class Adjustment(): 18 | """ Parent class for adjustments """ 19 | def __init__(self, configfile=None, config=None): 20 | logger.debug("Initializing %s: (configfile: %s, config: %s)", 21 | self.__class__.__name__, configfile, config) 22 | self.config = self.set_config(configfile, config) 23 | logger.debug("config: %s", self.config) 24 | logger.debug("Initialized %s", self.__class__.__name__) 25 | 26 | def set_config(self, configfile, config): 27 | """ Set the config to either global config or passed in config """ 28 | section = ".".join(self.__module__.split(".")[-2:]) 29 | if config is None: 30 | retval = get_config(section, configfile) 31 | else: 32 | config.section = section 33 | retval = config.config_dict 34 | config.section = None 35 | logger.debug("Config: %s", retval) 36 | return retval 37 | 38 | def process(self, old_face, new_face, raw_mask): 39 | """ Override for specific color adjustment process """ 40 | raise NotImplementedError 41 | 42 | def run(self, old_face, new_face, raw_mask): 43 | """ Perform selected adjustment on face """ 44 | logger.trace("Performing color adjustment") 45 | # Remove Mask for processing 46 | reinsert_mask = False 47 | if new_face.shape[2] == 4: 48 | reinsert_mask = True 49 | final_mask = new_face[:, :, -1] 50 | new_face = new_face[:, :, :3] 51 | new_face = self.process(old_face, new_face, raw_mask) 52 | new_face = np.clip(new_face, 0.0, 1.0) 53 | if reinsert_mask and new_face.shape[2] != 4: 54 | # Reinsert Mask 55 | new_face = np.concatenate((new_face, np.expand_dims(final_mask, axis=-1)), -1) 56 | logger.trace("Performed color adjustment") 57 | return new_face 58 | -------------------------------------------------------------------------------- /plugins/convert/scaling/_base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Parent class for scaling Adjustments for faceswap.py converter """ 3 | 4 | import logging 5 | import numpy as np 6 | 7 | from plugins.convert._config import Config 8 | 9 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 10 | 11 | 12 | def get_config(plugin_name, configfile=None): 13 | """ Return the config for the requested model """ 14 | return Config(plugin_name, configfile=configfile).config_dict 15 | 16 | 17 | class Adjustment(): 18 | """ Parent class for scaling adjustments """ 19 | def __init__(self, configfile=None, config=None): 20 | logger.debug("Initializing %s: (configfile: %s, config: %s)", 21 | self.__class__.__name__, configfile, config) 22 | self.config = self.set_config(configfile, config) 23 | logger.debug("config: %s", self.config) 24 | logger.debug("Initialized %s", self.__class__.__name__) 25 | 26 | def set_config(self, configfile, config): 27 | """ Set the config to either global config or passed in config """ 28 | section = ".".join(self.__module__.split(".")[-2:]) 29 | if config is None: 30 | logger.debug("Loading base config") 31 | retval = get_config(section, configfile=configfile) 32 | else: 33 | logger.debug("Loading passed in config") 34 | config.section = section 35 | retval = config.config_dict 36 | config.section = None 37 | logger.debug("Config: %s", retval) 38 | return retval 39 | 40 | def process(self, new_face): 41 | """ Override for specific scaling adjustment process """ 42 | raise NotImplementedError 43 | 44 | def run(self, new_face): 45 | """ Perform selected adjustment on face """ 46 | logger.trace("Performing scaling adjustment") 47 | # Remove Mask for processing 48 | reinsert_mask = False 49 | if new_face.shape[2] == 4: 50 | reinsert_mask = True 51 | final_mask = new_face[:, :, -1] 52 | new_face = new_face[:, :, :3] 53 | new_face = self.process(new_face) 54 | new_face = np.clip(new_face, 0.0, 1.0) 55 | if reinsert_mask and new_face.shape[2] != 4: 56 | # Reinsert Mask 57 | new_face = np.concatenate((new_face, np.expand_dims(final_mask, axis=-1)), -1) 58 | logger.trace("Performed scaling adjustment") 59 | return new_face 60 | -------------------------------------------------------------------------------- /plugins/train/model/dfaker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ DFaker Model 3 | Based on the dfaker model: https://github.com/dfaker """ 4 | 5 | 6 | from keras.initializers import RandomNormal 7 | from keras.layers import Input 8 | from keras.models import Model as KerasModel 9 | 10 | from .original import logger, Model as OriginalModel 11 | 12 | 13 | class Model(OriginalModel): 14 | """ Improved Autoeencoder Model """ 15 | def __init__(self, *args, **kwargs): 16 | logger.debug("Initializing %s: (args: %s, kwargs: %s", 17 | self.__class__.__name__, args, kwargs) 18 | kwargs["input_shape"] = (64, 64, 3) 19 | kwargs["encoder_dim"] = 1024 20 | self.kernel_initializer = RandomNormal(0, 0.02) 21 | super().__init__(*args, **kwargs) 22 | logger.debug("Initialized %s", self.__class__.__name__) 23 | 24 | def decoder(self): 25 | """ Decoder Network """ 26 | input_ = Input(shape=(8, 8, 512)) 27 | var_x = input_ 28 | 29 | var_x = self.blocks.upscale(var_x, 512, res_block_follows=True) 30 | var_x = self.blocks.res_block(var_x, 512, kernel_initializer=self.kernel_initializer) 31 | var_x = self.blocks.upscale(var_x, 256, res_block_follows=True) 32 | var_x = self.blocks.res_block(var_x, 256, kernel_initializer=self.kernel_initializer) 33 | var_x = self.blocks.upscale(var_x, 128, res_block_follows=True) 34 | var_x = self.blocks.res_block(var_x, 128, kernel_initializer=self.kernel_initializer) 35 | var_x = self.blocks.upscale(var_x, 64) 36 | var_x = self.blocks.conv2d(var_x, 3, 37 | kernel_size=5, 38 | padding="same", 39 | activation="sigmoid", 40 | name="face_out") 41 | outputs = [var_x] 42 | 43 | if self.config.get("mask_type", None): 44 | var_y = input_ 45 | var_y = self.blocks.upscale(var_y, 512) 46 | var_y = self.blocks.upscale(var_y, 256) 47 | var_y = self.blocks.upscale(var_y, 128) 48 | var_y = self.blocks.upscale(var_y, 64) 49 | var_y = self.blocks.conv2d(var_y, 1, 50 | kernel_size=5, 51 | padding="same", 52 | activation="sigmoid", 53 | name="mask_out") 54 | outputs.append(var_y) 55 | return KerasModel([input_], outputs=outputs) 56 | -------------------------------------------------------------------------------- /plugins/convert/writer/opencv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Image output writer for faceswap.py converter 3 | Uses cv2 for writing as in testing this was a lot faster than both Pillow and ImageIO 4 | """ 5 | 6 | import cv2 7 | from ._base import Output, logger 8 | 9 | 10 | class Writer(Output): 11 | """ Images output writer using cv2 """ 12 | def __init__(self, output_folder, **kwargs): 13 | super().__init__(output_folder, **kwargs) 14 | self.extension = ".{}".format(self.config["format"]) 15 | self.check_transparency_format() 16 | self.args = self.get_save_args() 17 | 18 | def check_transparency_format(self): 19 | """ Make sure that the output format is correct if draw_transparent is selected """ 20 | transparent = self.config["draw_transparent"] 21 | if not transparent or (transparent and self.config["format"] == "png"): 22 | return 23 | logger.warning("Draw Transparent selected, but the requested format does not support " 24 | "transparency. Changing output format to 'png'") 25 | self.config["format"] = "png" 26 | 27 | def get_save_args(self): 28 | """ Return the save parameters for the file format """ 29 | filetype = self.config["format"] 30 | args = list() 31 | if filetype == "jpg" and self.config["jpg_quality"] > 0: 32 | args = (cv2.IMWRITE_JPEG_QUALITY, # pylint: disable=no-member 33 | self.config["jpg_quality"]) 34 | if filetype == "png" and self.config["png_compress_level"] > -1: 35 | args = (cv2.IMWRITE_PNG_COMPRESSION, # pylint: disable=no-member 36 | self.config["png_compress_level"]) 37 | logger.debug(args) 38 | return args 39 | 40 | def write(self, filename, image): 41 | logger.trace("Outputting: (filename: '%s'", filename) 42 | filename = self.output_filename(filename) 43 | try: 44 | with open(filename, "wb") as outfile: 45 | outfile.write(image) 46 | except Exception as err: # pylint: disable=broad-except 47 | logger.error("Failed to save image '%s'. Original Error: %s", filename, err) 48 | 49 | def pre_encode(self, image): 50 | """ Pre_encode the image in lib/convert.py threads as it is a LOT quicker """ 51 | logger.trace("Pre-encoding image") 52 | image = cv2.imencode(self.extension, image, self.args)[1] # pylint: disable=no-member 53 | return image 54 | 55 | def close(self): 56 | """ Image writer does not need a close method """ 57 | return 58 | -------------------------------------------------------------------------------- /plugins/train/model/lightweight.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Original Model 3 | Based on the original https://www.reddit.com/r/deepfakes/ 4 | code sample + contribs """ 5 | 6 | from keras.layers import Dense, Flatten, Input, Reshape 7 | from keras.models import Model as KerasModel 8 | 9 | from .original import logger, Model as OriginalModel 10 | 11 | 12 | class Model(OriginalModel): 13 | """ Lightweight Model for ~2GB Graphics Cards """ 14 | def __init__(self, *args, **kwargs): 15 | logger.debug("Initializing %s: (args: %s, kwargs: %s", 16 | self.__class__.__name__, args, kwargs) 17 | 18 | kwargs["input_shape"] = (64, 64, 3) 19 | kwargs["encoder_dim"] = 512 20 | super().__init__(*args, **kwargs) 21 | logger.debug("Initialized %s", self.__class__.__name__) 22 | 23 | def encoder(self): 24 | """ Encoder Network """ 25 | input_ = Input(shape=self.input_shape) 26 | var_x = input_ 27 | var_x = self.blocks.conv(var_x, 128) 28 | var_x = self.blocks.conv(var_x, 256) 29 | var_x = self.blocks.conv(var_x, 512) 30 | var_x = Dense(self.encoder_dim)(Flatten()(var_x)) 31 | var_x = Dense(4 * 4 * 512)(var_x) 32 | var_x = Reshape((4, 4, 512))(var_x) 33 | var_x = self.blocks.upscale(var_x, 256) 34 | return KerasModel(input_, var_x) 35 | 36 | def decoder(self): 37 | """ Decoder Network """ 38 | input_ = Input(shape=(8, 8, 256)) 39 | var_x = input_ 40 | var_x = self.blocks.upscale(var_x, 512) 41 | var_x = self.blocks.upscale(var_x, 256) 42 | var_x = self.blocks.upscale(var_x, 128) 43 | var_x = self.blocks.conv2d(var_x, 3, 44 | kernel_size=5, 45 | padding="same", 46 | activation="sigmoid", 47 | name="face_out") 48 | outputs = [var_x] 49 | 50 | if self.config.get("mask_type", None): 51 | var_y = input_ 52 | var_y = self.blocks.upscale(var_y, 512) 53 | var_y = self.blocks.upscale(var_y, 256) 54 | var_y = self.blocks.upscale(var_y, 128) 55 | var_y = self.blocks.conv2d(var_y, 1, 56 | kernel_size=5, 57 | padding="same", 58 | activation="sigmoid", 59 | name="mask_out") 60 | outputs.append(var_y) 61 | return KerasModel(input_, outputs=outputs) 62 | -------------------------------------------------------------------------------- /plugins/convert/scaling/sharpen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Sharpening for enlarged face for faceswap.py converter """ 3 | import cv2 4 | import numpy as np 5 | 6 | from ._base import Adjustment, logger 7 | 8 | 9 | class Scaling(Adjustment): 10 | """ Sharpening Adjustments for the face applied after warp to final frame """ 11 | 12 | def process(self, new_face): 13 | """ Sharpen using the requested technique """ 14 | amount = self.config["amount"] / 100.0 15 | kernel_center = self.get_kernel_size(new_face, self.config["radius"]) 16 | new_face = getattr(self, self.config["method"])(new_face, kernel_center, amount) 17 | return new_face 18 | 19 | @staticmethod 20 | def get_kernel_size(new_face, radius_percent): 21 | """ Return the kernel size and central point for the given radius 22 | relative to frame width """ 23 | radius = max(1, round(new_face.shape[1] * radius_percent / 100)) 24 | kernel_size = int((radius * 2) + 1) 25 | kernel_size = (kernel_size, kernel_size) 26 | logger.trace(kernel_size) 27 | return kernel_size, radius 28 | 29 | @staticmethod 30 | def box(new_face, kernel_center, amount): 31 | """ Sharpen using box filter """ 32 | kernel_size, center = kernel_center 33 | kernel = np.zeros(kernel_size, dtype="float32") 34 | kernel[center, center] = 1.0 35 | box_filter = np.ones(kernel_size, dtype="float32") / kernel_size[0]**2 36 | kernel = kernel + (kernel - box_filter) * amount 37 | new_face = cv2.filter2D(new_face, -1, kernel) # pylint: disable=no-member 38 | return new_face 39 | 40 | @staticmethod 41 | def gaussian(new_face, kernel_center, amount): 42 | """ Sharpen using gaussian filter """ 43 | kernel_size = kernel_center[0] 44 | blur = cv2.GaussianBlur(new_face, kernel_size, 0) # pylint: disable=no-member 45 | new_face = cv2.addWeighted(new_face, # pylint: disable=no-member 46 | 1.0 + (0.5 * amount), 47 | blur, 48 | -(0.5 * amount), 49 | 0) 50 | return new_face 51 | 52 | def unsharp_mask(self, new_face, kernel_center, amount): 53 | """ Sharpen using unsharp mask """ 54 | kernel_size = kernel_center[0] 55 | threshold = self.config["threshold"] / 255.0 56 | blur = cv2.GaussianBlur(new_face, kernel_size, 0) # pylint: disable=no-member 57 | low_contrast_mask = (abs(new_face - blur) < threshold).astype("float32") 58 | sharpened = (new_face * (1.0 + amount)) + (blur * -amount) 59 | new_face = (new_face * (1.0 - low_contrast_mask)) + (sharpened * low_contrast_mask) 60 | return new_face 61 | -------------------------------------------------------------------------------- /plugins/train/model/dfl_h128.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ DeepFakesLab H128 Model 3 | Based on https://github.com/iperov/DeepFaceLab 4 | """ 5 | 6 | from keras.layers import Dense, Flatten, Input, Reshape 7 | from keras.models import Model as KerasModel 8 | 9 | from .original import logger, Model as OriginalModel 10 | 11 | 12 | class Model(OriginalModel): 13 | """ Low Memory version of Original Faceswap Model """ 14 | def __init__(self, *args, **kwargs): 15 | logger.debug("Initializing %s: (args: %s, kwargs: %s", 16 | self.__class__.__name__, args, kwargs) 17 | 18 | self.configfile = kwargs.get("configfile", None) 19 | kwargs["input_shape"] = (128, 128, 3) 20 | kwargs["encoder_dim"] = 256 if self.config["lowmem"] else 512 21 | 22 | super().__init__(*args, **kwargs) 23 | logger.debug("Initialized %s", self.__class__.__name__) 24 | 25 | def encoder(self): 26 | """ DFL H128 Encoder """ 27 | input_ = Input(shape=self.input_shape) 28 | var_x = input_ 29 | var_x = self.blocks.conv(var_x, 128) 30 | var_x = self.blocks.conv(var_x, 256) 31 | var_x = self.blocks.conv(var_x, 512) 32 | var_x = self.blocks.conv(var_x, 1024) 33 | var_x = Dense(self.encoder_dim)(Flatten()(var_x)) 34 | var_x = Dense(8 * 8 * self.encoder_dim)(var_x) 35 | var_x = Reshape((8, 8, self.encoder_dim))(var_x) 36 | var_x = self.blocks.upscale(var_x, self.encoder_dim) 37 | return KerasModel(input_, var_x) 38 | 39 | def decoder(self): 40 | """ DFL H128 Decoder """ 41 | input_ = Input(shape=(16, 16, self.encoder_dim)) 42 | # Face 43 | var_x = input_ 44 | var_x = self.blocks.upscale(var_x, self.encoder_dim) 45 | var_x = self.blocks.upscale(var_x, self.encoder_dim // 2) 46 | var_x = self.blocks.upscale(var_x, self.encoder_dim // 4) 47 | var_x = self.blocks.conv2d(var_x, 3, 48 | kernel_size=5, 49 | padding="same", 50 | activation="sigmoid", 51 | name="face_out") 52 | outputs = [var_x] 53 | # Mask 54 | if self.config.get("mask_type", None): 55 | var_y = input_ 56 | var_y = self.blocks.upscale(var_y, self.encoder_dim) 57 | var_y = self.blocks.upscale(var_y, self.encoder_dim // 2) 58 | var_y = self.blocks.upscale(var_y, self.encoder_dim // 4) 59 | var_y = self.blocks.conv2d(var_y, 1, 60 | kernel_size=5, 61 | padding="same", 62 | activation="sigmoid", 63 | name="mask_out") 64 | outputs.append(var_y) 65 | return KerasModel(input_, outputs=outputs) 66 | -------------------------------------------------------------------------------- /tools/alignments.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Tools for manipulating the alignments seralized file """ 3 | import logging 4 | 5 | from lib.utils import set_system_verbosity 6 | from .lib_alignments import (AlignmentData, Check, Draw, # noqa pylint: disable=unused-import 7 | Extract, Legacy, Manual, Merge, Reformat, Rename, 8 | RemoveAlignments, Sort, Spatial, UpdateHashes) 9 | 10 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 11 | 12 | 13 | class Alignments(): 14 | """ Perform tasks relating to alignments file """ 15 | def __init__(self, arguments): 16 | logger.debug("Initializing %s: (arguments: '%s'", self.__class__.__name__, arguments) 17 | self.args = arguments 18 | set_system_verbosity(self.args.loglevel) 19 | self.alignments = self.load_alignments() 20 | logger.debug("Initialized %s", self.__class__.__name__) 21 | 22 | def load_alignments(self): 23 | """ Loading alignments """ 24 | logger.debug("Loading alignments") 25 | if len(self.args.alignments_file) > 1 and self.args.job != "merge": 26 | logger.error("Multiple alignments files are only permitted for merging") 27 | exit(0) 28 | if len(self.args.alignments_file) == 1 and self.args.job == "merge": 29 | logger.error("More than one alignments file required for merging") 30 | exit(0) 31 | 32 | dest_format = self.get_dest_format() 33 | if len(self.args.alignments_file) == 1: 34 | retval = AlignmentData(self.args.alignments_file[0], dest_format) 35 | else: 36 | retval = [AlignmentData(a_file, dest_format) for a_file in self.args.alignments_file] 37 | logger.debug("Alignments: %s", retval) 38 | return retval 39 | 40 | def get_dest_format(self): 41 | """ Set the destination format for Alignments """ 42 | dest_format = None 43 | if hasattr(self.args, 'alignment_format') and self.args.alignment_format: 44 | dest_format = self.args.alignment_format 45 | logger.debug(dest_format) 46 | return dest_format 47 | 48 | def process(self): 49 | """ Main processing function of the Align tool """ 50 | if self.args.job.startswith("extract"): 51 | job = Extract 52 | elif self.args.job == "update-hashes": 53 | job = UpdateHashes 54 | elif self.args.job.startswith("remove-"): 55 | job = RemoveAlignments 56 | elif self.args.job.startswith("sort-"): 57 | job = Sort 58 | elif self.args.job in("missing-alignments", "missing-frames", 59 | "multi-faces", "leftover-faces", "no-faces"): 60 | job = Check 61 | else: 62 | job = globals()[self.args.job.title()] 63 | job = job(self.alignments, self.args) 64 | logger.debug(job) 65 | job.process() 66 | -------------------------------------------------------------------------------- /plugins/convert/writer/pillow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Image output writer for faceswap.py converter """ 3 | 4 | from io import BytesIO 5 | from PIL import Image 6 | 7 | from ._base import Output, logger 8 | 9 | 10 | class Writer(Output): 11 | """ Images output writer using cv2 """ 12 | def __init__(self, output_folder, **kwargs): 13 | super().__init__(output_folder, **kwargs) 14 | self.check_transparency_format() 15 | # Correct format namings for writing to byte stream 16 | self.format_dict = dict(jpg="JPEG", jp2="JPEG 2000", tif="TIFF") 17 | self.kwargs = self.get_save_kwargs() 18 | 19 | def check_transparency_format(self): 20 | """ Make sure that the output format is correct if draw_transparent is selected """ 21 | transparent = self.config["draw_transparent"] 22 | if not transparent or (transparent and self.config["format"] in ("png", "tif")): 23 | return 24 | logger.warning("Draw Transparent selected, but the requested format does not support " 25 | "transparency. Changing output format to 'png'") 26 | self.config["format"] = "png" 27 | 28 | def get_save_kwargs(self): 29 | """ Return the save parameters for the file format """ 30 | filetype = self.config["format"] 31 | kwargs = dict() 32 | if filetype in ("gif", "jpg", "png"): 33 | kwargs["optimize"] = self.config["optimize"] 34 | if filetype == "gif": 35 | kwargs["interlace"] = self.config["gif_interlace"] 36 | if filetype == "png": 37 | kwargs["compress_level"] = self.config["png_compress_level"] 38 | if filetype == "tif": 39 | kwargs["compression"] = self.config["tif_compression"] 40 | logger.debug(kwargs) 41 | return kwargs 42 | 43 | def write(self, filename, image): 44 | logger.trace("Outputting: (filename: '%s'", filename) 45 | filename = self.output_filename(filename) 46 | try: 47 | with open(filename, "wb") as outfile: 48 | outfile.write(image.read()) 49 | except Exception as err: # pylint: disable=broad-except 50 | logger.error("Failed to save image '%s'. Original Error: %s", filename, err) 51 | 52 | def pre_encode(self, image): 53 | """ Pre_encode the image in lib/convert.py threads as it is a LOT quicker """ 54 | logger.trace("Pre-encoding image") 55 | fmt = self.format_dict.get(self.config["format"], None) 56 | fmt = self.config["format"].upper() if fmt is None else fmt 57 | encoded = BytesIO() 58 | rgb = [2, 1, 0] 59 | if image.shape[2] == 4: 60 | rgb.append(3) 61 | out_image = Image.fromarray(image[..., rgb]) 62 | out_image.save(encoded, fmt, **self.kwargs) 63 | encoded.seek(0) 64 | return encoded 65 | 66 | def close(self): 67 | """ Image writer does not need a close method """ 68 | return 69 | -------------------------------------------------------------------------------- /lib/gui/statusbar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin python3 2 | """ Status bar for the GUI """ 3 | 4 | import tkinter as tk 5 | from tkinter import ttk 6 | 7 | 8 | class StatusBar(ttk.Frame): # pylint: disable=too-many-ancestors 9 | """ Status Bar for displaying the Status Message and 10 | Progress Bar """ 11 | 12 | def __init__(self, parent): 13 | ttk.Frame.__init__(self, parent) 14 | self.pack(side=tk.BOTTOM, padx=10, pady=2, fill=tk.X, expand=False) 15 | 16 | self.status_message = tk.StringVar() 17 | self.pbar_message = tk.StringVar() 18 | self.pbar_position = tk.IntVar() 19 | 20 | self.status_message.set("Ready") 21 | 22 | self.status() 23 | self.pbar = self.progress_bar() 24 | 25 | def status(self): 26 | """ Place Status into bottom bar """ 27 | statusframe = ttk.Frame(self) 28 | statusframe.pack(side=tk.LEFT, anchor=tk.W, fill=tk.X, expand=False) 29 | 30 | lbltitle = ttk.Label(statusframe, text="Status:", width=6, anchor=tk.W) 31 | lbltitle.pack(side=tk.LEFT, expand=False) 32 | 33 | lblstatus = ttk.Label(statusframe, 34 | width=40, 35 | textvariable=self.status_message, 36 | anchor=tk.W) 37 | lblstatus.pack(side=tk.LEFT, anchor=tk.W, fill=tk.X, expand=True) 38 | 39 | def progress_bar(self): 40 | """ Place progress bar into bottom bar """ 41 | progressframe = ttk.Frame(self) 42 | progressframe.pack(side=tk.RIGHT, anchor=tk.E, fill=tk.X) 43 | 44 | lblmessage = ttk.Label(progressframe, textvariable=self.pbar_message) 45 | lblmessage.pack(side=tk.LEFT, padx=3, fill=tk.X, expand=True) 46 | 47 | pbar = ttk.Progressbar(progressframe, 48 | length=200, 49 | variable=self.pbar_position, 50 | maximum=100, 51 | mode="determinate") 52 | pbar.pack(side=tk.LEFT, padx=2, fill=tk.X, expand=True) 53 | pbar.pack_forget() 54 | return pbar 55 | 56 | def progress_start(self, mode): 57 | """ Set progress bar mode and display """ 58 | self.progress_set_mode(mode) 59 | self.pbar.pack() 60 | 61 | def progress_stop(self): 62 | """ Reset progress bar and hide """ 63 | self.pbar_message.set("") 64 | self.pbar_position.set(0) 65 | self.progress_set_mode("determinate") 66 | self.pbar.pack_forget() 67 | 68 | def progress_set_mode(self, mode): 69 | """ Set the progress bar mode """ 70 | self.pbar.config(mode=mode) 71 | if mode == "indeterminate": 72 | self.pbar.config(maximum=100) 73 | self.pbar.start() 74 | else: 75 | self.pbar.stop() 76 | self.pbar.config(maximum=100) 77 | 78 | def progress_update(self, message, position, update_position=True): 79 | """ Update the GUIs progress bar and position """ 80 | self.pbar_message.set(message) 81 | if update_position: 82 | self.pbar_position.set(position) 83 | -------------------------------------------------------------------------------- /lib/Serializer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Library providing convenient classes and methods for writing data to files. 4 | """ 5 | import logging 6 | import json 7 | import pickle 8 | 9 | try: 10 | import yaml 11 | except ImportError: 12 | yaml = None 13 | 14 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 15 | 16 | 17 | class Serializer(): 18 | """ Parent Serializer class """ 19 | ext = "" 20 | woptions = "" 21 | roptions = "" 22 | 23 | @classmethod 24 | def marshal(cls, input_data): 25 | """ Override for marshalling """ 26 | raise NotImplementedError() 27 | 28 | @classmethod 29 | def unmarshal(cls, input_string): 30 | """ Override for unmarshalling """ 31 | raise NotImplementedError() 32 | 33 | 34 | class YAMLSerializer(Serializer): 35 | """ YAML Serializer """ 36 | ext = "yml" 37 | woptions = "w" 38 | roptions = "r" 39 | 40 | @classmethod 41 | def marshal(cls, input_data): 42 | return yaml.dump(input_data, default_flow_style=False) 43 | 44 | @classmethod 45 | def unmarshal(cls, input_string): 46 | return yaml.load(input_string) 47 | 48 | 49 | class JSONSerializer(Serializer): 50 | """ JSON Serializer """ 51 | ext = "json" 52 | woptions = "w" 53 | roptions = "r" 54 | 55 | @classmethod 56 | def marshal(cls, input_data): 57 | return json.dumps(input_data, indent=2) 58 | 59 | @classmethod 60 | def unmarshal(cls, input_string): 61 | return json.loads(input_string) 62 | 63 | 64 | class PickleSerializer(Serializer): 65 | """ Picke Serializer """ 66 | ext = "p" 67 | woptions = "wb" 68 | roptions = "rb" 69 | 70 | @classmethod 71 | def marshal(cls, input_data): 72 | return pickle.dumps(input_data) 73 | 74 | @classmethod 75 | def unmarshal(cls, input_bytes): # pylint: disable=arguments-differ 76 | return pickle.loads(input_bytes) 77 | 78 | 79 | def get_serializer(serializer): 80 | """ Return requested serializer """ 81 | if serializer == "json": 82 | return JSONSerializer 83 | if serializer == "pickle": 84 | return PickleSerializer 85 | if serializer == "yaml" and yaml is not None: 86 | return YAMLSerializer 87 | if serializer == "yaml" and yaml is None: 88 | logger.warning("You must have PyYAML installed to use YAML as the serializer." 89 | "Switching to JSON as the serializer.") 90 | return JSONSerializer 91 | 92 | 93 | def get_serializer_from_ext(ext): 94 | """ Get the sertializer from filename extension """ 95 | if ext == ".json": 96 | return JSONSerializer 97 | if ext == ".p": 98 | return PickleSerializer 99 | if ext in (".yaml", ".yml") and yaml is not None: 100 | return YAMLSerializer 101 | if ext in (".yaml", ".yml") and yaml is None: 102 | logger.warning("You must have PyYAML installed to use YAML as the serializer.\n" 103 | "Switching to JSON as the serializer.") 104 | return JSONSerializer 105 | -------------------------------------------------------------------------------- /plugins/convert/writer/_base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Parent class for output writers for faceswap.py converter """ 3 | 4 | import logging 5 | import os 6 | import re 7 | 8 | from plugins.convert._config import Config 9 | 10 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 11 | 12 | 13 | def get_config(plugin_name, configfile=None): 14 | """ Return the config for the requested model """ 15 | return Config(plugin_name, configfile=configfile).config_dict 16 | 17 | 18 | class Output(): 19 | """ Parent class for scaling adjustments """ 20 | def __init__(self, output_folder, configfile=None): 21 | logger.debug("Initializing %s: (output_folder: '%s')", 22 | self.__class__.__name__, output_folder) 23 | self.config = get_config(".".join(self.__module__.split(".")[-2:]), configfile=configfile) 24 | logger.debug("config: %s", self.config) 25 | self.output_folder = output_folder 26 | self.output_dimensions = None 27 | 28 | # Methods for making sure frames are written out in frame order 29 | self.re_search = re.compile(r"(\d+)(?=\.\w+$)") # Identify frame numbers 30 | self.cache = dict() # Cache for when frames must be written in correct order 31 | logger.debug("Initialized %s", self.__class__.__name__) 32 | 33 | @property 34 | def is_stream(self): 35 | """ Return whether the writer is a stream or images 36 | Writers that write to a stream have a frame_order paramater to dictate 37 | the order in which frames should be written out (eg. gif/ffmpeg) """ 38 | retval = hasattr(self, "frame_order") 39 | return retval 40 | 41 | def output_filename(self, filename): 42 | """ Return the output filename with the correct folder and extension 43 | NB: The plugin must have a config item 'format' that contains the 44 | file extension to use this method """ 45 | filename = os.path.splitext(os.path.basename(filename))[0] 46 | out_filename = "{}.{}".format(filename, self.config["format"]) 47 | out_filename = os.path.join(self.output_folder, out_filename) 48 | logger.trace("in filename: '%s', out filename: '%s'", filename, out_filename) 49 | return out_filename 50 | 51 | def cache_frame(self, filename, image): 52 | """ Add the incoming frame to the cache """ 53 | frame_no = int(re.search(self.re_search, filename).group()) 54 | self.cache[frame_no] = image 55 | logger.trace("Added to cache. Frame no: %s", frame_no) 56 | logger.trace("Current cache: %s", sorted(self.cache.keys())) 57 | 58 | def write(self, filename, image): 59 | """ Override for specific frame writing method """ 60 | raise NotImplementedError 61 | 62 | def pre_encode(self, image): # pylint: disable=unused-argument,no-self-use 63 | """ If the writer supports pre-encoding then override this to pre-encode 64 | the image in lib/convert.py to speed up saving """ 65 | return None 66 | 67 | def close(self): 68 | """ Override for specific frame writing close methods """ 69 | raise NotImplementedError 70 | -------------------------------------------------------------------------------- /lib/align_eyes.py: -------------------------------------------------------------------------------- 1 | # Code borrowed from https://github.com/jrosebr1/imutils/blob/d5cb29d02cf178c399210d5a139a821dfb0ae136/imutils/face_utils/helpers.py 2 | """ 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2015-2016 Adrian Rosebrock, http://www.pyimagesearch.com 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | """ 25 | 26 | from collections import OrderedDict 27 | import numpy as np 28 | import cv2 29 | 30 | # define a dictionary that maps the indexes of the facial 31 | # landmarks to specific face regions 32 | FACIAL_LANDMARKS_IDXS = OrderedDict([ 33 | ("mouth", (48, 68)), 34 | ("right_eyebrow", (17, 22)), 35 | ("left_eyebrow", (22, 27)), 36 | ("right_eye", (36, 42)), 37 | ("left_eye", (42, 48)), 38 | ("nose", (27, 36)), 39 | ("jaw", (0, 17)), 40 | ("chin", (8, 11)) 41 | ]) 42 | 43 | # Returns a rotation matrix that when applied to the 68 input facial landmarks 44 | # results in landmarks with eyes aligned horizontally 45 | def align_eyes(landmarks, size): 46 | desiredLeftEye = (0.35, 0.35) # (y, x) value 47 | desiredFaceWidth = desiredFaceHeight = size 48 | 49 | # extract the left and right eye (x, y)-coordinates 50 | (lStart, lEnd) = FACIAL_LANDMARKS_IDXS["left_eye"] 51 | (rStart, rEnd) = FACIAL_LANDMARKS_IDXS["right_eye"] 52 | leftEyePts = landmarks[lStart:lEnd] 53 | rightEyePts = landmarks[rStart:rEnd] 54 | 55 | # compute the center of mass for each eye 56 | leftEyeCenter = leftEyePts.mean(axis=0).astype("int") 57 | rightEyeCenter = rightEyePts.mean(axis=0).astype("int") 58 | 59 | # compute the angle between the eye centroids 60 | dY = rightEyeCenter[0,1] - leftEyeCenter[0,1] 61 | dX = rightEyeCenter[0,0] - leftEyeCenter[0,0] 62 | angle = np.degrees(np.arctan2(dY, dX)) - 180 63 | 64 | # compute center (x, y)-coordinates (i.e., the median point) 65 | # between the two eyes in the input image 66 | eyesCenter = ((leftEyeCenter[0,0] + rightEyeCenter[0,0]) // 2, (leftEyeCenter[0,1] + rightEyeCenter[0,1]) // 2) 67 | 68 | # grab the rotation matrix for rotating and scaling the face 69 | M = cv2.getRotationMatrix2D(eyesCenter, angle, 1.0) 70 | 71 | return M 72 | -------------------------------------------------------------------------------- /plugins/train/model/original_defaults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | The default options for the faceswap Original Model plugin. 4 | 5 | Defaults files should be named _defaults.py 6 | Any items placed into this file will automatically get added to the relevant config .ini files 7 | within the faceswap/config folder. 8 | 9 | The following variables should be defined: 10 | _HELPTEXT: A string describing what this plugin does 11 | _DEFAULTS: A dictionary containing the options, defaults and meta information. The 12 | dictionary should be defined as: 13 | {: {}} 14 | 15 | should always be lower text. 16 | dictionary requirements are listed below. 17 | 18 | The following keys are expected for the _DEFAULTS dict: 19 | datatype: [required] A python type class. This limits the type of data that can be 20 | provided in the .ini file and ensures that the value is returned in the 21 | correct type to faceswap. Valid datatypes are: , , 22 | , . 23 | default: [required] The default value for this option. 24 | info: [required] A string describing what this option does. 25 | choices: [optional] If this option's datatype is of then valid 26 | selections can be defined here. This validates the option and also enables 27 | a combobox / radio option in the GUI. 28 | gui_radio: [optional] If are defined, this indicates that the GUI should use 29 | radio buttons rather than a combobox to display this option. 30 | min_max: [partial] For and datatypes this is required 31 | otherwise it is ignored. Should be a tuple of min and max accepted values. 32 | This is used for controlling the GUI slider range. Values are not enforced. 33 | rounding: [partial] For and datatypes this is 34 | required otherwise it is ignored. Used for the GUI slider. For floats, this 35 | is the number of decimal places to display. For ints this is the step size. 36 | fixed: [optional] [train only]. Training configurations are fixed when the model is 37 | created, and then reloaded from the state file. Marking an item as fixed=False 38 | indicates that this value can be changed for existing models, and will override 39 | the value saved in the state file with the updated value in config. If not 40 | provided this will default to True. 41 | """ 42 | 43 | 44 | _HELPTEXT = "Original Faceswap Model." 45 | 46 | 47 | _DEFAULTS = { 48 | "lowmem": { 49 | "default": False, 50 | "info": "Lower memory mode. Set to 'True' if having issues with VRAM useage.\n" 51 | "NB: Models with a changed lowmem mode are not compatible with each other.", 52 | "datatype": bool, 53 | "rounding": None, 54 | "min_max": None, 55 | "choices": [], 56 | "gui_radio": False, 57 | "fixed": True, 58 | "group": "settings", 59 | }, 60 | } 61 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Adapted from https://github.com/kangwonlee/travis-yml-conda-posix-nt/blob/master/.travis.yml 2 | 3 | language: shell 4 | 5 | env: 6 | global: 7 | - CONDA_PYTHON=3.6 8 | - CONDA_BLD_PATH=${HOME}/conda-bld 9 | 10 | os: 11 | - linux 12 | # - windows 13 | # - osx 14 | 15 | 16 | cache: 17 | # More time is needed for caching due to the sheer size of the conda env. 18 | timeout: 1000 19 | directories: 20 | - ${HOME}/cache 21 | 22 | before_cache: 23 | # adapted from https://github.com/theochem/cgrid/blob/master/.travis.yml 24 | - rm -rf ${MINICONDA_PATH}/conda-bld 25 | - rm -rf ${MINICONDA_PATH}/locks 26 | - rm -rf ${MINICONDA_PATH}/pkgs 27 | - rm -rf ${MINICONDA_PATH}/var 28 | - rm -rf ${MINICONDA_PATH}/envs/*/conda-bld 29 | - rm -rf ${MINICONDA_PATH}/envs/*/locks 30 | - rm -rf ${MINICONDA_PATH}/envs/*/pkgs 31 | - rm -rf ${MINICONDA_PATH}/envs/*/var 32 | # Clean out test results 33 | - rm -rf ${HOME}/cache/tests/*/faces 34 | - rm -rf ${HOME}/cache/tests/*/conv 35 | - rm -rf ${HOME}/cache/tests/*/*.json 36 | - rm -rf ${HOME}/cache/tests/vid/faces_sorted 37 | - rm -rf ${HOME}/cache/tests/vid/model 38 | 39 | before_install: 40 | # set conda path info 41 | - | 42 | if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then 43 | MINICONDA_PATH=${HOME}/cache/miniconda; 44 | MINICONDA_SUB_PATH=$MINICONDA_PATH/bin; 45 | elif [[ "$TRAVIS_OS_NAME" == "windows" ]]; then 46 | MINICONDA_PATH=${HOME}/cache/miniconda3/; 47 | MINICONDA_PATH_WIN=`cygpath --windows $MINICONDA_PATH`; 48 | MINICONDA_SUB_PATH=$MINICONDA_PATH/Scripts; 49 | fi; 50 | # obtain miniconda installer 51 | - | 52 | if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then 53 | wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; 54 | # Not used at the moment, but in case we want to also run test on osx, we can. 55 | elif [[ "$TRAVIS_OS_NAME" == "osx" ]]; then 56 | wget https://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -O miniconda.sh; 57 | fi; 58 | 59 | install: 60 | # install miniconda 61 | # pip and conda will also need OpenSSL for Windows 62 | - | 63 | if test -e "$MINICONDA_PATH"; then 64 | echo "Conda already installed"; 65 | else 66 | echo "Installing conda"; 67 | if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then 68 | bash miniconda.sh -b -p $MINICONDA_PATH; 69 | elif [[ "$TRAVIS_OS_NAME" == "windows" ]]; then 70 | choco install openssl.light; 71 | choco install miniconda3 --params="'/AddToPath:1 /D:$MINICONDA_PATH_WIN'"; 72 | fi; 73 | fi; 74 | - export PATH="$MINICONDA_PATH:$MINICONDA_SUB_PATH:$PATH"; 75 | # for conda version 4.4 or later 76 | - source $MINICONDA_PATH/etc/profile.d/conda.sh; 77 | - hash -r; 78 | - conda config --set always_yes yes --set changeps1 no; 79 | - conda update -q conda; 80 | # Useful for debugging any issues with conda 81 | - conda info -a 82 | - echo "Python $CONDA_PYTHON running on $TRAVIS_OS_NAME"; 83 | # Only create the environment if we don't have it already 84 | - conda env list | grep faceswap || conda create -q --name faceswap python=$CONDA_PYTHON; 85 | - conda activate faceswap; 86 | - conda --version ; python --version ; pip --version; 87 | - python setup.py --installer; 88 | # For debugging purposes 89 | - df -h 90 | 91 | script: 92 | - python simple_tests.py; 93 | 94 | -------------------------------------------------------------------------------- /plugins/convert/mask/mask_blend.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Adjustments for the mask for faceswap.py converter """ 3 | 4 | import cv2 5 | import numpy as np 6 | 7 | from lib.model import masks as model_masks 8 | from ._base import Adjustment, BlurMask, logger 9 | 10 | 11 | class Mask(Adjustment): 12 | """ Return the requested mask """ 13 | def __init__(self, mask_type, output_size, predicted_available, **kwargs): 14 | super().__init__(mask_type, output_size, predicted_available, **kwargs) 15 | self.do_erode = self.config.get("erosion", 0) != 0 16 | self.do_blend = self.config.get("type", None) is not None 17 | 18 | def process(self, detected_face, predicted_mask=None): 19 | """ Return mask and perform processing """ 20 | mask = self.get_mask(detected_face, predicted_mask) 21 | raw_mask = mask.copy() 22 | if not self.skip and self.do_erode: 23 | mask = self.erode(mask) 24 | if not self.skip and self.do_blend: 25 | mask = self.blend(mask) 26 | raw_mask = np.expand_dims(raw_mask, axis=-1) if raw_mask.ndim != 3 else raw_mask 27 | mask = np.expand_dims(mask, axis=-1) if mask.ndim != 3 else mask 28 | logger.trace("mask shape: %s, raw_mask shape: %s", mask.shape, raw_mask.shape) 29 | return mask, raw_mask 30 | 31 | def get_mask(self, detected_face, predicted_mask): 32 | """ Return the mask from lib/model/masks and intersect with box """ 33 | if self.mask_type == "none": 34 | # Return a dummy mask if not using a mask 35 | mask = np.ones_like(self.dummy[:, :, 1]) 36 | elif self.mask_type == "predicted": 37 | mask = predicted_mask 38 | else: 39 | landmarks = detected_face.reference_landmarks 40 | mask = getattr(model_masks, self.mask_type)(landmarks, self.dummy, channels=1).mask 41 | np.nan_to_num(mask, copy=False) 42 | np.clip(mask, 0.0, 1.0, out=mask) 43 | return mask 44 | 45 | # MASK MANIPULATIONS 46 | def erode(self, mask): 47 | """ Erode/dilate mask if requested """ 48 | kernel = self.get_erosion_kernel(mask) 49 | if self.config["erosion"] > 0: 50 | logger.trace("Eroding mask") 51 | mask = cv2.erode(mask, kernel, iterations=1) # pylint: disable=no-member 52 | else: 53 | logger.trace("Dilating mask") 54 | mask = cv2.dilate(mask, kernel, iterations=1) # pylint: disable=no-member 55 | return mask 56 | 57 | def get_erosion_kernel(self, mask): 58 | """ Get the erosion kernel """ 59 | erosion_ratio = self.config["erosion"] / 100 60 | mask_radius = np.sqrt(np.sum(mask)) / 2 61 | kernel_size = max(1, int(abs(erosion_ratio * mask_radius))) 62 | erosion_kernel = cv2.getStructuringElement( # pylint: disable=no-member 63 | cv2.MORPH_ELLIPSE, # pylint: disable=no-member 64 | (kernel_size, kernel_size)) 65 | logger.trace("erosion_kernel shape: %s", erosion_kernel.shape) 66 | return erosion_kernel 67 | 68 | def blend(self, mask): 69 | """ Blur mask if requested """ 70 | logger.trace("Blending mask") 71 | mask = BlurMask(self.config["type"], 72 | mask, 73 | self.config["radius"], 74 | self.config["passes"]).blurred 75 | return mask 76 | -------------------------------------------------------------------------------- /plugins/train/model/dfl_h128_defaults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | The default options for the faceswap Dfl_H128 Model plugin. 4 | 5 | Defaults files should be named _defaults.py 6 | Any items placed into this file will automatically get added to the relevant config .ini files 7 | within the faceswap/config folder. 8 | 9 | The following variables should be defined: 10 | _HELPTEXT: A string describing what this plugin does 11 | _DEFAULTS: A dictionary containing the options, defaults and meta information. The 12 | dictionary should be defined as: 13 | {: {}} 14 | 15 | should always be lower text. 16 | dictionary requirements are listed below. 17 | 18 | The following keys are expected for the _DEFAULTS dict: 19 | datatype: [required] A python type class. This limits the type of data that can be 20 | provided in the .ini file and ensures that the value is returned in the 21 | correct type to faceswap. Valid datatypes are: , , 22 | , . 23 | default: [required] The default value for this option. 24 | info: [required] A string describing what this option does. 25 | choices: [optional] If this option's datatype is of then valid 26 | selections can be defined here. This validates the option and also enables 27 | a combobox / radio option in the GUI. 28 | gui_radio: [optional] If are defined, this indicates that the GUI should use 29 | radio buttons rather than a combobox to display this option. 30 | min_max: [partial] For and datatypes this is required 31 | otherwise it is ignored. Should be a tuple of min and max accepted values. 32 | This is used for controlling the GUI slider range. Values are not enforced. 33 | rounding: [partial] For and datatypes this is 34 | required otherwise it is ignored. Used for the GUI slider. For floats, this 35 | is the number of decimal places to display. For ints this is the step size. 36 | fixed: [optional] [train only]. Training configurations are fixed when the model is 37 | created, and then reloaded from the state file. Marking an item as fixed=False 38 | indicates that this value can be changed for existing models, and will override 39 | the value saved in the state file with the updated value in config. If not 40 | provided this will default to True. 41 | """ 42 | 43 | 44 | _HELPTEXT = "DFL H128 Model (Adapted from https://github.com/iperov/DeepFaceLab)" 45 | 46 | 47 | _DEFAULTS = { 48 | "lowmem": { 49 | "default": False, 50 | "info": "Lower memory mode. Set to 'True' if having issues with VRAM useage.\n" 51 | "NB: Models with a changed lowmem mode are not compatible with each other.", 52 | "datatype": bool, 53 | "rounding": None, 54 | "min_max": None, 55 | "choices": [], 56 | "gui_radio": False, 57 | "group": "settings", 58 | "fixed": True, 59 | }, 60 | } 61 | -------------------------------------------------------------------------------- /lib/model/optimizers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Optimizers for faceswap.py """ 3 | # Naming convention inherited from Keras so ignore invalid names 4 | # pylint:disable=invalid-name 5 | 6 | import logging 7 | 8 | from keras import backend as K 9 | from keras.optimizers import Adam as KerasAdam 10 | 11 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 12 | 13 | 14 | class Adam(KerasAdam): 15 | """Adapted Keras Adam Optimizer to allow support of calculations 16 | on CPU for Tensorflow. 17 | 18 | Adapted from https://github.com/iperov/DeepFaceLab 19 | """ 20 | 21 | def __init__(self, lr=0.001, beta_1=0.9, beta_2=0.999, 22 | epsilon=None, decay=0., amsgrad=False, cpu_mode=0, **kwargs): 23 | super().__init__(lr, beta_1, beta_2, epsilon, decay, **kwargs) 24 | self.cpu_mode = self.set_cpu_mode(cpu_mode) 25 | 26 | @staticmethod 27 | def set_cpu_mode(cpu_mode): 28 | """ Set the CPU mode to 0 if not using tensorflow, else passed in arg """ 29 | retval = False if K.backend() != "tensorflow" else cpu_mode 30 | logger.debug("Optimizer CPU Mode set to %s", retval) 31 | return retval 32 | 33 | def get_updates(self, loss, params): 34 | grads = self.get_gradients(loss, params) 35 | self.updates = [K.update_add(self.iterations, 1)] 36 | 37 | lr = self.lr 38 | if self.initial_decay > 0: 39 | lr = lr * (1. / (1. + self.decay * K.cast(self.iterations, 40 | K.dtype(self.decay)))) 41 | 42 | t = K.cast(self.iterations, K.floatx()) + 1 43 | lr_t = lr * (K.sqrt(1. - K.pow(self.beta_2, t)) / 44 | (1. - K.pow(self.beta_1, t))) 45 | 46 | # Pass off to CPU if requested 47 | if self.cpu_mode: 48 | with K.tf.device("/cpu:0"): 49 | ms, vs, vhats = self.update_1(params) 50 | else: 51 | ms, vs, vhats = self.update_1(params) 52 | 53 | self.weights = [self.iterations] + ms + vs + vhats 54 | 55 | for p, g, m, v, vhat in zip(params, grads, ms, vs, vhats): 56 | m_t = (self.beta_1 * m) + (1. - self.beta_1) * g 57 | v_t = (self.beta_2 * v) + (1. - self.beta_2) * K.square(g) 58 | if self.amsgrad: 59 | vhat_t = K.maximum(vhat, v_t) 60 | p_t = p - lr_t * m_t / (K.sqrt(vhat_t) + self.epsilon) 61 | self.updates.append(K.update(vhat, vhat_t)) 62 | else: 63 | p_t = p - lr_t * m_t / (K.sqrt(v_t) + self.epsilon) 64 | 65 | self.updates.append(K.update(m, m_t)) 66 | self.updates.append(K.update(v, v_t)) 67 | new_p = p_t 68 | 69 | # Apply constraints. 70 | if getattr(p, 'constraint', None) is not None: 71 | new_p = p.constraint(new_p) 72 | 73 | self.updates.append(K.update(p, new_p)) 74 | return self.updates 75 | 76 | def update_1(self, params): 77 | """ First update on CPU or GPU """ 78 | ms = [K.zeros(K.int_shape(p), dtype=K.dtype(p)) for p in params] 79 | vs = [K.zeros(K.int_shape(p), dtype=K.dtype(p)) for p in params] 80 | if self.amsgrad: 81 | vhats = [K.zeros(K.int_shape(p), dtype=K.dtype(p)) for p in params] 82 | else: 83 | vhats = [K.zeros(1) for _ in params] 84 | return ms, vs, vhats 85 | -------------------------------------------------------------------------------- /plugins/convert/color/match_hist_defaults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | The default options for the faceswap Match_Hist Color plugin. 4 | 5 | Defaults files should be named _defaults.py 6 | Any items placed into this file will automatically get added to the relevant config .ini files 7 | within the faceswap/config folder. 8 | 9 | The following variables should be defined: 10 | _HELPTEXT: A string describing what this plugin does 11 | _DEFAULTS: A dictionary containing the options, defaults and meta information. The 12 | dictionary should be defined as: 13 | {: {}} 14 | 15 | should always be lower text. 16 | dictionary requirements are listed below. 17 | 18 | The following keys are expected for the _DEFAULTS dict: 19 | datatype: [required] A python type class. This limits the type of data that can be 20 | provided in the .ini file and ensures that the value is returned in the 21 | correct type to faceswap. Valid datatypes are: , , 22 | , . 23 | default: [required] The default value for this option. 24 | info: [required] A string describing what this option does. 25 | choices: [optional] If this option's datatype is of then valid 26 | selections can be defined here. This validates the option and also enables 27 | a combobox / radio option in the GUI. 28 | gui_radio: [optional] If are defined, this indicates that the GUI should use 29 | radio buttons rather than a combobox to display this option. 30 | min_max: [partial] For and datatypes this is required 31 | otherwise it is ignored. Should be a tuple of min and max accepted values. 32 | This is used for controlling the GUI slider range. Values are not enforced. 33 | rounding: [partial] For and datatypes this is 34 | required otherwise it is ignored. Used for the GUI slider. For floats, this 35 | is the number of decimal places to display. For ints this is the step size. 36 | fixed: [optional] [train only]. Training configurations are fixed when the model is 37 | created, and then reloaded from the state file. Marking an item as fixed=False 38 | indicates that this value can be changed for existing models, and will override 39 | the value saved in the state file with the updated value in config. If not 40 | provided this will default to True. 41 | """ 42 | 43 | 44 | _HELPTEXT = "Options for matching the histograms between the source and destination faces" 45 | 46 | 47 | _DEFAULTS = { 48 | "threshold": { 49 | "default": 99.0, 50 | "info": "Adjust the threshold for histogram matching. Can reduce extreme colors " 51 | "leaking in by filtering out colors at the extreme ends of the histogram " 52 | "spectrum.", 53 | "datatype": float, 54 | "rounding": 1, 55 | "min_max": (90.0, 100.0), 56 | "choices": [], 57 | "gui_radio": False, 58 | "fixed": True, 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /plugins/train/model/villain_defaults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | The default options for the faceswap Villain Model plugin. 4 | 5 | Defaults files should be named _defaults.py 6 | Any items placed into this file will automatically get added to the relevant config .ini files 7 | within the faceswap/config folder. 8 | 9 | The following variables should be defined: 10 | _HELPTEXT: A string describing what this plugin does 11 | _DEFAULTS: A dictionary containing the options, defaults and meta information. The 12 | dictionary should be defined as: 13 | {: {}} 14 | 15 | should always be lower text. 16 | dictionary requirements are listed below. 17 | 18 | The following keys are expected for the _DEFAULTS dict: 19 | datatype: [required] A python type class. This limits the type of data that can be 20 | provided in the .ini file and ensures that the value is returned in the 21 | correct type to faceswap. Valid datatypes are: , , 22 | , . 23 | default: [required] The default value for this option. 24 | info: [required] A string describing what this option does. 25 | choices: [optional] If this option's datatype is of then valid 26 | selections can be defined here. This validates the option and also enables 27 | a combobox / radio option in the GUI. 28 | gui_radio: [optional] If are defined, this indicates that the GUI should use 29 | radio buttons rather than a combobox to display this option. 30 | min_max: [partial] For and datatypes this is required 31 | otherwise it is ignored. Should be a tuple of min and max accepted values. 32 | This is used for controlling the GUI slider range. Values are not enforced. 33 | rounding: [partial] For and datatypes this is 34 | required otherwise it is ignored. Used for the GUI slider. For floats, this 35 | is the number of decimal places to display. For ints this is the step size. 36 | fixed: [optional] [train only]. Training configurations are fixed when the model is 37 | created, and then reloaded from the state file. Marking an item as fixed=False 38 | indicates that this value can be changed for existing models, and will override 39 | the value saved in the state file with the updated value in config. If not 40 | provided this will default to True. 41 | """ 42 | 43 | 44 | _HELPTEXT = ( 45 | "A Higher resolution version of the Original Model by VillainGuy.\n" 46 | "Extremely VRAM heavy. Full model requires 9GB+ for batchsize 16\n" 47 | ) 48 | 49 | 50 | _DEFAULTS = { 51 | "lowmem": { 52 | "default": False, 53 | "info": "Lower memory mode. Set to 'True' if having issues with VRAM useage.\n" 54 | "NB: Models with a changed lowmem mode are not compatible with each other.", 55 | "datatype": bool, 56 | "rounding": None, 57 | "min_max": None, 58 | "choices": [], 59 | "gui_radio": False, 60 | "group": "settings", 61 | "fixed": True, 62 | }, 63 | } 64 | -------------------------------------------------------------------------------- /lib/gui/_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Default configurations for models """ 3 | 4 | import logging 5 | import sys 6 | import os 7 | from tkinter import font 8 | 9 | from lib.config import FaceswapConfig 10 | 11 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 12 | 13 | 14 | class Config(FaceswapConfig): 15 | """ Config File for GUI """ 16 | # pylint: disable=too-many-statements 17 | def set_defaults(self): 18 | """ Set the default values for config """ 19 | logger.debug("Setting defaults") 20 | self.set_globals() 21 | 22 | def set_globals(self): 23 | """ 24 | Set the global options for GUI 25 | """ 26 | logger.debug("Setting global config") 27 | section = "global" 28 | self.add_section(title=section, 29 | info="Faceswap GUI Options.\nNB: Faceswap will need to be restarted for " 30 | "any changes to take effect.") 31 | self.add_item( 32 | section=section, title="fullscreen", datatype=bool, default=False, group="startup", 33 | info="Start Faceswap maximized.") 34 | self.add_item( 35 | section=section, title="tab", datatype=str, default="extract", group="startup", 36 | choices=get_commands(), 37 | info="Start Faceswap in this tab.") 38 | self.add_item( 39 | section=section, title="options_panel_width", datatype=int, default=30, 40 | min_max=(10, 90), rounding=1, group="layout", 41 | info="How wide the lefthand option panel is as a percentage of GUI width at startup.") 42 | self.add_item( 43 | section=section, title="console_panel_height", datatype=int, default=20, 44 | min_max=(10, 90), rounding=1, group="layout", 45 | info="How tall the bottom console panel is as a percentage of GUI height at startup.") 46 | self.add_item( 47 | section=section, title="font", datatype=str, 48 | choices=get_clean_fonts(), 49 | default="default", group="font", info="Global font") 50 | self.add_item( 51 | section=section, title="font_size", datatype=int, default=9, 52 | min_max=(6, 12), rounding=1, group="font", 53 | info="Global font size.") 54 | 55 | 56 | def get_commands(): 57 | """ Return commands formatted for GUI """ 58 | root_path = os.path.abspath(os.path.dirname(sys.argv[0])) 59 | command_path = os.path.join(root_path, "scripts") 60 | tools_path = os.path.join(root_path, "tools") 61 | commands = [os.path.splitext(item)[0] for item in os.listdir(command_path) 62 | if os.path.splitext(item)[1] == ".py" 63 | and os.path.splitext(item)[0] not in ("gui", "fsmedia") 64 | and not os.path.splitext(item)[0].startswith("_")] 65 | tools = [os.path.splitext(item)[0] for item in os.listdir(tools_path) 66 | if os.path.splitext(item)[1] == ".py" 67 | and os.path.splitext(item)[0] not in ("gui", "cli") 68 | and not os.path.splitext(item)[0].startswith("_")] 69 | return commands + tools 70 | 71 | 72 | def get_clean_fonts(): 73 | """ Return the font list with any @prefixed or non-unicode characters stripped 74 | and default prefixed """ 75 | cleaned_fonts = sorted([fnt for fnt in font.families() 76 | if not fnt.startswith("@") and not any([ord(c) > 127 for c in fnt])]) 77 | return ["default"] + cleaned_fonts 78 | -------------------------------------------------------------------------------- /lib/keypress.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Source: http://home.wlu.edu/~levys/software/kbhit.py 4 | A Python class implementing KBHIT, the standard keyboard-interrupt poller. 5 | Works transparently on Windows and Posix (Linux, Mac OS X). Doesn't work 6 | with IDLE. 7 | 8 | This program is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU Lesser General Public License as 10 | published by the Free Software Foundation, either version 3 of the 11 | License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | """ 18 | 19 | import os 20 | 21 | # Windows 22 | if os.name == "nt": 23 | import msvcrt # pylint: disable=import-error 24 | 25 | # Posix (Linux, OS X) 26 | else: 27 | import sys 28 | import termios 29 | import atexit 30 | from select import select 31 | 32 | 33 | class KBHit: 34 | """ Creates a KBHit object that you can call to do various keyboard things. """ 35 | def __init__(self, is_gui=False): 36 | self.is_gui = is_gui 37 | if os.name == "nt" or self.is_gui: 38 | pass 39 | else: 40 | # Save the terminal settings 41 | self.file_desc = sys.stdin.fileno() 42 | self.new_term = termios.tcgetattr(self.file_desc) 43 | self.old_term = termios.tcgetattr(self.file_desc) 44 | 45 | # New terminal setting unbuffered 46 | self.new_term[3] = (self.new_term[3] & ~termios.ICANON & ~termios.ECHO) 47 | termios.tcsetattr(self.file_desc, termios.TCSAFLUSH, self.new_term) 48 | 49 | # Support normal-terminal reset at exit 50 | atexit.register(self.set_normal_term) 51 | 52 | def set_normal_term(self): 53 | """ Resets to normal terminal. On Windows this is a no-op. """ 54 | if os.name == "nt" or self.is_gui: 55 | pass 56 | else: 57 | termios.tcsetattr(self.file_desc, termios.TCSAFLUSH, self.old_term) 58 | 59 | def getch(self): 60 | """ Returns a keyboard character after kbhit() has been called. 61 | Should not be called in the same program as getarrow(). """ 62 | if self.is_gui and os.name != "nt": 63 | return None 64 | if os.name == "nt": 65 | return msvcrt.getch().decode("utf-8") 66 | return sys.stdin.read(1) 67 | 68 | def getarrow(self): 69 | """ Returns an arrow-key code after kbhit() has been called. Codes are 70 | 0 : up 71 | 1 : right 72 | 2 : down 73 | 3 : left 74 | Should not be called in the same program as getch(). """ 75 | 76 | if self.is_gui: 77 | return None 78 | if os.name == "nt": 79 | msvcrt.getch() # skip 0xE0 80 | char = msvcrt.getch() 81 | vals = [72, 77, 80, 75] 82 | else: 83 | char = sys.stdin.read(3)[2] 84 | vals = [65, 67, 66, 68] 85 | 86 | return vals.index(ord(char.decode("utf-8"))) 87 | 88 | def kbhit(self): 89 | """ Returns True if keyboard character was hit, False otherwise. """ 90 | if self.is_gui and os.name != "nt": 91 | return None 92 | if os.name == "nt": 93 | return msvcrt.kbhit() 94 | d_r, _, _ = select([sys.stdin], [], [], 0) 95 | return d_r != [] 96 | -------------------------------------------------------------------------------- /plugins/extract/detect/s3fd_defaults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | The default options for the faceswap S3Fd Detect plugin. 4 | 5 | Defaults files should be named _defaults.py 6 | Any items placed into this file will automatically get added to the relevant config .ini files 7 | within the faceswap/config folder. 8 | 9 | The following variables should be defined: 10 | _HELPTEXT: A string describing what this plugin does 11 | _DEFAULTS: A dictionary containing the options, defaults and meta information. The 12 | dictionary should be defined as: 13 | {: {}} 14 | 15 | should always be lower text. 16 | dictionary requirements are listed below. 17 | 18 | The following keys are expected for the _DEFAULTS dict: 19 | datatype: [required] A python type class. This limits the type of data that can be 20 | provided in the .ini file and ensures that the value is returned in the 21 | correct type to faceswap. Valid datatypes are: , , 22 | , . 23 | default: [required] The default value for this option. 24 | info: [required] A string describing what this option does. 25 | choices: [optional] If this option's datatype is of then valid 26 | selections can be defined here. This validates the option and also enables 27 | a combobox / radio option in the GUI. 28 | gui_radio: [optional] If are defined, this indicates that the GUI should use 29 | radio buttons rather than a combobox to display this option. 30 | min_max: [partial] For and datatypes this is required 31 | otherwise it is ignored. Should be a tuple of min and max accepted values. 32 | This is used for controlling the GUI slider range. Values are not enforced. 33 | rounding: [partial] For and datatypes this is 34 | required otherwise it is ignored. Used for the GUI slider. For floats, this 35 | is the number of decimal places to display. For ints this is the step size. 36 | fixed: [optional] [train only]. Training configurations are fixed when the model is 37 | created, and then reloaded from the state file. Marking an item as fixed=False 38 | indicates that this value can be changed for existing models, and will override 39 | the value saved in the state file with the updated value in config. If not 40 | provided this will default to True. 41 | """ 42 | 43 | 44 | _HELPTEXT = ( 45 | "S3FD Detector options.Fast on GPU, slow on CPU. Can detect more faces and fewer false " 46 | "positives than other GPU detectors, but is a lot more resource intensive." 47 | ) 48 | 49 | 50 | _DEFAULTS = { 51 | "confidence": { 52 | "default": 50, 53 | "info": "The confidence level at which the detector has succesfully found a face.\n" 54 | "Higher levels will be more discriminating, lower levels will have more false " 55 | "positives.", 56 | "datatype": int, 57 | "rounding": 5, 58 | "min_max": (25, 100), 59 | "choices": [], 60 | "gui_radio": False, 61 | "fixed": True, 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /plugins/extract/detect/cv2_dnn_defaults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | The default options for the faceswap Cv2_Dnn Detect plugin. 4 | 5 | Defaults files should be named _defaults.py 6 | Any items placed into this file will automatically get added to the relevant config .ini files 7 | within the faceswap/config folder. 8 | 9 | The following variables should be defined: 10 | _HELPTEXT: A string describing what this plugin does 11 | _DEFAULTS: A dictionary containing the options, defaults and meta information. The 12 | dictionary should be defined as: 13 | {: {}} 14 | 15 | should always be lower text. 16 | dictionary requirements are listed below. 17 | 18 | The following keys are expected for the _DEFAULTS dict: 19 | datatype: [required] A python type class. This limits the type of data that can be 20 | provided in the .ini file and ensures that the value is returned in the 21 | correct type to faceswap. Valid datatypes are: , , 22 | , . 23 | default: [required] The default value for this option. 24 | info: [required] A string describing what this option does. 25 | choices: [optional] If this option's datatype is of then valid 26 | selections can be defined here. This validates the option and also enables 27 | a combobox / radio option in the GUI. 28 | gui_radio: [optional] If are defined, this indicates that the GUI should use 29 | radio buttons rather than a combobox to display this option. 30 | min_max: [partial] For and datatypes this is required 31 | otherwise it is ignored. Should be a tuple of min and max accepted values. 32 | This is used for controlling the GUI slider range. Values are not enforced. 33 | rounding: [partial] For and datatypes this is 34 | required otherwise it is ignored. Used for the GUI slider. For floats, this 35 | is the number of decimal places to display. For ints this is the step size. 36 | fixed: [optional] [train only]. Training configurations are fixed when the model is 37 | created, and then reloaded from the state file. Marking an item as fixed=False 38 | indicates that this value can be changed for existing models, and will override 39 | the value saved in the state file with the updated value in config. If not 40 | provided this will default to True. 41 | """ 42 | 43 | 44 | _HELPTEXT = ( 45 | "CV2 DNN Detector options.\n" 46 | "A CPU only extractor, is the least reliable, but uses least resources and runs fast on CPU. " 47 | "Use this if not using a GPU and time is important" 48 | ) 49 | 50 | 51 | _DEFAULTS = { 52 | "confidence": { 53 | "default": 50, 54 | "info": "The confidence level at which the detector has succesfully found a face.\n" 55 | "Higher levels will be more discriminating, lower levels will have more false " 56 | "positives.", 57 | "datatype": int, 58 | "rounding": 5, 59 | "min_max": (25, 100), 60 | "choices": [], 61 | "gui_radio": False, 62 | "fixed": True, 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at deefakesrepo@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /plugins/train/model/original.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Original Model 3 | Based on the original https://www.reddit.com/r/deepfakes/ 4 | code sample + contribs """ 5 | 6 | from keras.layers import Dense, Flatten, Input, Reshape 7 | 8 | from keras.models import Model as KerasModel 9 | 10 | from ._base import ModelBase, logger 11 | 12 | 13 | class Model(ModelBase): 14 | """ Original Faceswap Model """ 15 | def __init__(self, *args, **kwargs): 16 | logger.debug("Initializing %s: (args: %s, kwargs: %s", 17 | self.__class__.__name__, args, kwargs) 18 | 19 | self.configfile = kwargs.get("configfile", None) 20 | if "input_shape" not in kwargs: 21 | kwargs["input_shape"] = (64, 64, 3) 22 | if "encoder_dim" not in kwargs: 23 | kwargs["encoder_dim"] = 512 if self.config["lowmem"] else 1024 24 | 25 | super().__init__(*args, **kwargs) 26 | logger.debug("Initialized %s", self.__class__.__name__) 27 | 28 | def add_networks(self): 29 | """ Add the original model weights """ 30 | logger.debug("Adding networks") 31 | self.add_network("decoder", "a", self.decoder(), is_output=True) 32 | self.add_network("decoder", "b", self.decoder(), is_output=True) 33 | self.add_network("encoder", None, self.encoder()) 34 | logger.debug("Added networks") 35 | 36 | def build_autoencoders(self, inputs): 37 | """ Initialize original model """ 38 | logger.debug("Initializing model") 39 | for side in ("a", "b"): 40 | logger.debug("Adding Autoencoder. Side: %s", side) 41 | decoder = self.networks["decoder_{}".format(side)].network 42 | output = decoder(self.networks["encoder"].network(inputs[0])) 43 | autoencoder = KerasModel(inputs, output) 44 | self.add_predictor(side, autoencoder) 45 | logger.debug("Initialized model") 46 | 47 | def encoder(self): 48 | """ Encoder Network """ 49 | input_ = Input(shape=self.input_shape) 50 | var_x = input_ 51 | var_x = self.blocks.conv(var_x, 128) 52 | var_x = self.blocks.conv(var_x, 256) 53 | var_x = self.blocks.conv(var_x, 512) 54 | if not self.config.get("lowmem", False): 55 | var_x = self.blocks.conv(var_x, 1024) 56 | var_x = Dense(self.encoder_dim)(Flatten()(var_x)) 57 | var_x = Dense(4 * 4 * 1024)(var_x) 58 | var_x = Reshape((4, 4, 1024))(var_x) 59 | var_x = self.blocks.upscale(var_x, 512) 60 | return KerasModel(input_, var_x) 61 | 62 | def decoder(self): 63 | """ Decoder Network """ 64 | input_ = Input(shape=(8, 8, 512)) 65 | var_x = input_ 66 | var_x = self.blocks.upscale(var_x, 256) 67 | var_x = self.blocks.upscale(var_x, 128) 68 | var_x = self.blocks.upscale(var_x, 64) 69 | var_x = self.blocks.conv2d(var_x, 3, 70 | kernel_size=5, 71 | padding="same", 72 | activation="sigmoid", 73 | name="face_out") 74 | outputs = [var_x] 75 | 76 | if self.config.get("mask_type", None): 77 | var_y = input_ 78 | var_y = self.blocks.upscale(var_y, 256) 79 | var_y = self.blocks.upscale(var_y, 128) 80 | var_y = self.blocks.upscale(var_y, 64) 81 | var_y = self.blocks.conv2d(var_y, 1, 82 | kernel_size=5, 83 | padding="same", 84 | activation="sigmoid", 85 | name="mask_out") 86 | outputs.append(var_y) 87 | return KerasModel(input_, outputs=outputs) 88 | -------------------------------------------------------------------------------- /plugins/extract/detect/s3fd_amd_defaults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | The default options for the faceswap S3Fd-AMD Detect plugin. 4 | 5 | Defaults files should be named _defaults.py 6 | Any items placed into this file will automatically get added to the relevant config .ini files 7 | within the faceswap/config folder. 8 | 9 | The following variables should be defined: 10 | _HELPTEXT: A string describing what this plugin does 11 | _DEFAULTS: A dictionary containing the options, defaults and meta information. The 12 | dictionary should be defined as: 13 | {: {}} 14 | 15 | should always be lower text. 16 | dictionary requirements are listed below. 17 | 18 | The following keys are expected for the _DEFAULTS dict: 19 | datatype: [required] A python type class. This limits the type of data that can be 20 | provided in the .ini file and ensures that the value is returned in the 21 | correct type to faceswap. Valid datatypes are: , , 22 | , . 23 | default: [required] The default value for this option. 24 | info: [required] A string describing what this option does. 25 | choices: [optional] If this option's datatype is of then valid 26 | selections can be defined here. This validates the option and also enables 27 | a combobox / radio option in the GUI. 28 | gui_radio: [optional] If are defined, this indicates that the GUI should use 29 | radio buttons rather than a combobox to display this option. 30 | min_max: [partial] For and datatypes this is required 31 | otherwise it is ignored. Should be a tuple of min and max accepted values. 32 | This is used for controlling the GUI slider range. Values are not enforced. 33 | rounding: [partial] For and datatypes this is 34 | required otherwise it is ignored. Used for the GUI slider. For floats, this 35 | is the number of decimal places to display. For ints this is the step size. 36 | fixed: [optional] [train only]. Training configurations are fixed when the model is 37 | created, and then reloaded from the state file. Marking an item as fixed=False 38 | indicates that this value can be changed for existing models, and will override 39 | the value saved in the state file with the updated value in config. If not 40 | provided this will default to True. 41 | """ 42 | 43 | 44 | _HELPTEXT = ( 45 | "S3FD-AMD Detector options. Uses keras backend to support AMD cards.\n" 46 | "Fast on GPU, slow on CPU. Can detect more faces and fewer false " 47 | "positives than other GPU detectors, but is a lot more resource intensive." 48 | ) 49 | 50 | 51 | _DEFAULTS = { 52 | "confidence": { 53 | "default": 50, 54 | "info": "The confidence level at which the detector has succesfully found a face.\n" 55 | "Higher levels will be more discriminating, lower levels will have more false " 56 | "positives.", 57 | "datatype": int, 58 | "rounding": 5, 59 | "min_max": (25, 100), 60 | "choices": [], 61 | "gui_radio": False, 62 | "fixed": True, 63 | }, 64 | "batch-size": { 65 | "default": 8, 66 | "info": "The batch size to use. Normally higher batch sizes equal better performance.\n" 67 | "A batchsize of 8 requires about 2 GB vram.", 68 | "datatype": int, 69 | "rounding": 1, 70 | "min_max": (1, 32), 71 | "choices": [], 72 | "gui_radio": False, 73 | "fixed": True, 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /plugins/train/model/iae.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Improved autoencoder for faceswap """ 3 | 4 | from keras.layers import Concatenate, Dense, Flatten, Input, Reshape 5 | from keras.models import Model as KerasModel 6 | 7 | from ._base import ModelBase, logger 8 | 9 | 10 | class Model(ModelBase): 11 | """ Improved Autoeencoder Model """ 12 | def __init__(self, *args, **kwargs): 13 | logger.debug("Initializing %s: (args: %s, kwargs: %s", 14 | self.__class__.__name__, args, kwargs) 15 | kwargs["input_shape"] = (64, 64, 3) 16 | kwargs["encoder_dim"] = 1024 17 | super().__init__(*args, **kwargs) 18 | logger.debug("Initialized %s", self.__class__.__name__) 19 | 20 | def add_networks(self): 21 | """ Add the IAE model weights """ 22 | logger.debug("Adding networks") 23 | self.add_network("encoder", None, self.encoder()) 24 | self.add_network("decoder", None, self.decoder(), is_output=True) 25 | self.add_network("intermediate", "a", self.intermediate()) 26 | self.add_network("intermediate", "b", self.intermediate()) 27 | self.add_network("inter", None, self.intermediate()) 28 | logger.debug("Added networks") 29 | 30 | def build_autoencoders(self, inputs): 31 | """ Initialize IAE model """ 32 | logger.debug("Initializing model") 33 | decoder = self.networks["decoder"].network 34 | encoder = self.networks["encoder"].network 35 | inter_both = self.networks["inter"].network 36 | for side in ("a", "b"): 37 | inter_side = self.networks["intermediate_{}".format(side)].network 38 | output = decoder(Concatenate()([inter_side(encoder(inputs[0])), 39 | inter_both(encoder(inputs[0]))])) 40 | 41 | autoencoder = KerasModel(inputs, output) 42 | self.add_predictor(side, autoencoder) 43 | logger.debug("Initialized model") 44 | 45 | def encoder(self): 46 | """ Encoder Network """ 47 | input_ = Input(shape=self.input_shape) 48 | var_x = input_ 49 | var_x = self.blocks.conv(var_x, 128) 50 | var_x = self.blocks.conv(var_x, 256) 51 | var_x = self.blocks.conv(var_x, 512) 52 | var_x = self.blocks.conv(var_x, 1024) 53 | var_x = Flatten()(var_x) 54 | return KerasModel(input_, var_x) 55 | 56 | def intermediate(self): 57 | """ Intermediate Network """ 58 | input_ = Input(shape=(None, 4 * 4 * 1024)) 59 | var_x = input_ 60 | var_x = Dense(self.encoder_dim)(var_x) 61 | var_x = Dense(4 * 4 * int(self.encoder_dim/2))(var_x) 62 | var_x = Reshape((4, 4, int(self.encoder_dim/2)))(var_x) 63 | return KerasModel(input_, var_x) 64 | 65 | def decoder(self): 66 | """ Decoder Network """ 67 | input_ = Input(shape=(4, 4, self.encoder_dim)) 68 | var_x = input_ 69 | var_x = self.blocks.upscale(var_x, 512) 70 | var_x = self.blocks.upscale(var_x, 256) 71 | var_x = self.blocks.upscale(var_x, 128) 72 | var_x = self.blocks.upscale(var_x, 64) 73 | var_x = self.blocks.conv2d(var_x, 3, 74 | kernel_size=5, 75 | padding="same", 76 | activation="sigmoid", 77 | name="face_out") 78 | outputs = [var_x] 79 | 80 | if self.config.get("mask_type", None): 81 | var_y = input_ 82 | var_y = self.blocks.upscale(var_y, 512) 83 | var_y = self.blocks.upscale(var_y, 256) 84 | var_y = self.blocks.upscale(var_y, 128) 85 | var_y = self.blocks.upscale(var_y, 64) 86 | var_y = self.blocks.conv2d(var_y, 1, 87 | kernel_size=5, 88 | padding="same", 89 | activation="sigmoid", 90 | name="mask_out") 91 | outputs.append(var_y) 92 | return KerasModel(input_, outputs=outputs) 93 | -------------------------------------------------------------------------------- /plugins/convert/writer/gif.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Animated GIF writer for faceswap.py converter """ 3 | import os 4 | 5 | import cv2 6 | import imageio 7 | 8 | from ._base import Output, logger 9 | 10 | 11 | class Writer(Output): 12 | """ Video output writer using imageio """ 13 | def __init__(self, output_folder, total_count, frame_ranges, **kwargs): 14 | logger.debug("total_count: %s, frame_ranges: %s", total_count, frame_ranges) 15 | super().__init__(output_folder, **kwargs) 16 | self.frame_order = self.set_frame_order(total_count, frame_ranges) 17 | self.output_dimensions = None # Fix dims of 1st frame in case of different sized images 18 | self.writer = None # Need to know dimensions of first frame, so set writer then 19 | self.gif_file = None # Set filename based on first file seen 20 | 21 | @property 22 | def gif_params(self): 23 | """ Format the gif params """ 24 | kwargs = {key: int(val) for key, val in self.config.items()} 25 | logger.debug(kwargs) 26 | return kwargs 27 | 28 | @staticmethod 29 | def set_frame_order(total_count, frame_ranges): 30 | """ Return the full list of frames to be converted in order """ 31 | if frame_ranges is None: 32 | retval = list(range(1, total_count + 1)) 33 | else: 34 | retval = list() 35 | for rng in frame_ranges: 36 | retval.extend(list(range(rng[0], rng[1] + 1))) 37 | logger.debug("frame_order: %s", retval) 38 | return retval 39 | 40 | def get_writer(self): 41 | """ Add the requested encoding options and return the writer """ 42 | logger.debug("writer config: %s", self.config) 43 | return imageio.get_writer(self.gif_file, 44 | mode="i", 45 | **self.config) 46 | 47 | def write(self, filename, image): 48 | """ Frames come from the pool in arbitrary order, so cache frames 49 | for writing out in correct order """ 50 | logger.trace("Received frame: (filename: '%s', shape: %s", filename, image.shape) 51 | if not self.gif_file: 52 | self.set_gif_filename(filename) 53 | self.set_dimensions(image.shape[:2]) 54 | self.writer = self.get_writer() 55 | if (image.shape[1], image.shape[0]) != self.output_dimensions: 56 | image = cv2.resize(image, self.output_dimensions) # pylint: disable=no-member 57 | self.cache_frame(filename, image) 58 | self.save_from_cache() 59 | 60 | def set_gif_filename(self, filename): 61 | """ Set the gif output filename """ 62 | logger.debug("sample filename: '%s'", filename) 63 | filename = os.path.splitext(os.path.basename(filename))[0] 64 | idx = len(filename) 65 | for char in list(filename[::-1]): 66 | if not char.isdigit() and char not in ("_", "-"): 67 | break 68 | idx -= 1 69 | self.gif_file = os.path.join(self.output_folder, "{}_converted.gif".format(filename[:idx])) 70 | logger.info("Outputting to: '%s'", self.gif_file) 71 | 72 | def set_dimensions(self, frame_dims): 73 | """ Set the dimensions based on a given frame frame. This protects against different 74 | sized images coming in and ensure all images go out at the same size for writers 75 | that require it """ 76 | logger.debug("input dimensions: %s", frame_dims) 77 | self.output_dimensions = (frame_dims[1], frame_dims[0]) 78 | logger.debug("Set dimensions: %s", self.output_dimensions) 79 | 80 | def save_from_cache(self): 81 | """ Save all the frames that are ready to be output from cache """ 82 | while self.frame_order: 83 | if self.frame_order[0] not in self.cache: 84 | logger.trace("Next frame not ready. Continuing") 85 | break 86 | save_no = self.frame_order.pop(0) 87 | save_image = self.cache.pop(save_no) 88 | logger.trace("Rendering from cache. Frame no: %s", save_no) 89 | self.writer.append_data(save_image[:, :, ::-1]) 90 | logger.trace("Current cache size: %s", len(self.cache)) 91 | 92 | def close(self): 93 | """ Close the ffmpeg writer and mux the audio """ 94 | self.writer.close() 95 | -------------------------------------------------------------------------------- /plugins/convert/writer/gif_defaults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | The default options for the faceswap Gif Writer plugin. 4 | 5 | Defaults files should be named _defaults.py 6 | Any items placed into this file will automatically get added to the relevant config .ini files 7 | within the faceswap/config folder. 8 | 9 | The following variables should be defined: 10 | _HELPTEXT: A string describing what this plugin does 11 | _DEFAULTS: A dictionary containing the options, defaults and meta information. The 12 | dictionary should be defined as: 13 | {: {}} 14 | 15 | should always be lower text. 16 | dictionary requirements are listed below. 17 | 18 | The following keys are expected for the _DEFAULTS dict: 19 | datatype: [required] A python type class. This limits the type of data that can be 20 | provided in the .ini file and ensures that the value is returned in the 21 | correct type to faceswap. Valid datatypes are: , , 22 | , . 23 | default: [required] The default value for this option. 24 | info: [required] A string describing what this option does. 25 | choices: [optional] If this option's datatype is of then valid 26 | selections can be defined here. This validates the option and also enables 27 | a combobox / radio option in the GUI. 28 | gui_radio: [optional] If are defined, this indicates that the GUI should use 29 | radio buttons rather than a combobox to display this option. 30 | min_max: [partial] For and datatypes this is required 31 | otherwise it is ignored. Should be a tuple of min and max accepted values. 32 | This is used for controlling the GUI slider range. Values are not enforced. 33 | rounding: [partial] For and datatypes this is 34 | required otherwise it is ignored. Used for the GUI slider. For floats, this 35 | is the number of decimal places to display. For ints this is the step size. 36 | fixed: [optional] [train only]. Training configurations are fixed when the model is 37 | created, and then reloaded from the state file. Marking an item as fixed=False 38 | indicates that this value can be changed for existing models, and will override 39 | the value saved in the state file with the updated value in config. If not 40 | provided this will default to True. 41 | """ 42 | 43 | 44 | _HELPTEXT = "Options for outputting converted frames to an animated gif." 45 | 46 | 47 | _DEFAULTS = { 48 | "fps": { 49 | "default": 25, 50 | "info": "Frames per Second.", 51 | "datatype": int, 52 | "rounding": 1, 53 | "min_max": (1, 60), 54 | "choices": [], 55 | "group": "settings", 56 | "gui_radio": False, 57 | "fixed": True, 58 | }, 59 | "loop": { 60 | "default": 0, 61 | "info": "The number of iterations. Set to 0 to loop indefinitely.", 62 | "datatype": int, 63 | "rounding": 1, 64 | "min_max": (0, 100), 65 | "choices": [], 66 | "group": "settings", 67 | "gui_radio": False, 68 | "fixed": True, 69 | }, 70 | "palettesize": { 71 | "default": "256", 72 | "info": "The number of colors to quantize the image to. Is rounded to the nearest " 73 | "power of two.", 74 | "datatype": str, 75 | "rounding": None, 76 | "min_max": None, 77 | "choices": ["2", "4", "8", "16", "32", "64", "128", "256"], 78 | "group": "settings", 79 | "gui_radio": False, 80 | "fixed": True, 81 | }, 82 | "subrectangles": { 83 | "default": False, 84 | "info": "If True, will try and optimize the GIF by storing only the rectangular parts " 85 | "of each frame that change with respect to the previous.", 86 | "datatype": bool, 87 | "rounding": None, 88 | "min_max": None, 89 | "choices": [], 90 | "group": "settings", 91 | "gui_radio": False, 92 | "fixed": True, 93 | }, 94 | } 95 | -------------------------------------------------------------------------------- /plugins/train/model/villain.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Original - VillainGuy model 3 | Based on the original https://www.reddit.com/r/deepfakes/ code sample + contribs 4 | Adapted from a model by VillainGuy (https://github.com/VillainGuy) """ 5 | 6 | from keras.initializers import RandomNormal 7 | from keras.layers import add, Dense, Flatten, Input, Reshape 8 | from keras.models import Model as KerasModel 9 | 10 | from lib.model.layers import PixelShuffler 11 | from .original import logger, Model as OriginalModel 12 | 13 | 14 | class Model(OriginalModel): 15 | """ Villain Faceswap Model """ 16 | def __init__(self, *args, **kwargs): 17 | logger.debug("Initializing %s: (args: %s, kwargs: %s", 18 | self.__class__.__name__, args, kwargs) 19 | 20 | self.configfile = kwargs.get("configfile", None) 21 | kwargs["input_shape"] = (128, 128, 3) 22 | kwargs["encoder_dim"] = 512 if self.config["lowmem"] else 1024 23 | self.kernel_initializer = RandomNormal(0, 0.02) 24 | 25 | super().__init__(*args, **kwargs) 26 | logger.debug("Initialized %s", self.__class__.__name__) 27 | 28 | def encoder(self): 29 | """ Encoder Network """ 30 | kwargs = dict(kernel_initializer=self.kernel_initializer) 31 | input_ = Input(shape=self.input_shape) 32 | in_conv_filters = self.input_shape[0] 33 | if self.input_shape[0] > 128: 34 | in_conv_filters = 128 + (self.input_shape[0] - 128) // 4 35 | dense_shape = self.input_shape[0] // 16 36 | 37 | var_x = self.blocks.conv(input_, in_conv_filters, res_block_follows=True, **kwargs) 38 | tmp_x = var_x 39 | res_cycles = 8 if self.config.get("lowmem", False) else 16 40 | for _ in range(res_cycles): 41 | nn_x = self.blocks.res_block(var_x, 128, **kwargs) 42 | var_x = nn_x 43 | # consider adding scale before this layer to scale the residual chain 44 | var_x = add([var_x, tmp_x]) 45 | var_x = self.blocks.conv(var_x, 128, **kwargs) 46 | var_x = PixelShuffler()(var_x) 47 | var_x = self.blocks.conv(var_x, 128, **kwargs) 48 | var_x = PixelShuffler()(var_x) 49 | var_x = self.blocks.conv(var_x, 128, **kwargs) 50 | var_x = self.blocks.conv_sep(var_x, 256, **kwargs) 51 | var_x = self.blocks.conv(var_x, 512, **kwargs) 52 | if not self.config.get("lowmem", False): 53 | var_x = self.blocks.conv_sep(var_x, 1024, **kwargs) 54 | 55 | var_x = Dense(self.encoder_dim, **kwargs)(Flatten()(var_x)) 56 | var_x = Dense(dense_shape * dense_shape * 1024, **kwargs)(var_x) 57 | var_x = Reshape((dense_shape, dense_shape, 1024))(var_x) 58 | var_x = self.blocks.upscale(var_x, 512, **kwargs) 59 | return KerasModel(input_, var_x) 60 | 61 | def decoder(self): 62 | """ Decoder Network """ 63 | kwargs = dict(kernel_initializer=self.kernel_initializer) 64 | decoder_shape = self.input_shape[0] // 8 65 | input_ = Input(shape=(decoder_shape, decoder_shape, 512)) 66 | 67 | var_x = input_ 68 | var_x = self.blocks.upscale(var_x, 512, res_block_follows=True, **kwargs) 69 | var_x = self.blocks.res_block(var_x, 512, **kwargs) 70 | var_x = self.blocks.upscale(var_x, 256, res_block_follows=True, **kwargs) 71 | var_x = self.blocks.res_block(var_x, 256, **kwargs) 72 | var_x = self.blocks.upscale(var_x, self.input_shape[0], res_block_follows=True, **kwargs) 73 | var_x = self.blocks.res_block(var_x, self.input_shape[0], **kwargs) 74 | var_x = self.blocks.conv2d(var_x, 3, 75 | kernel_size=5, 76 | padding="same", 77 | activation="sigmoid", 78 | name="face_out") 79 | outputs = [var_x] 80 | 81 | if self.config.get("mask_type", None): 82 | var_y = input_ 83 | var_y = self.blocks.upscale(var_y, 512) 84 | var_y = self.blocks.upscale(var_y, 256) 85 | var_y = self.blocks.upscale(var_y, self.input_shape[0]) 86 | var_y = self.blocks.conv2d(var_y, 1, 87 | kernel_size=5, 88 | padding="same", 89 | activation="sigmoid", 90 | name="mask_out") 91 | outputs.append(var_y) 92 | return KerasModel(input_, outputs=outputs) 93 | -------------------------------------------------------------------------------- /plugins/extract/detect/cv2_dnn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ OpenCV DNN Face detection plugin """ 3 | 4 | import numpy as np 5 | 6 | from ._base import cv2, Detector, logger 7 | 8 | 9 | class Detect(Detector): 10 | """ CV2 DNN detector for face recognition """ 11 | def __init__(self, **kwargs): 12 | git_model_id = 4 13 | model_filename = ["resnet_ssd_v1.caffemodel", "resnet_ssd_v1.prototxt"] 14 | super().__init__(git_model_id=git_model_id, model_filename=model_filename, **kwargs) 15 | self.target = (300, 300) # Doesn't use VRAM 16 | self.vram = 0 17 | self.detector = None 18 | self.confidence = self.config["confidence"] / 100 19 | 20 | def initialize(self, *args, **kwargs): 21 | """ Calculate batch size """ 22 | super().initialize(*args, **kwargs) 23 | logger.info("Initializing cv2 DNN Detector...") 24 | logger.verbose("Using CPU for detection") 25 | self.detector = cv2.dnn.readNetFromCaffe(self.model_path[1], # pylint: disable=no-member 26 | self.model_path[0]) 27 | self.detector.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU) # pylint: disable=no-member 28 | self.init.set() 29 | logger.info("Initialized cv2 DNN Detector.") 30 | 31 | def detect_faces(self, *args, **kwargs): 32 | """ Detect faces in grayscale image """ 33 | super().detect_faces(*args, **kwargs) 34 | while True: 35 | item = self.get_item() 36 | if item == "EOF": 37 | break 38 | logger.trace("Detecting faces: %s", item["filename"]) 39 | [detect_image, scale] = self.compile_detection_image(item["image"], 40 | is_square=True, 41 | scale_up=True) 42 | height, width = detect_image.shape[:2] 43 | for angle in self.rotation: 44 | current_image, rotmat = self.rotate_image(detect_image, angle) 45 | logger.trace("Detecting faces") 46 | 47 | blob = cv2.dnn.blobFromImage(current_image, # pylint: disable=no-member 48 | 1.0, 49 | self.target, 50 | [104, 117, 123], 51 | False, 52 | False) 53 | self.detector.setInput(blob) 54 | detected = self.detector.forward() 55 | faces = list() 56 | for i in range(detected.shape[2]): 57 | confidence = detected[0, 0, i, 2] 58 | if confidence >= self.confidence: 59 | logger.trace("Accepting due to confidence %s >= %s", 60 | confidence, self.confidence) 61 | faces.append([(detected[0, 0, i, 3] * width), 62 | (detected[0, 0, i, 4] * height), 63 | (detected[0, 0, i, 5] * width), 64 | (detected[0, 0, i, 6] * height)]) 65 | 66 | logger.trace("Detected faces: %s", [face for face in faces]) 67 | 68 | if angle != 0 and faces: 69 | logger.verbose("found face(s) by rotating image %s degrees", angle) 70 | 71 | if faces: 72 | break 73 | 74 | detected_faces = self.process_output(faces, rotmat, scale) 75 | item["detected_faces"] = detected_faces 76 | self.finalize(item) 77 | 78 | self.queues["out"].put("EOF") 79 | logger.debug("Detecting Faces Complete") 80 | 81 | def process_output(self, faces, rotation_matrix, scale): 82 | """ Compile found faces for output """ 83 | logger.trace("Processing Output: (faces: %s, rotation_matrix: %s)", 84 | faces, rotation_matrix) 85 | 86 | faces = [self.to_bounding_box_dict(face[0], face[1], face[2], face[3]) for face in faces] 87 | if isinstance(rotation_matrix, np.ndarray): 88 | faces = [self.rotate_rect(face, rotation_matrix) 89 | for face in faces] 90 | detected = [self.to_bounding_box_dict(face["left"] / scale, face["top"] / scale, 91 | face["right"] / scale, face["bottom"] / scale) 92 | for face in faces] 93 | 94 | logger.trace("Processed Output: %s", detected) 95 | return detected 96 | -------------------------------------------------------------------------------- /plugins/convert/color/color_transfer_defaults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | The default options for the faceswap Color_Transfer Color plugin. 4 | 5 | Defaults files should be named _defaults.py 6 | Any items placed into this file will automatically get added to the relevant config .ini files 7 | within the faceswap/config folder. 8 | 9 | The following variables should be defined: 10 | _HELPTEXT: A string describing what this plugin does 11 | _DEFAULTS: A dictionary containing the options, defaults and meta information. The 12 | dictionary should be defined as: 13 | {: {}} 14 | 15 | should always be lower text. 16 | dictionary requirements are listed below. 17 | 18 | The following keys are expected for the _DEFAULTS dict: 19 | datatype: [required] A python type class. This limits the type of data that can be 20 | provided in the .ini file and ensures that the value is returned in the 21 | correct type to faceswap. Valid datatypes are: , , 22 | , . 23 | default: [required] The default value for this option. 24 | info: [required] A string describing what this option does. 25 | choices: [optional] If this option's datatype is of then valid 26 | selections can be defined here. This validates the option and also enables 27 | a combobox / radio option in the GUI. 28 | gui_radio: [optional] If are defined, this indicates that the GUI should use 29 | radio buttons rather than a combobox to display this option. 30 | min_max: [partial] For and datatypes this is required 31 | otherwise it is ignored. Should be a tuple of min and max accepted values. 32 | This is used for controlling the GUI slider range. Values are not enforced. 33 | rounding: [partial] For and datatypes this is 34 | required otherwise it is ignored. Used for the GUI slider. For floats, this 35 | is the number of decimal places to display. For ints this is the step size. 36 | fixed: [optional] [train only]. Training configurations are fixed when the model is 37 | created, and then reloaded from the state file. Marking an item as fixed=False 38 | indicates that this value can be changed for existing models, and will override 39 | the value saved in the state file with the updated value in config. If not 40 | provided this will default to True. 41 | """ 42 | 43 | 44 | _HELPTEXT = ( 45 | "Options for transfering the color distribution from the source to the target image using the " 46 | "mean and standard deviations of the L*a*b* color space.\nThis implementation is (loosely) " 47 | "based on the 'Color Transfer between Images' paper by Reinhard et al., 2001. matching the " 48 | "histograms between the source and destination faces." 49 | ) 50 | 51 | 52 | _DEFAULTS = { 53 | "clip": { 54 | "default": True, 55 | "info": "Should components of L*a*b* image be scaled by np.clip before converting " 56 | "back to BGR color space?\nIf False then components will be min-max scaled " 57 | "appropriately.\nClipping will keep target image brightness truer to the " 58 | "input.\nScaling will adjust image brightness to avoid washed out portions in " 59 | "the resulting color transfer that can be caused by clipping.", 60 | "datatype": bool, 61 | "group": "method", 62 | "rounding": None, 63 | "min_max": None, 64 | "choices": [], 65 | "gui_radio": False, 66 | "fixed": True, 67 | }, 68 | "preserve_paper": { 69 | "default": True, 70 | "info": "Should color transfer strictly follow methodology layed out in original " 71 | "paper?\nThe method does not always produce aesthetically pleasing results.\n" 72 | "If False then L*a*b* components will be scaled using the reciprocal of the " 73 | "scaling factor proposed in the paper. This method seems to produce more " 74 | "consistently aesthetically pleasing results.", 75 | "datatype": bool, 76 | "group": "method", 77 | "rounding": None, 78 | "min_max": None, 79 | "choices": [], 80 | "gui_radio": False, 81 | "fixed": True, 82 | }, 83 | } 84 | -------------------------------------------------------------------------------- /lib/gui/display.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin python3 2 | """ Display Frame of the Faceswap GUI 3 | 4 | What is displayed in the Display Frame varies 5 | depending on what tasked is being run """ 6 | 7 | import logging 8 | import tkinter as tk 9 | from tkinter import ttk 10 | 11 | from .display_analysis import Analysis 12 | from .display_command import GraphDisplay, PreviewExtract, PreviewTrain 13 | from .utils import get_config 14 | 15 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 16 | 17 | 18 | class DisplayNotebook(ttk.Notebook): # pylint: disable=too-many-ancestors 19 | """ The display tabs """ 20 | 21 | def __init__(self, parent): 22 | logger.debug("Initializing %s", self.__class__.__name__) 23 | super().__init__(parent) 24 | parent.add(self) 25 | tk_vars = get_config().tk_vars 26 | self.wrapper_var = tk_vars["display"] 27 | self.runningtask = tk_vars["runningtask"] 28 | 29 | self.set_wrapper_var_trace() 30 | self.add_static_tabs() 31 | self.static_tabs = [child for child in self.tabs()] 32 | logger.debug("Initialized %s", self.__class__.__name__) 33 | 34 | def set_wrapper_var_trace(self): 35 | """ Set the trigger actions for the display vars 36 | when they have been triggered in the Process Wrapper """ 37 | logger.debug("Setting wrapper var trace") 38 | self.wrapper_var.trace("w", self.update_displaybook) 39 | 40 | def add_static_tabs(self): 41 | """ Add tabs that are permanently available """ 42 | logger.debug("Adding static tabs") 43 | for tab in ("job queue", "analysis"): 44 | if tab == "job queue": 45 | continue # Not yet implemented 46 | if tab == "analysis": 47 | helptext = {"stats": 48 | "Summary statistics for each training session"} 49 | frame = Analysis(self, tab, helptext) 50 | else: 51 | frame = self.add_frame() 52 | self.add(frame, text=tab.title()) 53 | 54 | def add_frame(self): 55 | """ Add a single frame for holding tab's contents """ 56 | logger.debug("Adding frame") 57 | frame = ttk.Frame(self) 58 | frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5) 59 | return frame 60 | 61 | def command_display(self, command): 62 | """ Select what to display based on incoming 63 | command """ 64 | build_tabs = getattr(self, "{}_tabs".format(command)) 65 | build_tabs() 66 | 67 | def extract_tabs(self, command="extract"): 68 | """ Build the extract tabs """ 69 | logger.debug("Build extract tabs") 70 | helptext = ("Updates preview from output every 5 " 71 | "seconds to limit disk contention") 72 | PreviewExtract(self, "preview", helptext, 5000, command) 73 | logger.debug("Built extract tabs") 74 | 75 | def train_tabs(self): 76 | """ Build the train tabs """ 77 | logger.debug("Build train tabs") 78 | for tab in ("graph", "preview"): 79 | if tab == "graph": 80 | helptext = "Graph showing Loss vs Iterations" 81 | GraphDisplay(self, "graph", helptext, 5000) 82 | elif tab == "preview": 83 | helptext = "Training preview. Updated on every save iteration" 84 | PreviewTrain(self, "preview", helptext, 1000) 85 | logger.debug("Built train tabs") 86 | 87 | def convert_tabs(self): 88 | """ Build the convert tabs 89 | Currently identical to Extract, so just call that """ 90 | logger.debug("Build convert tabs") 91 | self.extract_tabs(command="convert") 92 | logger.debug("Built convert tabs") 93 | 94 | def remove_tabs(self): 95 | """ Remove all command specific tabs """ 96 | for child in self.tabs(): 97 | if child in self.static_tabs: 98 | continue 99 | logger.debug("removing child: %s", child) 100 | child_name = child.split(".")[-1] 101 | child_object = self.children[child_name] # returns the OptionalDisplayPage object 102 | child_object.close() # Call the OptionalDisplayPage close() method 103 | self.forget(child) 104 | 105 | def update_displaybook(self, *args): # pylint: disable=unused-argument 106 | """ Set the display tabs based on executing task """ 107 | command = self.wrapper_var.get() 108 | self.remove_tabs() 109 | if not command or command not in ("extract", "train", "convert"): 110 | return 111 | self.command_display(command) 112 | -------------------------------------------------------------------------------- /plugins/plugin_loader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Plugin loader for extract, training and model tasks """ 3 | 4 | import logging 5 | import os 6 | from importlib import import_module 7 | 8 | from lib.utils import get_backend 9 | 10 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 11 | 12 | 13 | class PluginLoader(): 14 | """ Plugin loader for extract, training and model tasks """ 15 | @staticmethod 16 | def get_detector(name, disable_logging=False): 17 | """ Return requested detector plugin """ 18 | return PluginLoader._import("extract.detect", name, disable_logging) 19 | 20 | @staticmethod 21 | def get_aligner(name, disable_logging=False): 22 | """ Return requested detector plugin """ 23 | return PluginLoader._import("extract.align", name, disable_logging) 24 | 25 | @staticmethod 26 | def get_model(name, disable_logging=False): 27 | """ Return requested model plugin """ 28 | return PluginLoader._import("train.model", name, disable_logging) 29 | 30 | @staticmethod 31 | def get_trainer(name, disable_logging=False): 32 | """ Return requested trainer plugin """ 33 | return PluginLoader._import("train.trainer", name, disable_logging) 34 | 35 | @staticmethod 36 | def get_converter(category, name, disable_logging=False): 37 | """ Return the converter sub plugin """ 38 | return PluginLoader._import("convert.{}".format(category), name, disable_logging) 39 | 40 | @staticmethod 41 | def _import(attr, name, disable_logging): 42 | """ Import the plugin's module """ 43 | name = name.replace("-", "_") 44 | ttl = attr.split(".")[-1].title() 45 | if not disable_logging: 46 | logger.info("Loading %s from %s plugin...", ttl, name.title()) 47 | attr = "model" if attr == "Trainer" else attr.lower() 48 | mod = ".".join(("plugins", attr, name)) 49 | module = import_module(mod) 50 | return getattr(module, ttl) 51 | 52 | @staticmethod 53 | def get_available_extractors(extractor_type): 54 | """ Return a list of available aligners/detectors """ 55 | extractpath = os.path.join(os.path.dirname(__file__), 56 | "extract", 57 | extractor_type) 58 | extractors = sorted(item.name.replace(".py", "").replace("_", "-") 59 | for item in os.scandir(extractpath) 60 | if not item.name.startswith("_") 61 | and not item.name.endswith("defaults.py") 62 | and item.name.endswith(".py") 63 | and item.name != "manual.py") 64 | # TODO Remove this hacky fix when we move them to the same models 65 | multi_versions = [extractor.replace("-amd", "") 66 | for extractor in extractors if extractor.endswith("-amd")] 67 | if get_backend() == "amd": 68 | for extractor in multi_versions: 69 | extractors.remove(extractor) 70 | else: 71 | for extractor in multi_versions: 72 | extractors.remove("{}-amd".format(extractor)) 73 | return extractors 74 | 75 | @staticmethod 76 | def get_available_models(): 77 | """ Return a list of available models """ 78 | modelpath = os.path.join(os.path.dirname(__file__), "train", "model") 79 | models = sorted(item.name.replace(".py", "").replace("_", "-") 80 | for item in os.scandir(modelpath) 81 | if not item.name.startswith("_") 82 | and not item.name.endswith("defaults.py") 83 | and item.name.endswith(".py")) 84 | return models 85 | 86 | @staticmethod 87 | def get_default_model(): 88 | """ Return the default model """ 89 | models = PluginLoader.get_available_models() 90 | return 'original' if 'original' in models else models[0] 91 | 92 | @staticmethod 93 | def get_available_convert_plugins(convert_category, add_none=True): 94 | """ Return a list of available models """ 95 | convertpath = os.path.join(os.path.dirname(__file__), 96 | "convert", 97 | convert_category) 98 | converters = sorted(item.name.replace(".py", "").replace("_", "-") 99 | for item in os.scandir(convertpath) 100 | if not item.name.startswith("_") 101 | and not item.name.endswith("defaults.py") 102 | and item.name.endswith(".py")) 103 | if add_none: 104 | converters.insert(0, "none") 105 | return converters 106 | -------------------------------------------------------------------------------- /plugins/extract/detect/mtcnn_defaults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | The default options for the faceswap Mtcnn Detect plugin. 4 | 5 | Defaults files should be named _defaults.py 6 | Any items placed into this file will automatically get added to the relevant config .ini files 7 | within the faceswap/config folder. 8 | 9 | The following variables should be defined: 10 | _HELPTEXT: A string describing what this plugin does 11 | _DEFAULTS: A dictionary containing the options, defaults and meta information. The 12 | dictionary should be defined as: 13 | {: {}} 14 | 15 | should always be lower text. 16 | dictionary requirements are listed below. 17 | 18 | The following keys are expected for the _DEFAULTS dict: 19 | datatype: [required] A python type class. This limits the type of data that can be 20 | provided in the .ini file and ensures that the value is returned in the 21 | correct type to faceswap. Valid datatypes are: , , 22 | , . 23 | default: [required] The default value for this option. 24 | info: [required] A string describing what this option does. 25 | choices: [optional] If this option's datatype is of then valid 26 | selections can be defined here. This validates the option and also enables 27 | a combobox / radio option in the GUI. 28 | gui_radio: [optional] If are defined, this indicates that the GUI should use 29 | radio buttons rather than a combobox to display this option. 30 | min_max: [partial] For and datatypes this is required 31 | otherwise it is ignored. Should be a tuple of min and max accepted values. 32 | This is used for controlling the GUI slider range. Values are not enforced. 33 | rounding: [partial] For and datatypes this is 34 | required otherwise it is ignored. Used for the GUI slider. For floats, this 35 | is the number of decimal places to display. For ints this is the step size. 36 | fixed: [optional] [train only]. Training configurations are fixed when the model is 37 | created, and then reloaded from the state file. Marking an item as fixed=False 38 | indicates that this value can be changed for existing models, and will override 39 | the value saved in the state file with the updated value in config. If not 40 | provided this will default to True. 41 | """ 42 | 43 | 44 | _HELPTEXT = ( 45 | "MTCNN Detector options.\n" 46 | "Fast on GPU, slow on CPU. Uses fewer resources than other GPU detectors but can often return " 47 | "more false positives." 48 | ) 49 | 50 | 51 | _DEFAULTS = { 52 | "minsize": { 53 | "default": 20, 54 | "info": "The minimum size of a face (in pixels) to be accepted as a positive match.\n" 55 | "Lower values use significantly more VRAM and will detect more false " 56 | "positives.", 57 | "datatype": int, 58 | "rounding": 10, 59 | "min_max": (20, 1000), 60 | "choices": [], 61 | "gui_radio": False, 62 | "fixed": True, 63 | }, 64 | "threshold_1": { 65 | "default": 0.6, 66 | "info": "First stage threshold for face detection. This stage obtains face " 67 | "candidates.", 68 | "datatype": float, 69 | "rounding": 2, 70 | "min_max": (0.1, 0.9), 71 | "choices": [], 72 | "gui_radio": False, 73 | "fixed": True, 74 | }, 75 | "threshold_2": { 76 | "default": 0.7, 77 | "info": "Second stage threshold for face detection. This stage refines face " 78 | "candidates.", 79 | "datatype": float, 80 | "rounding": 2, 81 | "min_max": (0.1, 0.9), 82 | "choices": [], 83 | "gui_radio": False, 84 | "fixed": True, 85 | }, 86 | "threshold_3": { 87 | "default": 0.7, 88 | "info": "Third stage threshold for face detection. This stage further refines face " 89 | "candidates.", 90 | "datatype": float, 91 | "rounding": 2, 92 | "min_max": (0.1, 0.9), 93 | "choices": [], 94 | "gui_radio": False, 95 | "fixed": True, 96 | }, 97 | "scalefactor": { 98 | "default": 0.709, 99 | "info": "The scale factor for the image pyramid.", 100 | "datatype": float, 101 | "rounding": 3, 102 | "min_max": (0.1, 0.9), 103 | "choices": [], 104 | "gui_radio": False, 105 | "fixed": True, 106 | }, 107 | } 108 | -------------------------------------------------------------------------------- /lib/queue_manager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Queue Manager for faceswap 3 | 4 | NB: Keep this in it's own module! If it gets loaded from 5 | a multiprocess on a Windows System it will break Faceswap""" 6 | 7 | import logging 8 | import multiprocessing as mp 9 | import sys 10 | import threading 11 | 12 | from queue import Queue, Empty as QueueEmpty # pylint: disable=unused-import; # noqa 13 | from time import sleep 14 | 15 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 16 | 17 | 18 | class QueueManager(): 19 | """ Manage queues for availabilty across processes 20 | Don't import this class directly, instead 21 | import the variable: queue_manager """ 22 | def __init__(self): 23 | logger.debug("Initializing %s", self.__class__.__name__) 24 | 25 | # Hacky fix to stop multiprocessing spawning managers in child processes 26 | if mp.current_process().name == "MainProcess": 27 | # Use a Multiprocessing manager in main process 28 | self.manager = mp.Manager() 29 | else: 30 | # Use a standard mp.queue in child process. NB: This will never be used 31 | # but spawned processes will load this module, so we need to dummy in a queue 32 | self.manager = mp 33 | self.shutdown = self.manager.Event() 34 | self.queues = dict() 35 | # Despite launching a subprocess, the scripts still want to access the same logging 36 | # queue as the GUI, so make sure the GUI gets it's own queue 37 | self._log_queue = self.manager.Queue() if "gui" not in sys.argv else mp.Queue() 38 | logger.debug("Initialized %s", self.__class__.__name__) 39 | 40 | def add_queue(self, name, maxsize=0, multiprocessing_queue=True): 41 | """ Add a queue to the manager 42 | 43 | Adds an event "shutdown" to the queue that can be used to indicate 44 | to a process that any activity on the queue should cease """ 45 | 46 | logger.debug("QueueManager adding: (name: '%s', maxsize: %s)", name, maxsize) 47 | if name in self.queues.keys(): 48 | raise ValueError("Queue '{}' already exists.".format(name)) 49 | 50 | if multiprocessing_queue: 51 | queue = self.manager.Queue(maxsize=maxsize) 52 | else: 53 | queue = Queue(maxsize=maxsize) 54 | 55 | setattr(queue, "shutdown", self.shutdown) 56 | self.queues[name] = queue 57 | logger.debug("QueueManager added: (name: '%s')", name) 58 | 59 | def del_queue(self, name): 60 | """ remove a queue from the manager """ 61 | logger.debug("QueueManager deleting: '%s'", name) 62 | del self.queues[name] 63 | logger.debug("QueueManager deleted: '%s'", name) 64 | 65 | def get_queue(self, name, maxsize=0, multiprocessing_queue=True): 66 | """ Return a queue from the manager 67 | If it doesn't exist, create it """ 68 | logger.debug("QueueManager getting: '%s'", name) 69 | queue = self.queues.get(name, None) 70 | if not queue: 71 | self.add_queue(name, maxsize, multiprocessing_queue) 72 | queue = self.queues[name] 73 | logger.debug("QueueManager got: '%s'", name) 74 | return queue 75 | 76 | def terminate_queues(self): 77 | """ Set shutdown event, clear and send EOF to all queues 78 | To be called if there is an error """ 79 | logger.debug("QueueManager terminating all queues") 80 | self.shutdown.set() 81 | self.flush_queues() 82 | for q_name, queue in self.queues.items(): 83 | logger.debug("QueueManager terminating: '%s'", q_name) 84 | queue.put("EOF") 85 | logger.debug("QueueManager terminated all queues") 86 | 87 | def flush_queues(self): 88 | """ Empty out all queues """ 89 | for q_name in self.queues.keys(): 90 | self.flush_queue(q_name) 91 | logger.debug("QueueManager flushed all queues") 92 | 93 | def flush_queue(self, q_name): 94 | """ Empty out a specific queue """ 95 | logger.debug("QueueManager flushing: '%s'", q_name) 96 | queue = self.queues[q_name] 97 | while not queue.empty(): 98 | queue.get(True, 1) 99 | 100 | def debug_monitor(self, update_secs=2): 101 | """ Debug tool for monitoring queues """ 102 | thread = threading.Thread(target=self.debug_queue_sizes, 103 | args=(update_secs, )) 104 | thread.daemon = True 105 | thread.start() 106 | 107 | def debug_queue_sizes(self, update_secs): 108 | """ Output the queue sizes 109 | logged to INFO so it also displays in console 110 | """ 111 | while True: 112 | for name in sorted(self.queues.keys()): 113 | logger.info("%s: %s", name, self.queues[name].qsize()) 114 | sleep(update_secs) 115 | 116 | 117 | queue_manager = QueueManager() # pylint: disable=invalid-name 118 | -------------------------------------------------------------------------------- /plugins/convert/writer/opencv_defaults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | The default options for the faceswap Opencv Writer plugin. 4 | 5 | Defaults files should be named _defaults.py 6 | Any items placed into this file will automatically get added to the relevant config .ini files 7 | within the faceswap/config folder. 8 | 9 | The following variables should be defined: 10 | _HELPTEXT: A string describing what this plugin does 11 | _DEFAULTS: A dictionary containing the options, defaults and meta information. The 12 | dictionary should be defined as: 13 | {: {}} 14 | 15 | should always be lower text. 16 | dictionary requirements are listed below. 17 | 18 | The following keys are expected for the _DEFAULTS dict: 19 | datatype: [required] A python type class. This limits the type of data that can be 20 | provided in the .ini file and ensures that the value is returned in the 21 | correct type to faceswap. Valid datatypes are: , , 22 | , . 23 | default: [required] The default value for this option. 24 | info: [required] A string describing what this option does. 25 | choices: [optional] If this option's datatype is of then valid 26 | selections can be defined here. This validates the option and also enables 27 | a combobox / radio option in the GUI. 28 | gui_radio: [optional] If are defined, this indicates that the GUI should use 29 | radio buttons rather than a combobox to display this option. 30 | min_max: [partial] For and datatypes this is required 31 | otherwise it is ignored. Should be a tuple of min and max accepted values. 32 | This is used for controlling the GUI slider range. Values are not enforced. 33 | rounding: [partial] For and datatypes this is 34 | required otherwise it is ignored. Used for the GUI slider. For floats, this 35 | is the number of decimal places to display. For ints this is the step size. 36 | fixed: [optional] [train only]. Training configurations are fixed when the model is 37 | created, and then reloaded from the state file. Marking an item as fixed=False 38 | indicates that this value can be changed for existing models, and will override 39 | the value saved in the state file with the updated value in config. If not 40 | provided this will default to True. 41 | """ 42 | 43 | 44 | _HELPTEXT = ( 45 | "Options for outputting converted frames to a series of images using OpenCV\n" 46 | "OpenCV can be faster than other image writers, but lacks some configuration " 47 | "options and formats." 48 | ) 49 | 50 | 51 | _DEFAULTS = { 52 | "format": { 53 | "default": "png", 54 | "info": "Image format to use:" 55 | "\n\t bmp: Windows bitmap" 56 | "\n\t jpg: JPEG format" 57 | "\n\t jp2: JPEG 2000 format" 58 | "\n\t png: Portable Network Graphics" 59 | "\n\t ppm: Portable Pixmap Format", 60 | "datatype": str, 61 | "rounding": None, 62 | "min_max": None, 63 | "choices": ["bmp", "jpg", "jp2", "png", "ppm"], 64 | "gui_radio": True, 65 | "fixed": True, 66 | }, 67 | "draw_transparent": { 68 | "default": False, 69 | "info": "Place the swapped face on a transparent layer rather than the original frame." 70 | "\nNB: This is only compatible with images saved in png format. If an " 71 | "incompatible format is selected then the image will be saved as a png.", 72 | "datatype": bool, 73 | "rounding": None, 74 | "min_max": None, 75 | "choices": [], 76 | "group": "settings", 77 | "gui_radio": False, 78 | "fixed": True, 79 | }, 80 | "jpg_quality": { 81 | "default": 75, 82 | "info": "[jpg only] Set the jpg quality. 1 is worst 95 is best. Higher quality leads " 83 | "to larger file sizes.", 84 | "datatype": int, 85 | "rounding": 1, 86 | "min_max": (1, 95), 87 | "choices": [], 88 | "group": "compression", 89 | "gui_radio": False, 90 | "fixed": True, 91 | }, 92 | "png_compress_level": { 93 | "default": 3, 94 | "info": "[png only] ZLIB compression level, 1 gives best speed, 9 gives best " 95 | "compression, 0 gives no compression at all.", 96 | "datatype": int, 97 | "rounding": 1, 98 | "min_max": (0, 9), 99 | "choices": [], 100 | "group": "compression", 101 | "gui_radio": False, 102 | "fixed": True, 103 | }, 104 | } 105 | -------------------------------------------------------------------------------- /tools/lib_alignments/annotate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Tools for annotating an input image """ 3 | 4 | import logging 5 | 6 | import cv2 7 | import numpy as np 8 | 9 | from lib.align_eyes import FACIAL_LANDMARKS_IDXS 10 | 11 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 12 | 13 | 14 | class Annotate(): 15 | """ Annotate an input image """ 16 | 17 | def __init__(self, image, alignments, original_roi=None): 18 | logger.debug("Initializing %s: (alignments: %s, original_roi: %s)", 19 | self.__class__.__name__, alignments, original_roi) 20 | self.image = image 21 | self.alignments = alignments 22 | self.roi = original_roi 23 | self.colors = {1: (255, 0, 0), 24 | 2: (0, 255, 0), 25 | 3: (0, 0, 255), 26 | 4: (255, 255, 0), 27 | 5: (255, 0, 255), 28 | 6: (0, 255, 255)} 29 | logger.debug("Initialized %s", self.__class__.__name__) 30 | 31 | def draw_black_image(self): 32 | """ Change image to black at correct dimensions """ 33 | logger.trace("Drawing black image") 34 | height, width = self.image.shape[:2] 35 | self.image = np.zeros((height, width, 3), np.uint8) 36 | 37 | def draw_bounding_box(self, color_id=1, thickness=1): 38 | """ Draw the bounding box around faces """ 39 | color = self.colors[color_id] 40 | for alignment in self.alignments: 41 | top_left = (alignment["x"], alignment["y"]) 42 | bottom_right = (alignment["x"] + alignment["w"], alignment["y"] + alignment["h"]) 43 | logger.trace("Drawing bounding box: (top_left: %s, bottom_right: %s, color: %s, " 44 | "thickness: %s)", top_left, bottom_right, color, thickness) 45 | cv2.rectangle(self.image, # pylint: disable=no-member 46 | top_left, 47 | bottom_right, 48 | color, 49 | thickness) 50 | 51 | def draw_extract_box(self, color_id=2, thickness=1): 52 | """ Draw the extracted face box """ 53 | if not self.roi: 54 | return 55 | color = self.colors[color_id] 56 | for idx, roi in enumerate(self.roi): 57 | logger.trace("Drawing Extract Box: (idx: %s, roi: %s)", idx, roi) 58 | top_left = [point for point in roi.squeeze()[0]] 59 | top_left = (top_left[0], top_left[1] - 10) 60 | cv2.putText(self.image, # pylint: disable=no-member 61 | str(idx), 62 | top_left, 63 | cv2.FONT_HERSHEY_DUPLEX, # pylint: disable=no-member 64 | 1.0, 65 | color, 66 | thickness) 67 | cv2.polylines(self.image, [roi], True, color, thickness) # pylint: disable=no-member 68 | 69 | def draw_landmarks(self, color_id=3, radius=1): 70 | """ Draw the facial landmarks """ 71 | color = self.colors[color_id] 72 | for alignment in self.alignments: 73 | landmarks = alignment["landmarksXY"] 74 | logger.trace("Drawing Landmarks: (landmarks: %s, color: %s, radius: %s)", 75 | landmarks, color, radius) 76 | for (pos_x, pos_y) in landmarks: 77 | cv2.circle(self.image, # pylint: disable=no-member 78 | (pos_x, pos_y), 79 | radius, 80 | color, 81 | -1) 82 | 83 | def draw_landmarks_mesh(self, color_id=4, thickness=1): 84 | """ Draw the facial landmarks """ 85 | color = self.colors[color_id] 86 | for alignment in self.alignments: 87 | landmarks = alignment["landmarksXY"] 88 | logger.trace("Drawing Landmarks Mesh: (landmarks: %s, color: %s, thickness: %s)", 89 | landmarks, color, thickness) 90 | for key, val in FACIAL_LANDMARKS_IDXS.items(): 91 | points = np.array([landmarks[val[0]:val[1]]], np.int32) 92 | fill_poly = bool(key in ("right_eye", "left_eye", "mouth")) 93 | cv2.polylines(self.image, # pylint: disable=no-member 94 | points, 95 | fill_poly, 96 | color, 97 | thickness) 98 | 99 | def draw_grey_out_faces(self, live_face): 100 | """ Grey out all faces except target """ 101 | if not self.roi: 102 | return 103 | alpha = 0.6 104 | overlay = self.image.copy() 105 | for idx, roi in enumerate(self.roi): 106 | if idx != int(live_face): 107 | logger.trace("Greying out face: (idx: %s, roi: %s)", idx, roi) 108 | cv2.fillPoly(overlay, roi, (0, 0, 0)) # pylint: disable=no-member 109 | cv2.addWeighted(overlay, # pylint: disable=no-member 110 | alpha, 111 | self.image, 112 | 1 - alpha, 113 | 0, 114 | self.image) 115 | -------------------------------------------------------------------------------- /plugins/convert/mask/mask_blend_defaults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | The default options for the faceswap Mask_Blend Mask plugin. 4 | 5 | Defaults files should be named _defaults.py 6 | Any items placed into this file will automatically get added to the relevant config .ini files 7 | within the faceswap/config folder. 8 | 9 | The following variables should be defined: 10 | _HELPTEXT: A string describing what this plugin does 11 | _DEFAULTS: A dictionary containing the options, defaults and meta information. The 12 | dictionary should be defined as: 13 | {: {}} 14 | 15 | should always be lower text. 16 | dictionary requirements are listed below. 17 | 18 | The following keys are expected for the _DEFAULTS dict: 19 | datatype: [required] A python type class. This limits the type of data that can be 20 | provided in the .ini file and ensures that the value is returned in the 21 | correct type to faceswap. Valid datatypes are: , , 22 | , . 23 | default: [required] The default value for this option. 24 | info: [required] A string describing what this option does. 25 | choices: [optional] If this option's datatype is of then valid 26 | selections can be defined here. This validates the option and also enables 27 | a combobox / radio option in the GUI. 28 | gui_radio: [optional] If are defined, this indicates that the GUI should use 29 | radio buttons rather than a combobox to display this option. 30 | min_max: [partial] For and datatypes this is required 31 | otherwise it is ignored. Should be a tuple of min and max accepted values. 32 | This is used for controlling the GUI slider range. Values are not enforced. 33 | rounding: [partial] For and datatypes this is 34 | required otherwise it is ignored. Used for the GUI slider. For floats, this 35 | is the number of decimal places to display. For ints this is the step size. 36 | fixed: [optional] [train only]. Training configurations are fixed when the model is 37 | created, and then reloaded from the state file. Marking an item as fixed=False 38 | indicates that this value can be changed for existing models, and will override 39 | the value saved in the state file with the updated value in config. If not 40 | provided this will default to True. 41 | """ 42 | 43 | 44 | _HELPTEXT = "Options for blending the edges between the mask and the background image" 45 | 46 | 47 | _DEFAULTS = { 48 | "type": { 49 | "default": "normalized", 50 | "info": "The type of blending to use:" 51 | "\n\t gaussian: Blend with Gaussian filter. Slower, but often better than " 52 | "Normalized" 53 | "\n\t normalized: Blend with Normalized box filter. Faster than Gaussian" 54 | "\n\t none: Don't perform blending", 55 | "datatype": str, 56 | "rounding": None, 57 | "min_max": None, 58 | "choices": ["gaussian", "normalized", "none"], 59 | "gui_radio": True, 60 | "fixed": True, 61 | }, 62 | "radius": { 63 | "default": 3.0, 64 | "info": "Radius dictates how much blending should occur.\nThis figure is set as a " 65 | "percentage of the mask diameter to give the radius in pixels. Eg: for a mask " 66 | "with diameter 200px, a percentage of 6% would give a final radius of 3px.\n" 67 | "Higher percentage means more blending.", 68 | "datatype": float, 69 | "rounding": 1, 70 | "min_max": (0.1, 25.0), 71 | "choices": [], 72 | "gui_radio": False, 73 | "group": "settings", 74 | "fixed": True, 75 | }, 76 | "passes": { 77 | "default": 4, 78 | "info": "The number of passes to perform. Additional passes of the blending algorithm " 79 | "can improve smoothing at a time cost. This is more useful for 'box' type " 80 | "blending.\nAdditional passes have exponentially less effect so it's not " 81 | "worth setting this too high.", 82 | "datatype": int, 83 | "rounding": 1, 84 | "min_max": (1, 8), 85 | "choices": [], 86 | "gui_radio": False, 87 | "group": "settings", 88 | "fixed": True, 89 | }, 90 | "erosion": { 91 | "default": 0.0, 92 | "info": "Erosion kernel size as a percentage of the mask radius area.\nPositive " 93 | "values apply erosion which reduces the size of the swapped area.\nNegative " 94 | "values apply dilation which increases the swapped area.", 95 | "datatype": float, 96 | "rounding": 1, 97 | "min_max": (-100.0, 100.0), 98 | "choices": [], 99 | "gui_radio": False, 100 | "group": "settings", 101 | "fixed": True, 102 | }, 103 | } 104 | -------------------------------------------------------------------------------- /lib/umeyama.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ Umeyama for Faceswap 3 | 4 | License (Modified BSD) 5 | Copyright (C) 2011, the scikit-image team All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted 8 | provided that the following conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this list of conditions 11 | and the following disclaimer. 12 | 13 | Redistributions in binary form must reproduce the above copyright notice, this list of 14 | conditions and the following disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | Neither the name of skimage nor the names of its contributors may be used to endorse or promote 18 | products derived from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 21 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 22 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 23 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 24 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | umeyama function from scikit-image/skimage/transform/_geometric.py 30 | """ 31 | import numpy as np 32 | 33 | MEAN_FACE_X = np.array([ 34 | 0.000213256, 0.0752622, 0.18113, 0.29077, 0.393397, 0.586856, 0.689483, 35 | 0.799124, 0.904991, 0.98004, 0.490127, 0.490127, 0.490127, 0.490127, 36 | 0.36688, 0.426036, 0.490127, 0.554217, 0.613373, 0.121737, 0.187122, 37 | 0.265825, 0.334606, 0.260918, 0.182743, 0.645647, 0.714428, 0.793132, 38 | 0.858516, 0.79751, 0.719335, 0.254149, 0.340985, 0.428858, 0.490127, 39 | .551395, 0.639268, 0.726104, 0.642159, 0.556721, 0.490127, 0.423532, 40 | 0.338094, 0.290379, 0.428096, 0.490127, 0.552157, 0.689874, 0.553364, 41 | 0.490127, 0.42689]) 42 | 43 | MEAN_FACE_Y = np.array([ 44 | 0.106454, 0.038915, 0.0187482, 0.0344891, 0.0773906, 0.0773906, 0.0344891, 45 | 0.0187482, 0.038915, 0.106454, 0.203352, 0.307009, 0.409805, 0.515625, 46 | 0.587326, 0.609345, 0.628106, 0.609345, 0.587326, 0.216423, 0.178758, 47 | 0.179852, 0.231733, 0.245099, 0.244077, 0.231733, 0.179852, 0.178758, 48 | 0.216423, 0.244077, 0.245099, 0.780233, 0.745405, 0.727388, 0.742578, 49 | 0.727388, 0.745405, 0.780233, 0.864805, 0.902192, 0.909281, 0.902192, 50 | 0.864805, 0.784792, 0.778746, 0.785343, 0.778746, 0.784792, 0.824182, 51 | 0.831803, 0.824182]) 52 | 53 | 54 | def umeyama(src, estimate_scale, dst=None): 55 | """Estimate N-D similarity transformation with or without scaling. 56 | Parameters 57 | ---------- 58 | src : (M, N) array 59 | Source coordinates. 60 | dst : (M, N) array 61 | Destination coordinates. 62 | estimate_scale : bool 63 | Whether to estimate scaling factor. 64 | Returns 65 | ------- 66 | T : (N + 1, N + 1) 67 | The homogeneous similarity transformation matrix. The matrix contains 68 | NaN values only if the problem is not well-conditioned. 69 | References 70 | ---------- 71 | .. [1] "Least-squares estimation of transformation parameters between two 72 | point patterns", Shinji Umeyama, PAMI 1991, DOI: 10.1109/34.88573 73 | """ 74 | if dst is None: 75 | dst = np.stack([MEAN_FACE_X, MEAN_FACE_Y], axis=1) 76 | 77 | num = src.shape[0] 78 | dim = src.shape[1] 79 | 80 | # Compute mean of src and dst. 81 | src_mean = src.mean(axis=0) 82 | dst_mean = dst.mean(axis=0) 83 | 84 | # Subtract mean from src and dst. 85 | src_demean = src - src_mean 86 | dst_demean = dst - dst_mean 87 | 88 | # Eq. (38). 89 | A = np.dot(dst_demean.T, src_demean) / num 90 | 91 | # Eq. (39). 92 | d = np.ones((dim,), dtype=np.double) 93 | if np.linalg.det(A) < 0: 94 | d[dim - 1] = -1 95 | 96 | T = np.eye(dim + 1, dtype=np.double) 97 | 98 | U, S, V = np.linalg.svd(A) 99 | 100 | # Eq. (40) and (43). 101 | rank = np.linalg.matrix_rank(A) 102 | if rank == 0: 103 | return np.nan * T 104 | elif rank == dim - 1: 105 | if np.linalg.det(U) * np.linalg.det(V) > 0: 106 | T[:dim, :dim] = np.dot(U, V) 107 | else: 108 | s = d[dim - 1] 109 | d[dim - 1] = -1 110 | T[:dim, :dim] = np.dot(U, np.dot(np.diag(d), V)) 111 | d[dim - 1] = s 112 | else: 113 | T[:dim, :dim] = np.dot(U, np.dot(np.diag(d), V.T)) 114 | 115 | if estimate_scale: 116 | # Eq. (41) and (42). 117 | scale = 1.0 / src_demean.var(axis=0).sum() * np.dot(S, d) 118 | else: 119 | scale = 1.0 120 | 121 | T[:dim, dim] = dst_mean - scale * np.dot(T[:dim, :dim], src_mean.T) 122 | T[:dim, :dim] *= scale 123 | 124 | return T 125 | -------------------------------------------------------------------------------- /plugins/train/model/realface_defaults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | The default options for the faceswap Realface Model plugin. 4 | 5 | Defaults files should be named _defaults.py 6 | Any items placed into this file will automatically get added to the relevant config .ini files 7 | within the faceswap/config folder. 8 | 9 | The following variables should be defined: 10 | _HELPTEXT: A string describing what this plugin does 11 | _DEFAULTS: A dictionary containing the options, defaults and meta information. The 12 | dictionary should be defined as: 13 | {: {}} 14 | 15 | should always be lower text. 16 | dictionary requirements are listed below. 17 | 18 | The following keys are expected for the _DEFAULTS dict: 19 | datatype: [required] A python type class. This limits the type of data that can be 20 | provided in the .ini file and ensures that the value is returned in the 21 | correct type to faceswap. Valid datatypes are: , , 22 | , . 23 | default: [required] The default value for this option. 24 | info: [required] A string describing what this option does. 25 | choices: [optional] If this option's datatype is of then valid 26 | selections can be defined here. This validates the option and also enables 27 | a combobox / radio option in the GUI. 28 | gui_radio: [optional] If are defined, this indicates that the GUI should use 29 | radio buttons rather than a combobox to display this option. 30 | min_max: [partial] For and datatypes this is required 31 | otherwise it is ignored. Should be a tuple of min and max accepted values. 32 | This is used for controlling the GUI slider range. Values are not enforced. 33 | rounding: [partial] For and datatypes this is 34 | required otherwise it is ignored. Used for the GUI slider. For floats, this 35 | is the number of decimal places to display. For ints this is the step size. 36 | fixed: [optional] [train only]. Training configurations are fixed when the model is 37 | created, and then reloaded from the state file. Marking an item as fixed=False 38 | indicates that this value can be changed for existing models, and will override 39 | the value saved in the state file with the updated value in config. If not 40 | provided this will default to True. 41 | """ 42 | 43 | 44 | _HELPTEXT = ( 45 | "An extra detailed variant of Original model.\n" 46 | "Incorporates ideas from Bryanlyon and inspiration from the Villain model.\n" 47 | "Requires about 6GB-8GB of VRAM (batchsize 8-16).\n" 48 | ) 49 | 50 | 51 | _DEFAULTS = { 52 | "input_size": { 53 | "default": 64, 54 | "info": "Resolution (in pixels) of the input image to train on.\n" 55 | "BE AWARE Larger resolution will dramatically increase VRAM requirements.\n" 56 | "Higher resolutions may increase prediction accuracy, but does not effect the " 57 | "resulting output size.\nMust be between 64 and 128 and be divisible by 16.", 58 | "datatype": int, 59 | "rounding": 16, 60 | "min_max": (64, 128), 61 | "choices": [], 62 | "gui_radio": False, 63 | "fixed": True, 64 | "group": "size" 65 | }, 66 | "output_size": { 67 | "default": 128, 68 | "info": "Output image resolution (in pixels).\nBe aware that larger resolution will " 69 | "increase VRAM requirements.\nNB: Must be between 64 and 256 and be divisible " 70 | "by 16.", 71 | "datatype": int, 72 | "rounding": 16, 73 | "min_max": (64, 256), 74 | "choices": [], 75 | "gui_radio": False, 76 | "fixed": True, 77 | "group": "size" 78 | }, 79 | "dense_nodes": { 80 | "default": 1536, 81 | "info": "Number of nodes for decoder. Might affect your model's ability to learn in " 82 | "general.\nNote that: Lower values will affect the ability to predict " 83 | "details.", 84 | "datatype": int, 85 | "rounding": 64, 86 | "min_max": (768, 2048), 87 | "choices": [], 88 | "gui_radio": False, 89 | "fixed": True, 90 | "group": "network" 91 | }, 92 | "complexity_encoder": { 93 | "default": 128, 94 | "info": "Encoder Convolution Layer Complexity. sensible ranges: 128 to 150.", 95 | "datatype": int, 96 | "rounding": 4, 97 | "min_max": (96, 160), 98 | "choices": [], 99 | "gui_radio": False, 100 | "fixed": True, 101 | "group": "network" 102 | }, 103 | "complexity_decoder": { 104 | "default": 512, 105 | "info": "Decoder Complexity.", 106 | "datatype": int, 107 | "rounding": 4, 108 | "min_max": (512, 544), 109 | "choices": [], 110 | "gui_radio": False, 111 | "fixed": True, 112 | "group": "network" 113 | }, 114 | } 115 | -------------------------------------------------------------------------------- /plugins/train/model/dfl_sae_defaults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | The default options for the faceswap Dfl_SAE Model plugin. 4 | 5 | Defaults files should be named _defaults.py 6 | Any items placed into this file will automatically get added to the relevant config .ini files 7 | within the faceswap/config folder. 8 | 9 | The following variables should be defined: 10 | _HELPTEXT: A string describing what this plugin does 11 | _DEFAULTS: A dictionary containing the options, defaults and meta information. The 12 | dictionary should be defined as: 13 | {: {}} 14 | 15 | should always be lower text. 16 | dictionary requirements are listed below. 17 | 18 | The following keys are expected for the _DEFAULTS dict: 19 | datatype: [required] A python type class. This limits the type of data that can be 20 | provided in the .ini file and ensures that the value is returned in the 21 | correct type to faceswap. Valid datatypes are: , , 22 | , . 23 | default: [required] The default value for this option. 24 | info: [required] A string describing what this option does. 25 | choices: [optional] If this option's datatype is of then valid 26 | selections can be defined here. This validates the option and also enables 27 | a combobox / radio option in the GUI. 28 | gui_radio: [optional] If are defined, this indicates that the GUI should use 29 | radio buttons rather than a combobox to display this option. 30 | min_max: [partial] For and datatypes this is required 31 | otherwise it is ignored. Should be a tuple of min and max accepted values. 32 | This is used for controlling the GUI slider range. Values are not enforced. 33 | rounding: [partial] For and datatypes this is 34 | required otherwise it is ignored. Used for the GUI slider. For floats, this 35 | is the number of decimal places to display. For ints this is the step size. 36 | fixed: [optional] [train only]. Training configurations are fixed when the model is 37 | created, and then reloaded from the state file. Marking an item as fixed=False 38 | indicates that this value can be changed for existing models, and will override 39 | the value saved in the state file with the updated value in config. If not 40 | provided this will default to True. 41 | """ 42 | 43 | 44 | _HELPTEXT = "DFL SAE Model (Adapted from https://github.com/iperov/DeepFaceLab)" 45 | 46 | 47 | _DEFAULTS = { 48 | "input_size": { 49 | "default": 128, 50 | "info": "Resolution (in pixels) of the input image to train on.\n" 51 | "BE AWARE Larger resolution will dramatically increase VRAM requirements.\n" 52 | "\nMust be divisible by 16.", 53 | "datatype": int, 54 | "rounding": 16, 55 | "min_max": (64, 256), 56 | "group": "size", 57 | "fixed": True, 58 | }, 59 | "clipnorm": { 60 | "default": True, 61 | "info": "Controls gradient clipping of the optimizer. Can prevent model corruption at " 62 | "the expense of VRAM.", 63 | "datatype": bool, 64 | "fixed": False, 65 | "group": "settings", 66 | }, 67 | "architecture": { 68 | "default": "df", 69 | "info": "Model architecture:" 70 | "\n\t'df': Keeps the faces more natural." 71 | "\n\t'liae': Can help fix overly different face shapes.", 72 | "datatype": str, 73 | "choices": ["df", "liae"], 74 | "gui_radio": True, 75 | "fixed": True, 76 | "group": "network", 77 | }, 78 | "autoencoder_dims": { 79 | "default": 0, 80 | "info": "Face information is stored in AutoEncoder dimensions. If there are not enough " 81 | "dimensions then certain facial features may not be recognized." 82 | "\nHigher number of dimensions are better, but require more VRAM." 83 | "\nSet to 0 to use the architecture defaults (256 for liae, 512 for df).", 84 | "datatype": int, 85 | "rounding": 32, 86 | "min_max": (0, 1024), 87 | "fixed": True, 88 | "group": "network", 89 | }, 90 | "encoder_dims": { 91 | "default": 42, 92 | "info": "Encoder dimensions per channel. Higher number of encoder dimensions will help " 93 | "the model to recognize more facial features, but will require more VRAM.", 94 | "datatype": int, 95 | "rounding": 1, 96 | "min_max": (21, 85), 97 | "fixed": True, 98 | "group": "network", 99 | }, 100 | "decoder_dims": { 101 | "default": 21, 102 | "info": "Decoder dimensions per channel. Higher number of decoder dimensions will help " 103 | "the model to improve details, but will require more VRAM.", 104 | "datatype": int, 105 | "rounding": 1, 106 | "min_max": (10, 85), 107 | "fixed": True, 108 | "group": "network", 109 | }, 110 | "multiscale_decoder": { 111 | "default": False, 112 | "info": "Multiscale decoder can help to obtain better details.", 113 | "datatype": bool, 114 | "fixed": True, 115 | "group": "network", 116 | }, 117 | } 118 | -------------------------------------------------------------------------------- /plugins/convert/mask/box_blend_defaults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | The default options for the faceswap Box_Blend Mask plugin. 4 | 5 | Defaults files should be named _defaults.py 6 | Any items placed into this file will automatically get added to the relevant config .ini files 7 | within the faceswap/config folder. 8 | 9 | The following variables should be defined: 10 | _HELPTEXT: A string describing what this plugin does 11 | _DEFAULTS: A dictionary containing the options, defaults and meta information. The 12 | dictionary should be defined as: 13 | {: {}} 14 | 15 | should always be lower text. 16 | dictionary requirements are listed below. 17 | 18 | The following keys are expected for the _DEFAULTS dict: 19 | datatype: [required] A python type class. This limits the type of data that can be 20 | provided in the .ini file and ensures that the value is returned in the 21 | correct type to faceswap. Valid datatypes are: , , 22 | , . 23 | default: [required] The default value for this option. 24 | info: [required] A string describing what this option does. 25 | choices: [optional] If this option's datatype is of then valid 26 | selections can be defined here. This validates the option and also enables 27 | a combobox / radio option in the GUI. 28 | gui_radio: [optional] If are defined, this indicates that the GUI should use 29 | radio buttons rather than a combobox to display this option. 30 | min_max: [partial] For and datatypes this is required 31 | otherwise it is ignored. Should be a tuple of min and max accepted values. 32 | This is used for controlling the GUI slider range. Values are not enforced. 33 | rounding: [partial] For and datatypes this is 34 | required otherwise it is ignored. Used for the GUI slider. For floats, this 35 | is the number of decimal places to display. For ints this is the step size. 36 | fixed: [optional] [train only]. Training configurations are fixed when the model is 37 | created, and then reloaded from the state file. Marking an item as fixed=False 38 | indicates that this value can be changed for existing models, and will override 39 | the value saved in the state file with the updated value in config. If not 40 | provided this will default to True. 41 | """ 42 | 43 | 44 | _HELPTEXT = "Options for blending the edges of the swapped box with the background image" 45 | 46 | 47 | _DEFAULTS = { 48 | "type": { 49 | "default": "gaussian", 50 | "info": "The type of blending to use:" 51 | "\n\t gaussian: Blend with Gaussian filter. Slower, but often better than " 52 | "Normalized" 53 | "\n\t normalized: Blend with Normalized box filter. Faster than Gaussian" 54 | "\n\t none: Don't perform blending", 55 | "datatype": str, 56 | "rounding": None, 57 | "min_max": None, 58 | "choices": ["gaussian", "normalized", "none"], 59 | "gui_radio": True, 60 | "fixed": True, 61 | }, 62 | "distance": { 63 | "default": 11.0, 64 | "info": "The distance from the edges of the swap box to start blending.\nThe distance " 65 | "is set as percentage of the swap box size to give the number of pixels from " 66 | "the edge of the box. Eg: For a swap area of 256px and a percentage of 4%, " 67 | "blending would commence 10 pixels from the edge.\nHigher percentages start " 68 | "the blending from closer to the center of the face, so will reveal more of " 69 | "the source face.", 70 | "datatype": float, 71 | "rounding": 1, 72 | "group": "settings", 73 | "min_max": (0.1, 25.0), 74 | "choices": [], 75 | "gui_radio": False, 76 | "fixed": True, 77 | }, 78 | "radius": { 79 | "default": 5.0, 80 | "info": "Radius dictates how much blending should occur, or more specifically, how " 81 | "far the blending will spread away from the 'distance' parameter.\nThis " 82 | "figure is set as a percentage of the swap box size to give the radius in " 83 | "pixels. Eg: For a swap area of 256px and a percentage of 5%, the radius " 84 | "would be 13 pixels\nNB: Higher percentage means more blending, but too high " 85 | "may reveal more of the source face, or lead to hard lines at the border.", 86 | "datatype": float, 87 | "rounding": 1, 88 | "min_max": (0.1, 25.0), 89 | "choices": [], 90 | "gui_radio": False, 91 | "group": "settings", 92 | "fixed": True, 93 | }, 94 | "passes": { 95 | "default": 1, 96 | "info": "The number of passes to perform. Additional passes of the blending algorithm " 97 | "can improve smoothing at a time cost. This is more useful for 'box' type " 98 | "blending.\nAdditional passes have exponentially less effect so it's not " 99 | "worth setting this too high.", 100 | "datatype": int, 101 | "rounding": 1, 102 | "min_max": (1, 8), 103 | "choices": [], 104 | "gui_radio": False, 105 | "group": "settings", 106 | "fixed": True, 107 | }, 108 | } 109 | -------------------------------------------------------------------------------- /lib/vgg_face2_keras.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin python3 2 | """ VGG_Face2 inference 3 | Model exported from: https://github.com/WeidiXie/Keras-VGGFace2-ResNet50 4 | which is based on: https://www.robots.ox.ac.uk/~vgg/software/vgg_face/ 5 | 6 | Licensed under Creative Commons Attribution License. 7 | https://creativecommons.org/licenses/by-nc/4.0/ 8 | """ 9 | 10 | import logging 11 | import sys 12 | import os 13 | 14 | import cv2 15 | import numpy as np 16 | from fastcluster import linkage 17 | from lib.utils import GetModel, set_system_verbosity 18 | 19 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 20 | 21 | 22 | class VGGFace2(): 23 | """ VGG Face feature extraction. 24 | Input images should be in BGR Order """ 25 | 26 | def __init__(self, backend="GPU", loglevel="INFO"): 27 | logger.debug("Initializing %s: (backend: %s, loglevel: %s)", 28 | self.__class__.__name__, backend, loglevel) 29 | set_system_verbosity(loglevel) 30 | backend = backend.upper() 31 | git_model_id = 10 32 | model_filename = ["vggface2_resnet50_v2.h5"] 33 | self.input_size = 224 34 | # Average image provided in https://github.com/ox-vgg/vgg_face2 35 | self.average_img = np.array([91.4953, 103.8827, 131.0912]) 36 | 37 | self.model = self.get_model(git_model_id, model_filename, backend) 38 | logger.debug("Initialized %s", self.__class__.__name__) 39 | 40 | # <<< GET MODEL >>> # 41 | def get_model(self, git_model_id, model_filename, backend): 42 | """ Check if model is available, if not, download and unzip it """ 43 | root_path = os.path.abspath(os.path.dirname(sys.argv[0])) 44 | cache_path = os.path.join(root_path, "plugins", "extract", ".cache") 45 | model = GetModel(model_filename, cache_path, git_model_id).model_path 46 | if backend == "CPU": 47 | if os.environ.get("KERAS_BACKEND", "") == "plaidml.keras.backend": 48 | logger.info("Switching to tensorflow backend.") 49 | os.environ["KERAS_BACKEND"] = "tensorflow" 50 | import keras 51 | from lib.model.layers import L2_normalize 52 | if backend == "CPU": 53 | with keras.backend.tf.device("/cpu:0"): 54 | return keras.models.load_model(model, { 55 | "L2_normalize": L2_normalize 56 | }) 57 | else: 58 | return keras.models.load_model(model, { 59 | "L2_normalize": L2_normalize 60 | }) 61 | 62 | def predict(self, face): 63 | """ Return encodings for given image from vgg_face """ 64 | if face.shape[0] != self.input_size: 65 | face = self.resize_face(face) 66 | face = np.expand_dims(face - self.average_img, axis=0) 67 | preds = self.model.predict(face) 68 | return preds[0, :] 69 | 70 | def resize_face(self, face): 71 | """ Resize incoming face to model_input_size """ 72 | if face.shape[0] < self.input_size: 73 | interpolation = cv2.INTER_CUBIC # pylint:disable=no-member 74 | else: 75 | interpolation = cv2.INTER_AREA # pylint:disable=no-member 76 | 77 | face = cv2.resize(face, # pylint:disable=no-member 78 | dsize=(self.input_size, self.input_size), 79 | interpolation=interpolation) 80 | return face 81 | 82 | @staticmethod 83 | def find_cosine_similiarity(source_face, test_face): 84 | """ Find the cosine similarity between a source face and a test face """ 85 | var_a = np.matmul(np.transpose(source_face), test_face) 86 | var_b = np.sum(np.multiply(source_face, source_face)) 87 | var_c = np.sum(np.multiply(test_face, test_face)) 88 | return 1 - (var_a / (np.sqrt(var_b) * np.sqrt(var_c))) 89 | 90 | def sorted_similarity(self, predictions, method="ward"): 91 | """ Sort a matrix of predictions by similarity Adapted from: 92 | https://gmarti.gitlab.io/ml/2017/09/07/how-to-sort-distance-matrix.html 93 | input: 94 | - predictions is a stacked matrix of vgg_face predictions shape: (x, 4096) 95 | - method = ["ward","single","average","complete"] 96 | output: 97 | - result_order is a list of indices with the order implied by the hierarhical tree 98 | 99 | sorted_similarity transforms a distance matrix into a sorted distance matrix according to 100 | the order implied by the hierarchical tree (dendrogram) 101 | """ 102 | logger.info("Sorting face distances. Depending on your dataset this may take some time...") 103 | num_predictions = predictions.shape[0] 104 | result_linkage = linkage(predictions, method=method, preserve_input=False) 105 | result_order = self.seriation(result_linkage, 106 | num_predictions, 107 | num_predictions + num_predictions - 2) 108 | 109 | return result_order 110 | 111 | def seriation(self, tree, points, current_index): 112 | """ Seriation method for sorted similarity 113 | input: 114 | - tree is a hierarchical tree (dendrogram) 115 | - points is the number of points given to the clustering process 116 | - current_index is the position in the tree for the recursive traversal 117 | output: 118 | - order implied by the hierarchical tree 119 | 120 | seriation computes the order implied by a hierarchical tree (dendrogram) 121 | """ 122 | if current_index < points: 123 | return [current_index] 124 | left = int(tree[current_index-points, 0]) 125 | right = int(tree[current_index-points, 1]) 126 | return self.seriation(tree, points, left) + self.seriation(tree, points, right) 127 | -------------------------------------------------------------------------------- /lib/vgg_face.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin python3 2 | """ VGG_Face inference using OpenCV-DNN 3 | Model from: https://www.robots.ox.ac.uk/~vgg/software/vgg_face/ 4 | 5 | Licensed under Creative Commons Attribution License. 6 | https://creativecommons.org/licenses/by-nc/4.0/ 7 | """ 8 | 9 | import logging 10 | import sys 11 | import os 12 | 13 | import cv2 14 | import numpy as np 15 | from fastcluster import linkage 16 | 17 | from lib.utils import GetModel 18 | 19 | logger = logging.getLogger(__name__) # pylint: disable=invalid-name 20 | 21 | 22 | class VGGFace(): 23 | """ VGG Face feature extraction. 24 | Input images should be in BGR Order """ 25 | 26 | def __init__(self, backend="CPU"): 27 | logger.debug("Initializing %s: (backend: %s)", self.__class__.__name__, backend) 28 | git_model_id = 7 29 | model_filename = ["vgg_face_v1.caffemodel", "vgg_face_v1.prototxt"] 30 | self.input_size = 224 31 | # Average image provided in http://www.robots.ox.ac.uk/~vgg/software/vgg_face/ 32 | self.average_img = [129.1863, 104.7624, 93.5940] 33 | 34 | self.model = self.get_model(git_model_id, model_filename, backend) 35 | logger.debug("Initialized %s", self.__class__.__name__) 36 | 37 | # <<< GET MODEL >>> # 38 | def get_model(self, git_model_id, model_filename, backend): 39 | """ Check if model is available, if not, download and unzip it """ 40 | root_path = os.path.abspath(os.path.dirname(sys.argv[0])) 41 | cache_path = os.path.join(root_path, "plugins", "extract", ".cache") 42 | model = GetModel(model_filename, cache_path, git_model_id).model_path 43 | model = cv2.dnn.readNetFromCaffe(model[1], model[0]) # pylint: disable=no-member 44 | model.setPreferableTarget(self.get_backend(backend)) 45 | return model 46 | 47 | @staticmethod 48 | def get_backend(backend): 49 | """ Return the cv2 DNN backend """ 50 | if backend == "OPENCL": 51 | logger.info("Using OpenCL backend. If the process runs, you can safely ignore any of " 52 | "the failure messages.") 53 | retval = getattr(cv2.dnn, "DNN_TARGET_{}".format(backend)) # pylint: disable=no-member 54 | return retval 55 | 56 | def predict(self, face): 57 | """ Return encodings for given image from vgg_face """ 58 | if face.shape[0] != self.input_size: 59 | face = self.resize_face(face) 60 | blob = cv2.dnn.blobFromImage(face, # pylint: disable=no-member 61 | 1.0, 62 | (self.input_size, self.input_size), 63 | self.average_img, 64 | False, 65 | False) 66 | self.model.setInput(blob) 67 | preds = self.model.forward("fc7")[0, :] 68 | return preds 69 | 70 | def resize_face(self, face): 71 | """ Resize incoming face to model_input_size """ 72 | if face.shape[0] < self.input_size: 73 | interpolation = cv2.INTER_CUBIC # pylint:disable=no-member 74 | else: 75 | interpolation = cv2.INTER_AREA # pylint:disable=no-member 76 | 77 | face = cv2.resize(face, # pylint:disable=no-member 78 | dsize=(self.input_size, self.input_size), 79 | interpolation=interpolation) 80 | return face 81 | 82 | @staticmethod 83 | def find_cosine_similiarity(source_face, test_face): 84 | """ Find the cosine similarity between a source face and a test face """ 85 | var_a = np.matmul(np.transpose(source_face), test_face) 86 | var_b = np.sum(np.multiply(source_face, source_face)) 87 | var_c = np.sum(np.multiply(test_face, test_face)) 88 | return 1 - (var_a / (np.sqrt(var_b) * np.sqrt(var_c))) 89 | 90 | def sorted_similarity(self, predictions, method="ward"): 91 | """ Sort a matrix of predictions by similarity Adapted from: 92 | https://gmarti.gitlab.io/ml/2017/09/07/how-to-sort-distance-matrix.html 93 | input: 94 | - predictions is a stacked matrix of vgg_face predictions shape: (x, 4096) 95 | - method = ["ward","single","average","complete"] 96 | output: 97 | - result_order is a list of indices with the order implied by the hierarhical tree 98 | 99 | sorted_similarity transforms a distance matrix into a sorted distance matrix according to 100 | the order implied by the hierarchical tree (dendrogram) 101 | """ 102 | logger.info("Sorting face distances. Depending on your dataset this may take some time...") 103 | num_predictions = predictions.shape[0] 104 | result_linkage = linkage(predictions, method=method, preserve_input=False) 105 | result_order = self.seriation(result_linkage, 106 | num_predictions, 107 | num_predictions + num_predictions - 2) 108 | 109 | return result_order 110 | 111 | def seriation(self, tree, points, current_index): 112 | """ Seriation method for sorted similarity 113 | input: 114 | - tree is a hierarchical tree (dendrogram) 115 | - points is the number of points given to the clustering process 116 | - current_index is the position in the tree for the recursive traversal 117 | output: 118 | - order implied by the hierarchical tree 119 | 120 | seriation computes the order implied by a hierarchical tree (dendrogram) 121 | """ 122 | if current_index < points: 123 | return [current_index] 124 | left = int(tree[current_index-points, 0]) 125 | right = int(tree[current_index-points, 1]) 126 | return self.seriation(tree, points, left) + self.seriation(tree, points, right) 127 | --------------------------------------------------------------------------------