Skip to content

Commit

Permalink
model_refactor (deepfakes#571) (deepfakes#572)
Browse files Browse the repository at this point in the history
* model_refactor (deepfakes#571)

* original model to new structure

* IAE model to new structure

* OriginalHiRes to new structure

* Fix trainer for different resolutions

* Initial config implementation

* Configparse library added

* improved training data loader

* dfaker model working

* Add logging to training functions

* Non blocking input for cli training

* Add error handling to threads. Add non-mp queues to queue_handler

* Improved Model Building and NNMeta

* refactor lib/models

* training refactor. DFL H128 model Implementation

* Dfaker - use hashes

* Move timelapse. Remove perceptual loss arg

* Update INSTALL.md. Add logger formatting. Update Dfaker training

* DFL h128 partially ported

* Add mask to dfaker (deepfakes#573)

* Remove old models. Add mask to dfaker

* dfl mask. Make masks selectable in config (deepfakes#575)

* DFL H128 Mask. Mask type selectable in config.

* remove gan_v2_2

* Creating Input Size config for models

Creating Input Size config for models

Will be used downstream in converters.

Also name change of image_shape to input_shape to clarify ( for future models with potentially different output_shapes)

* Add mask loss options to config

* MTCNN options to config.ini. Remove GAN config. Update USAGE.md

* Add sliders for numerical values in GUI

* Add config plugins menu to gui. Validate config

* Only backup model if loss has dropped. Get training working again

* bugfixes

* Standardise loss printing

* GUI idle cpu fixes. Graph loss fix.

* mutli-gpu logging bugfix

* Merge branch 'staging' into train_refactor

* backup state file

* Crash protection: Only backup if both total losses have dropped

* Port OriginalHiRes_RC4 to train_refactor (OriginalHiRes)

* Load and save model structure with weights

* Slight code update

* Improve config loader. Add subpixel opt to all models. Config to state

* Show samples... wrong input

* Remove AE topology. Add input/output shapes to State

* Port original_villain (birb/VillainGuy) model to faceswap

* Add plugin info to GUI config pages

* Load input shape from state. IAE Config options.

* Fix transform_kwargs.
Coverage to ratio.
Bugfix mask detection

* Suppress keras userwarnings.
Automate zoom.
Coverage_ratio to model def.

* Consolidation of converters & refactor (deepfakes#574)

* Consolidation of converters & refactor

Initial Upload of alpha

Items
- consolidate convert_mased & convert_adjust into one converter
-add average color adjust to convert_masked
-allow mask transition blur size to be a fixed integer of pixels and a fraction of the facial mask size
-allow erosion/dilation size to be a fixed integer of pixels and a fraction of the facial mask size
-eliminate redundant type conversions to avoid multiple round-off errors
-refactor loops for vectorization/speed
-reorganize for clarity & style changes

TODO
- bug/issues with warping the new face onto a transparent old image...use a cleanup mask for now
- issues with mask border giving black ring at zero erosion .. investigate
- remove GAN ??
- test enlargment factors of umeyama standard face .. match to coverage factor
- make enlargment factor a model parameter
- remove convert_adjusted and referencing code when finished

* Update Convert_Masked.py

default blur size of 2 to match original...
description of enlargement tests
breakout matrxi scaling into def

* Enlargment scale as a cli parameter

* Update cli.py

* dynamic interpolation algorithm

Compute x & y scale factors from the affine matrix on the fly by QR decomp.
Choose interpolation alogrithm for the affine warp based on an upsample or downsample for each image

* input size
input size from config

* fix issues with <1.0 erosion

* Update convert.py

* Update Convert_Adjust.py

more work on the way to merginf

* Clean up help note on sharpen

* cleanup seamless

* Delete Convert_Adjust.py

* Update umeyama.py

* Update training_data.py

* swapping

* segmentation stub

* changes to convert.str

* Update masked.py

* Backwards compatibility fix for models
Get converter running

* Convert:
Move masks to class.
bugfix blur_size
some linting

* mask fix

* convert fixes

- missing facehull_rect re-added
- coverage to %
- corrected coverage logic
- cleanup of gui option ordering

* Update cli.py

* default for blur

* Update masked.py

* added preliminary low_mem version of OriginalHighRes model plugin

* Code cleanup, minor fixes

* Update masked.py

* Update masked.py

* Add dfl mask to convert

* histogram fix & seamless location

* update

* revert

* bugfix: Load actual configuration in gui

* Standardize nn_blocks

* Update cli.py

* Minor code amends

* Fix Original HiRes model

* Add masks to preview output for mask trainers
refactor trainer.__base.py

* Masked trainers converter support

* convert bugfix

* Bugfix: Converter for masked (dfl/dfaker) trainers

* Additional Losses (deepfakes#592)

* initial upload

* Delete blur.py

* default initializer = He instead of Glorot (deepfakes#588)

* Allow kernel_initializer to be overridable

* Add ICNR Initializer option for upscale on all models.

* Hopefully fixes RSoDs with original-highres model plugin

* remove debug line

* Original-HighRes model plugin Red Screen of Death fix, take deepfakes#2

* Move global options to _base. Rename Villain model

* clipnorm and res block biases

* scale the end of res block

* res block

* dfaker pre-activation res

* OHRES pre-activation

* villain pre-activation

* tabs/space in nn_blocks

* fix for histogram with mask all set to zero

* fix to prevent two networks with same name

* GUI: Wider tooltips. Improve TQDM capture

* Fix regex bug

* Convert padding=48 to ratio of image size

* Add size option to alignments tool extract

* Pass through training image size to convert from model

* Convert: Pull training coverage from model

* convert: coverage, blur and erode to percent

* simplify matrix scaling

* ordering of sliders in train

* Add matrix scaling to utils. Use interpolation in lib.aligner transform

* masked.py Import get_matrix_scaling from utils

* fix circular import

* Update masked.py

* quick fix for matrix scaling

* testing thus for now

* tqdm regex capture bugfix

* Minor ammends

* blur size cleanup

* Remove coverage option from convert (Now cascades from model)

* Implement convert for all model types

* Add mask option and coverage option to all existing models

* bugfix for model loading on convert

* debug print removal

* Bugfix for masks in dfl_h128 and iae

* Update preview display. Add preview scaling to cli

* mask notes

* Delete training_data_v2.py

errant file

* training data variables

* Fix timelapse function

* Add new config items to state file for legacy purposes

* Slight GUI tweak

* Raise exception if problem with loaded model

* Add Tensorboard support (Logs stored in model directory)

* ICNR fix

* loss bugfix

* convert bugfix

* Move ini files to config folder. Make TensorBoard optional

* Fix training data for unbalanced inputs/outputs

* Fix config "none" test

* Keep helptext in .ini files when saving config from GUI

* Remove frame_dims from alignments

* Add no-flip and warp-to-landmarks cli options

* Revert OHR to RC4_fix version

* Fix lowmem mode on OHR model

* padding to variable

* Save models in parallel threads

* Speed-up of res_block stability

* Automated Reflection Padding

* Reflect Padding as a training option

Includes auto-calculation of proper padding shapes, input_shapes, output_shapes

Flag included in config now

* rest of reflect padding

* Move TB logging to cli. Session info to state file

* Add session iterations to state file

* Add recent files to menu. GUI code tidy up

* [GUI] Fix recent file list update issue

* Add correct loss names to TensorBoard logs

* Update live graph to use TensorBoard and remove animation

* Fix analysis tab. GUI optimizations

* Analysis Graph popup to Tensorboard Logs

* [GUI] Bug fix for graphing for models with hypens in name

* [GUI] Correctly split loss to tabs during training

* [GUI] Add loss type selection to analysis graph

* Fix store command name in recent files. Switch to correct tab on open

* [GUI] Disable training graph when 'no-logs' is selected

* Fix graphing race condition

* rename original_hires model to unbalanced
  • Loading branch information
torzdf authored Feb 9, 2019
1 parent 584c41e commit cd00859
Show file tree
Hide file tree
Showing 94 changed files with 7,452 additions and 4,268 deletions.
2 changes: 2 additions & 0 deletions .github/ISSUE_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
**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-playground](https://github.com/deepfakes/faceswap-playground/issues) instead. The faceswap-playground is also an excellent place to ask questions and submit feedback.**

**Please always attach your generated crash_report.log to any bug report**

## Expected behavior

*Describe, in some detail, what you are trying to do and what the output is that you expect from the program.*
Expand Down
9 changes: 5 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,19 @@
!Dockerfile*
!requirements*
!.cache
!lib
!lib/face_alignment
!config/
!lib/
!lib/*
!lib/gui
!lib/gui/.cache/preview
!lib/gui/.cache/icons
!scripts
!plugins/
!plugins/*
!plugins/extract/*
!plugins/model/*
!plugins/train/*
!tools
!tools/lib*

*.ini
*.pyc
__pycache__/
56 changes: 28 additions & 28 deletions INSTALL.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
# Installing Faceswap
- [Installing Faceswap](#installing-faceswap)
- [Prerequisites](#prerequisites)
- [Hardware Requirements](#hardware-requirements)
- [Supported operating systems](#supported-operating-systems)
- [Hardware Requirements](#hardware-requirements)
- [Supported operating systems](#supported-operating-systems)
- [Important before you proceed](#important-before-you-proceed)
- [General Install Guide](#general-install-guide)
- [Installing dependencies](#installing-dependencies)
- [Getting the faceswap code](#getting-the-faceswap-code)
- [Setup](#setup)
- [About some of the options](#about-some-of-the-options)
- [Run the project](#run-the-project)
- [Notes](#notes)
- [Installing dependencies](#installing-dependencies)
- [Getting the faceswap code](#getting-the-faceswap-code)
- [Setup](#setup)
- [About some of the options](#about-some-of-the-options)
- [Run the project](#run-the-project)
- [Notes](#notes)
- [Windows Install Guide](#windows-install-guide)
- [Prerequisites](#prerequisites-1)
- [Microsoft Visual Studio 2015](#microsoft-visual-studio-2015)
- [Cuda](#cuda)
- [cuDNN](#cudnn)
- [CMake](#cmake)
- [Anaconda](#anaconda)
- [Git](#git)
- [Setup](#setup-1)
- [Anaconda](#anaconda-1)
- [Set up a virtual environment](#set-up-a-virtual-environment)
- [Entering your virtual environment](#entering-your-virtual-environment)
- [Faceswap](#faceswap)
- [Easy install](#easy-install)
- [Manual install](#manual-install)
- [Running Faceswap](#running-faceswap)
- [Create a desktop shortcut](#create-a-desktop-shortcut)
- [Updating faceswap](#updating-faceswap)
- [Dlib](#dlib)
- [Build Latest Dlib with GPU Support](#build-latest-dlib-with-gpu-support)
- [Easy install of Dlib without GPU Support](#easy-install-of-dlib-without-gpu-support)
- [Prerequisites](#prerequisites-1)
- [Microsoft Visual Studio 2015](#microsoft-visual-studio-2015)
- [Cuda](#cuda)
- [cuDNN](#cudnn)
- [CMake](#cmake)
- [Anaconda](#anaconda)
- [Git](#git)
- [Setup](#setup-1)
- [Anaconda](#anaconda-1)
- [Set up a virtual environment](#set-up-a-virtual-environment)
- [Entering your virtual environment](#entering-your-virtual-environment)
- [Faceswap](#faceswap)
- [Easy install](#easy-install)
- [Manual install](#manual-install)
- [Running Faceswap](#running-faceswap)
- [Create a desktop shortcut](#create-a-desktop-shortcut)
- [Updating faceswap](#updating-faceswap)
- [Dlib](#dlib)
- [Build Latest Dlib with GPU Support](#build-latest-dlib-with-gpu-support)
- [Easy install of Dlib without GPU Support](#easy-install-of-dlib-without-gpu-support)

# Prerequisites
Machine learning essentially involves a ton of trial and error. You're letting a program try millions of different settings to land on an algorithm that sort of does what you want it to do. This process is really really slow unless you have the hardware required to speed this up.
Expand Down
7 changes: 6 additions & 1 deletion USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ You can see the full list of arguments for extracting via help flag. i.e.
python faceswap.py extract -h
```

Some of the plugins have configurable options. You can find the config options in: `<faceswap_folder>\plugins\extract\config.ini`. Extract needs to have been run at least once to generate the config file

## TRAIN
The training process will take the longest, especially on CPU. We specify the folders where the two faces are, and where we will save our training model. It will start hammering the training data once you run the command. I personally really like to go by the preview and quit the processing once I'm happy with the results.

Expand All @@ -51,6 +53,9 @@ You can see the full list of arguments for training via help flag. i.e.
python faceswap.py train -h
```

Some of the plugins have configurable options. You can find the config options in: `<faceswap_folder>\plugins\traom\config.ini`. Train needs to have been run at least once to generate the config file


## CONVERT
Now that we're happy with our trained model, we can convert our video. How does it work? Similarly to the extraction script, actually! The conversion script basically detects a face in a picture using the same algorithm, quickly crops the image to the right size, runs our bot on this cropped image of the face it has found, and then (crudely) pastes the processed face back into the picture.

Expand Down Expand Up @@ -86,7 +91,7 @@ python tools.py effmpeg -h
```

## Extracting video frames with FFMPEG
Alternatively you can split a video into seperate frames using [ffmpeg](https://www.ffmpeg.org) for instance. Below is an example command to process a video to seperate frames.
Alternatively you can split a video into separate frames using [ffmpeg](https://www.ffmpeg.org) for instance. Below is an example command to process a video to separate frames.

```bash
ffmpeg -i /path/to/my/video.mp4 /path/to/output/video-frame-%d.png
Expand Down
File renamed without changes.
88 changes: 0 additions & 88 deletions lib/PixelShuffler.py

This file was deleted.

46 changes: 18 additions & 28 deletions lib/aligner.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,16 @@
logger = logging.getLogger(__name__) # pylint: disable=invalid-name


MEAN_FACE_X = np.array([
0.000213256, 0.0752622, 0.18113, 0.29077, 0.393397, 0.586856, 0.689483,
0.799124, 0.904991, 0.98004, 0.490127, 0.490127, 0.490127, 0.490127,
0.36688, 0.426036, 0.490127, 0.554217, 0.613373, 0.121737, 0.187122,
0.265825, 0.334606, 0.260918, 0.182743, 0.645647, 0.714428, 0.793132,
0.858516, 0.79751, 0.719335, 0.254149, 0.340985, 0.428858, 0.490127,
.551395, 0.639268, 0.726104, 0.642159, 0.556721, 0.490127, 0.423532,
0.338094, 0.290379, 0.428096, 0.490127, 0.552157, 0.689874, 0.553364,
0.490127, 0.42689])

MEAN_FACE_Y = np.array([
0.106454, 0.038915, 0.0187482, 0.0344891, 0.0773906, 0.0773906, 0.0344891,
0.0187482, 0.038915, 0.106454, 0.203352, 0.307009, 0.409805, 0.515625,
0.587326, 0.609345, 0.628106, 0.609345, 0.587326, 0.216423, 0.178758,
0.179852, 0.231733, 0.245099, 0.244077, 0.231733, 0.179852, 0.178758,
0.216423, 0.244077, 0.245099, 0.780233, 0.745405, 0.727388, 0.742578,
0.727388, 0.745405, 0.780233, 0.864805, 0.902192, 0.909281, 0.902192,
0.864805, 0.784792, 0.778746, 0.785343, 0.778746, 0.784792, 0.824182,
0.831803, 0.824182])

LANDMARKS_2D = np.stack([MEAN_FACE_X, MEAN_FACE_Y], axis=1)


class Extract():
""" Based on the original https://www.reddit.com/r/deepfakes/
code sample + contribs """

def extract(self, image, face, size, align_eyes):
""" Extract a face from an image """
logger.trace("size: %s. align_eyes: %s", size, align_eyes)
padding = int(size * 0.1875)
alignment = get_align_mat(face, size, align_eyes)
extracted = self.transform(image, alignment, size, 48)
extracted = self.transform(image, alignment, size, padding)
logger.trace("Returning face and alignment matrix: (alignment_matrix: %s)", alignment)
return extracted, alignment

Expand All @@ -60,8 +38,9 @@ def transform(self, image, mat, size, padding=0):
""" Transform Image """
logger.trace("matrix: %s, size: %s. padding: %s", mat, size, padding)
matrix = self.transform_matrix(mat, size, padding)
interpolators = get_matrix_scaling(matrix)
return cv2.warpAffine( # pylint: disable=no-member
image, matrix, (size, size))
image, matrix, (size, size), flags=interpolators[0])

def transform_points(self, points, mat, size, padding=0):
""" Transform points along matrix """
Expand Down Expand Up @@ -144,12 +123,23 @@ def get_feature_mask(aligned_landmarks_68, size,
return mask


def get_matrix_scaling(mat):
""" Get the correct interpolator """
x_scale = np.sqrt(mat[0, 0] * mat[0, 0] + mat[0, 1] * mat[0, 1])
y_scale = (mat[0, 0] * mat[1, 1] - mat[0, 1] * mat[1, 0]) / x_scale
avg_scale = (x_scale + y_scale) * 0.5
if avg_scale >= 1.0:
interpolators = cv2.INTER_CUBIC, cv2.INTER_AREA # pylint: disable=no-member
else:
interpolators = cv2.INTER_AREA, cv2.INTER_CUBIC # pylint: disable=no-member
logger.trace("interpolator: %s, inverse interpolator: %s", interpolators[0], interpolators[1])
return interpolators


def get_align_mat(face, size, should_align_eyes):
""" Return the alignment Matrix """
logger.trace("size: %s, should_align_eyes: %s", size, should_align_eyes)
mat_umeyama = umeyama(np.array(face.landmarks_as_xy[17:]),
LANDMARKS_2D,
True)[0:2]
mat_umeyama = umeyama(np.array(face.landmarks_as_xy[17:]), True)[0:2]

if should_align_eyes is False:
return mat_umeyama
Expand Down
33 changes: 3 additions & 30 deletions lib/alignments.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,33 +270,6 @@ def yield_original_index_reverse(image_alignments, number_alignments):

# << LEGACY FUNCTIONS >> #

# < Original Frame Dimensions > #
# For dfaker and convert-adjust the original dimensions of a frame are
# required to calculate the transposed landmarks. As transposed landmarks
# will change on face size, we store original frame dimensions
# These were not previously required, so this adds the dimensions
# to the landmarks file

def get_legacy_no_dims(self):
""" Return a list of frames that do not contain the original frame
height and width attributes """
logger.debug("Getting alignments without frame_dims")
keys = list()
for key, val in self.data.items():
for alignment in val:
if "frame_dims" not in alignment.keys():
keys.append(key)
break
logger.debug("Got alignments without frame_dims: %s", len(keys))
return keys

def add_dimensions(self, frame_name, dimensions):
""" Backward compatability fix. Add frame dimensions
to alignments """
logger.trace("Adding dimensions: (frame: '%s', dimensions: %s)", frame_name, dimensions)
for face in self.get_faces_in_frame(frame_name):
face["frame_dims"] = dimensions

# < Rotation > #
# The old rotation method would rotate the image to find a face, then
# store the rotated landmarks along with a rotation value to tell the
Expand All @@ -319,20 +292,20 @@ def get_legacy_rotation(self):
logger.debug("Got alignments containing legacy rotations: %s", len(keys))
return keys

def rotate_existing_landmarks(self, frame_name):
def rotate_existing_landmarks(self, frame_name, frame):
""" Backwards compatability fix. Rotates the landmarks to
their correct position and deletes r
NB: The original frame dimensions must be passed in otherwise
NB: The original frame must be passed in otherwise
the transformation cannot be performed """
logger.trace("Rotating existing landmarks for frame: '%s'", frame_name)
dims = frame.shape[:2]
for face in self.get_faces_in_frame(frame_name):
angle = face.get("r", 0)
if not angle:
logger.trace("Landmarks do not require rotation: '%s'", frame_name)
return
logger.trace("Rotating landmarks: (frame: '%s', angle: %s)", frame_name, angle)
dims = face["frame_dims"]
r_mat = self.get_original_rotation_matrix(dims, angle)
rotate_landmarks(face, r_mat)
del face["r"]
Expand Down
Loading

0 comments on commit cd00859

Please sign in to comment.