1
0
Fork 0
mirror of https://github.com/deepfakes/faceswap synced 2025-06-07 10:43:27 -04:00
faceswap/lib/aligner.py
torzdf cd00859c40
model_refactor (#571) (#572)
* model_refactor (#571)

* original model to new structure

* IAE model to new structure

* OriginalHiRes to new structure

* Fix trainer for different resolutions

* Initial config implementation

* Configparse library added

* improved training data loader

* dfaker model working

* Add logging to training functions

* Non blocking input for cli training

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

* Improved Model Building and NNMeta

* refactor lib/models

* training refactor. DFL H128 model Implementation

* Dfaker - use hashes

* Move timelapse. Remove perceptual loss arg

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

* DFL h128 partially ported

* Add mask to dfaker (#573)

* Remove old models. Add mask to dfaker

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

* DFL H128 Mask. Mask type selectable in config.

* remove gan_v2_2

* Creating Input Size config for models

Creating Input Size config for models

Will be used downstream in converters.

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

* Add mask loss options to config

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

* Add sliders for numerical values in GUI

* Add config plugins menu to gui. Validate config

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

* bugfixes

* Standardise loss printing

* GUI idle cpu fixes. Graph loss fix.

* mutli-gpu logging bugfix

* Merge branch 'staging' into train_refactor

* backup state file

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

* Port OriginalHiRes_RC4 to train_refactor (OriginalHiRes)

* Load and save model structure with weights

* Slight code update

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

* Show samples... wrong input

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

* Port original_villain (birb/VillainGuy) model to faceswap

* Add plugin info to GUI config pages

* Load input shape from state. IAE Config options.

* Fix transform_kwargs.
Coverage to ratio.
Bugfix mask detection

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

* Consolidation of converters & refactor (#574)

* Consolidation of converters & refactor

Initial Upload of alpha

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

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

* Update Convert_Masked.py

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

* Enlargment scale as a cli parameter

* Update cli.py

* dynamic interpolation algorithm

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

* input size
input size from config

* fix issues with <1.0 erosion

* Update convert.py

* Update Convert_Adjust.py

more work on the way to merginf

* Clean up help note on sharpen

* cleanup seamless

* Delete Convert_Adjust.py

* Update umeyama.py

* Update training_data.py

* swapping

* segmentation stub

* changes to convert.str

* Update masked.py

* Backwards compatibility fix for models
Get converter running

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

* mask fix

* convert fixes

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

* Update cli.py

* default for blur

* Update masked.py

* added preliminary low_mem version of OriginalHighRes model plugin

* Code cleanup, minor fixes

* Update masked.py

* Update masked.py

* Add dfl mask to convert

* histogram fix & seamless location

* update

* revert

* bugfix: Load actual configuration in gui

* Standardize nn_blocks

* Update cli.py

* Minor code amends

* Fix Original HiRes model

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

* Masked trainers converter support

* convert bugfix

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

* Additional Losses (#592)

* initial upload

* Delete blur.py

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

* Allow kernel_initializer to be overridable

* Add ICNR Initializer option for upscale on all models.

* Hopefully fixes RSoDs with original-highres model plugin

* remove debug line

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

* Move global options to _base. Rename Villain model

* clipnorm and res block biases

* scale the end of res block

* res block

* dfaker pre-activation res

* OHRES pre-activation

* villain pre-activation

* tabs/space in nn_blocks

* fix for histogram with mask all set to zero

* fix to prevent two networks with same name

* GUI: Wider tooltips. Improve TQDM capture

* Fix regex bug

* Convert padding=48 to ratio of image size

* Add size option to alignments tool extract

* Pass through training image size to convert from model

* Convert: Pull training coverage from model

* convert: coverage, blur and erode to percent

* simplify matrix scaling

* ordering of sliders in train

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

* masked.py Import get_matrix_scaling from utils

* fix circular import

* Update masked.py

* quick fix for matrix scaling

* testing thus for now

* tqdm regex capture bugfix

* Minor ammends

* blur size cleanup

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

* Implement convert for all model types

* Add mask option and coverage option to all existing models

* bugfix for model loading on convert

* debug print removal

* Bugfix for masks in dfl_h128 and iae

* Update preview display. Add preview scaling to cli

* mask notes

* Delete training_data_v2.py

errant file

* training data variables

* Fix timelapse function

* Add new config items to state file for legacy purposes

* Slight GUI tweak

* Raise exception if problem with loaded model

* Add Tensorboard support (Logs stored in model directory)

* ICNR fix

* loss bugfix

* convert bugfix

* Move ini files to config folder. Make TensorBoard optional

* Fix training data for unbalanced inputs/outputs

* Fix config "none" test

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

* Remove frame_dims from alignments

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

* Revert OHR to RC4_fix version

* Fix lowmem mode on OHR model

* padding to variable

* Save models in parallel threads

* Speed-up of res_block stability

* Automated Reflection Padding

* Reflect Padding as a training option

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

Flag included in config now

* rest of reflect padding

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

* Add session iterations to state file

* Add recent files to menu. GUI code tidy up

* [GUI] Fix recent file list update issue

* Add correct loss names to TensorBoard logs

* Update live graph to use TensorBoard and remove animation

* Fix analysis tab. GUI optimizations

* Analysis Graph popup to Tensorboard Logs

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

* [GUI] Correctly split loss to tabs during training

* [GUI] Add loss type selection to analysis graph

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

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

* Fix graphing race condition

* rename original_hires model to unbalanced
2019-02-09 18:35:12 +00:00

180 lines
7.7 KiB
Python

#!/usr/bin/env python3
""" Aligner for faceswap.py """
import logging
import cv2
import numpy as np
from lib.umeyama import umeyama
from lib.align_eyes import align_eyes as func_align_eyes, FACIAL_LANDMARKS_IDXS
logger = logging.getLogger(__name__) # pylint: disable=invalid-name
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, padding)
logger.trace("Returning face and alignment matrix: (alignment_matrix: %s)", alignment)
return extracted, alignment
@staticmethod
def transform_matrix(mat, size, padding):
""" Transform the matrix for current size and padding """
logger.trace("size: %s. padding: %s", size, padding)
matrix = mat * (size - 2 * padding)
matrix[:, 2] += padding
logger.trace("Returning: %s", matrix)
return matrix
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), flags=interpolators[0])
def transform_points(self, points, mat, size, padding=0):
""" Transform points along matrix """
logger.trace("points: %s, matrix: %s, size: %s. padding: %s", points, mat, size, padding)
matrix = self.transform_matrix(mat, size, padding)
points = np.expand_dims(points, axis=1)
points = cv2.transform( # pylint: disable=no-member
points, matrix, points.shape)
retval = np.squeeze(points)
logger.trace("Returning: %s", retval)
return retval
def get_original_roi(self, mat, size, padding=0):
""" Return the square aligned box location on the original
image """
logger.trace("matrix: %s, size: %s. padding: %s", mat, size, padding)
matrix = self.transform_matrix(mat, size, padding)
points = np.array([[0, 0],
[0, size - 1],
[size - 1, size - 1],
[size - 1, 0]], np.int32)
points = points.reshape((-1, 1, 2))
matrix = cv2.invertAffineTransform(matrix) # pylint: disable=no-member
logger.trace("Returning: (points: %s, matrix: %s", points, matrix)
return cv2.transform(points, matrix) # pylint: disable=no-member
@staticmethod
def get_feature_mask(aligned_landmarks_68, size,
padding=0, dilation=30):
""" Return the face feature mask """
# pylint: disable=no-member
logger.trace("aligned_landmarks_68: %s, size: %s, padding: %s, dilation: %s",
aligned_landmarks_68, size, padding, dilation)
scale = size - 2 * padding
translation = padding
pad_mat = np.matrix([[scale, 0.0, translation],
[0.0, scale, translation]])
aligned_landmarks_68 = np.expand_dims(aligned_landmarks_68, axis=1)
aligned_landmarks_68 = cv2.transform(aligned_landmarks_68,
pad_mat,
aligned_landmarks_68.shape)
aligned_landmarks_68 = np.squeeze(aligned_landmarks_68)
(l_start, l_end) = FACIAL_LANDMARKS_IDXS["left_eye"]
(r_start, r_end) = FACIAL_LANDMARKS_IDXS["right_eye"]
(m_start, m_end) = FACIAL_LANDMARKS_IDXS["mouth"]
(n_start, n_end) = FACIAL_LANDMARKS_IDXS["nose"]
(lb_start, lb_end) = FACIAL_LANDMARKS_IDXS["left_eyebrow"]
(rb_start, rb_end) = FACIAL_LANDMARKS_IDXS["right_eyebrow"]
(c_start, c_end) = FACIAL_LANDMARKS_IDXS["chin"]
l_eye_points = aligned_landmarks_68[l_start:l_end].tolist()
l_brow_points = aligned_landmarks_68[lb_start:lb_end].tolist()
r_eye_points = aligned_landmarks_68[r_start:r_end].tolist()
r_brow_points = aligned_landmarks_68[rb_start:rb_end].tolist()
nose_points = aligned_landmarks_68[n_start:n_end].tolist()
chin_points = aligned_landmarks_68[c_start:c_end].tolist()
mouth_points = aligned_landmarks_68[m_start:m_end].tolist()
l_eye_points = l_eye_points + l_brow_points
r_eye_points = r_eye_points + r_brow_points
mouth_points = mouth_points + nose_points + chin_points
l_eye_hull = cv2.convexHull(np.array(l_eye_points).reshape(
(-1, 2)).astype(int)).flatten().reshape((-1, 2))
r_eye_hull = cv2.convexHull(np.array(r_eye_points).reshape(
(-1, 2)).astype(int)).flatten().reshape((-1, 2))
mouth_hull = cv2.convexHull(np.array(mouth_points).reshape(
(-1, 2)).astype(int)).flatten().reshape((-1, 2))
mask = np.zeros((size, size, 3), dtype=float)
cv2.fillConvexPoly(mask, l_eye_hull, (1, 1, 1))
cv2.fillConvexPoly(mask, r_eye_hull, (1, 1, 1))
cv2.fillConvexPoly(mask, mouth_hull, (1, 1, 1))
if dilation > 0:
kernel = np.ones((dilation, dilation), np.uint8)
mask = cv2.dilate(mask, kernel, iterations=1)
logger.trace("Returning: %s", mask)
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:]), True)[0:2]
if should_align_eyes is False:
return mat_umeyama
mat_umeyama = mat_umeyama * size
# Convert to matrix
landmarks = np.matrix(face.landmarks_as_xy)
# cv2 expects points to be in the form
# np.array([ [[x1, y1]], [[x2, y2]], ... ]), we'll expand the dim
landmarks = np.expand_dims(landmarks, axis=1)
# Align the landmarks using umeyama
umeyama_landmarks = cv2.transform( # pylint: disable=no-member
landmarks,
mat_umeyama,
landmarks.shape)
# Determine a rotation matrix to align eyes horizontally
mat_align_eyes = func_align_eyes(umeyama_landmarks, size)
# Extend the 2x3 transform matrices to 3x3 so we can multiply them
# and combine them as one
mat_umeyama = np.matrix(mat_umeyama)
mat_umeyama.resize((3, 3))
mat_align_eyes = np.matrix(mat_align_eyes)
mat_align_eyes.resize((3, 3))
mat_umeyama[2] = mat_align_eyes[2] = [0, 0, 1]
# Combine the umeyama transform with the extra rotation matrix
transform_mat = mat_align_eyes * mat_umeyama
# Remove the extra row added, shape needs to be 2x3
transform_mat = np.delete(transform_mat, 2, 0)
transform_mat = transform_mat / size
logger.trace("Returning: %s", transform_mat)
return transform_mat