mirror of
https://github.com/deepfakes/faceswap
synced 2025-06-07 10:43:27 -04:00
* model_refactor (#571) * original model to new structure * IAE model to new structure * OriginalHiRes to new structure * Fix trainer for different resolutions * Initial config implementation * Configparse library added * improved training data loader * dfaker model working * Add logging to training functions * Non blocking input for cli training * Add error handling to threads. Add non-mp queues to queue_handler * Improved Model Building and NNMeta * refactor lib/models * training refactor. DFL H128 model Implementation * Dfaker - use hashes * Move timelapse. Remove perceptual loss arg * Update INSTALL.md. Add logger formatting. Update Dfaker training * DFL h128 partially ported * Add mask to dfaker (#573) * Remove old models. Add mask to dfaker * dfl mask. Make masks selectable in config (#575) * DFL H128 Mask. Mask type selectable in config. * remove gan_v2_2 * Creating Input Size config for models Creating Input Size config for models Will be used downstream in converters. Also name change of image_shape to input_shape to clarify ( for future models with potentially different output_shapes) * Add mask loss options to config * MTCNN options to config.ini. Remove GAN config. Update USAGE.md * Add sliders for numerical values in GUI * Add config plugins menu to gui. Validate config * Only backup model if loss has dropped. Get training working again * bugfixes * Standardise loss printing * GUI idle cpu fixes. Graph loss fix. * mutli-gpu logging bugfix * Merge branch 'staging' into train_refactor * backup state file * Crash protection: Only backup if both total losses have dropped * Port OriginalHiRes_RC4 to train_refactor (OriginalHiRes) * Load and save model structure with weights * Slight code update * Improve config loader. Add subpixel opt to all models. Config to state * Show samples... wrong input * Remove AE topology. Add input/output shapes to State * Port original_villain (birb/VillainGuy) model to faceswap * Add plugin info to GUI config pages * Load input shape from state. IAE Config options. * Fix transform_kwargs. Coverage to ratio. Bugfix mask detection * Suppress keras userwarnings. Automate zoom. Coverage_ratio to model def. * Consolidation of converters & refactor (#574) * Consolidation of converters & refactor Initial Upload of alpha Items - consolidate convert_mased & convert_adjust into one converter -add average color adjust to convert_masked -allow mask transition blur size to be a fixed integer of pixels and a fraction of the facial mask size -allow erosion/dilation size to be a fixed integer of pixels and a fraction of the facial mask size -eliminate redundant type conversions to avoid multiple round-off errors -refactor loops for vectorization/speed -reorganize for clarity & style changes TODO - bug/issues with warping the new face onto a transparent old image...use a cleanup mask for now - issues with mask border giving black ring at zero erosion .. investigate - remove GAN ?? - test enlargment factors of umeyama standard face .. match to coverage factor - make enlargment factor a model parameter - remove convert_adjusted and referencing code when finished * Update Convert_Masked.py default blur size of 2 to match original... description of enlargement tests breakout matrxi scaling into def * Enlargment scale as a cli parameter * Update cli.py * dynamic interpolation algorithm Compute x & y scale factors from the affine matrix on the fly by QR decomp. Choose interpolation alogrithm for the affine warp based on an upsample or downsample for each image * input size input size from config * fix issues with <1.0 erosion * Update convert.py * Update Convert_Adjust.py more work on the way to merginf * Clean up help note on sharpen * cleanup seamless * Delete Convert_Adjust.py * Update umeyama.py * Update training_data.py * swapping * segmentation stub * changes to convert.str * Update masked.py * Backwards compatibility fix for models Get converter running * Convert: Move masks to class. bugfix blur_size some linting * mask fix * convert fixes - missing facehull_rect re-added - coverage to % - corrected coverage logic - cleanup of gui option ordering * Update cli.py * default for blur * Update masked.py * added preliminary low_mem version of OriginalHighRes model plugin * Code cleanup, minor fixes * Update masked.py * Update masked.py * Add dfl mask to convert * histogram fix & seamless location * update * revert * bugfix: Load actual configuration in gui * Standardize nn_blocks * Update cli.py * Minor code amends * Fix Original HiRes model * Add masks to preview output for mask trainers refactor trainer.__base.py * Masked trainers converter support * convert bugfix * Bugfix: Converter for masked (dfl/dfaker) trainers * Additional Losses (#592) * initial upload * Delete blur.py * default initializer = He instead of Glorot (#588) * Allow kernel_initializer to be overridable * Add ICNR Initializer option for upscale on all models. * Hopefully fixes RSoDs with original-highres model plugin * remove debug line * Original-HighRes model plugin Red Screen of Death fix, take #2 * Move global options to _base. Rename Villain model * clipnorm and res block biases * scale the end of res block * res block * dfaker pre-activation res * OHRES pre-activation * villain pre-activation * tabs/space in nn_blocks * fix for histogram with mask all set to zero * fix to prevent two networks with same name * GUI: Wider tooltips. Improve TQDM capture * Fix regex bug * Convert padding=48 to ratio of image size * Add size option to alignments tool extract * Pass through training image size to convert from model * Convert: Pull training coverage from model * convert: coverage, blur and erode to percent * simplify matrix scaling * ordering of sliders in train * Add matrix scaling to utils. Use interpolation in lib.aligner transform * masked.py Import get_matrix_scaling from utils * fix circular import * Update masked.py * quick fix for matrix scaling * testing thus for now * tqdm regex capture bugfix * Minor ammends * blur size cleanup * Remove coverage option from convert (Now cascades from model) * Implement convert for all model types * Add mask option and coverage option to all existing models * bugfix for model loading on convert * debug print removal * Bugfix for masks in dfl_h128 and iae * Update preview display. Add preview scaling to cli * mask notes * Delete training_data_v2.py errant file * training data variables * Fix timelapse function * Add new config items to state file for legacy purposes * Slight GUI tweak * Raise exception if problem with loaded model * Add Tensorboard support (Logs stored in model directory) * ICNR fix * loss bugfix * convert bugfix * Move ini files to config folder. Make TensorBoard optional * Fix training data for unbalanced inputs/outputs * Fix config "none" test * Keep helptext in .ini files when saving config from GUI * Remove frame_dims from alignments * Add no-flip and warp-to-landmarks cli options * Revert OHR to RC4_fix version * Fix lowmem mode on OHR model * padding to variable * Save models in parallel threads * Speed-up of res_block stability * Automated Reflection Padding * Reflect Padding as a training option Includes auto-calculation of proper padding shapes, input_shapes, output_shapes Flag included in config now * rest of reflect padding * Move TB logging to cli. Session info to state file * Add session iterations to state file * Add recent files to menu. GUI code tidy up * [GUI] Fix recent file list update issue * Add correct loss names to TensorBoard logs * Update live graph to use TensorBoard and remove animation * Fix analysis tab. GUI optimizations * Analysis Graph popup to Tensorboard Logs * [GUI] Bug fix for graphing for models with hypens in name * [GUI] Correctly split loss to tabs during training * [GUI] Add loss type selection to analysis graph * Fix store command name in recent files. Switch to correct tab on open * [GUI] Disable training graph when 'no-logs' is selected * Fix graphing race condition * rename original_hires model to unbalanced
180 lines
7.7 KiB
Python
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
|