1
0
Fork 0
mirror of https://github.com/deepfakes/faceswap synced 2025-06-08 11:53:26 -04:00
faceswap/lib/faces_detect.py
torzdf 27a685383e
Convert refactor (#703)
* Separate predict and implement pool

* Add check and raise error to multithreading

Box functions to config. Add crop box option.

* All masks to mask module. Refactor convert masks

Predicted mask passed from model. Cli update

* Intesect box with mask and fixes

* Use raw NN output for convert

Use raw mask for face adjustments. Split adjustments to pre and post warp

* Separate out adjustments. Add unmask sharpen

* Set sensible defaults. Pre PR Testing

* Fix queue sizes. Move masked.py to lib

* Fix Skip Frames. Fix GUI Config popup

* Sensible queue limits. Add a less resource intensive single processing mode

* Fix predicted mask. Amend smooth box defaults

* Deterministic ordering for video output

* Video to Video convert implemented

* Fixups

- Remove defaults from folders across all stages
- Move match-hist and aca into color adjustments selectable
- Handle crashes properly for pooled processes
- Fix output directory does not exist error when creating a new output folder
- Force output to frames if input is not a video

* Add Color Transfer adjustment method

Wrap info text in GUI plugin configure popup

* Refactor image adjustments. Easier to create plugins

Start implementing config options for video encoding

* Add video encoding config options

Allow video encoding for frames input (must pass in a reference video)
Move video and image output writers to plugins

* Image writers config options

Move scaling to cli
Move draw_transparent to images config
Add config options for cv2 writer
Add Pillow image writer

* Add gif filetype to Pillow. Fix draw transparent for Pillow

* Add Animated GIF writer

standardize opencv/pillow defaults

* [speedup] Pre-encode supported writers in the convert pool (opencv, pillow)

Move scaling to convert pool
Remove dfaker mask

* Fix default writer

* Bugfixes

* Better custom argparse formatting
2019-04-21 19:19:06 +00:00

265 lines
11 KiB
Python

#!/usr/bin python3
""" Face and landmarks detection for faceswap.py """
import logging
import numpy as np
from dlib import rectangle as d_rectangle # pylint: disable=no-name-in-module
from lib.aligner import Extract as AlignerExtract, get_align_mat, get_matrix_scaling
logger = logging.getLogger(__name__) # pylint: disable=invalid-name
class DetectedFace():
""" Detected face and landmark information """
def __init__( # pylint: disable=invalid-name
self, image=None, x=None, w=None, y=None, h=None,
landmarksXY=None):
logger.trace("Initializing %s", self.__class__.__name__)
self.image = image
self.x = x
self.w = w
self.y = y
self.h = h
self.landmarksXY = landmarksXY
self.hash = None # Hash must be set when the file is saved due to image compression
self.aligned = dict()
self.feed = dict()
self.reference = dict()
logger.trace("Initialized %s", self.__class__.__name__)
@property
def extract_ratio(self):
""" The ratio of padding to add for training images """
return 0.375
@property
def landmarks_as_xy(self):
""" Landmarks as XY """
return self.landmarksXY
def to_dlib_rect(self):
""" Return Bounding Box as Dlib Rectangle """
left = self.x
top = self.y
right = self.x + self.w
bottom = self.y + self.h
retval = d_rectangle(left, top, right, bottom)
logger.trace("Returning: %s", retval)
return retval
def from_dlib_rect(self, d_rect, image=None):
""" Set Bounding Box from a Dlib Rectangle """
logger.trace("Creating from dlib_rectangle: %s", d_rect)
if not isinstance(d_rect, d_rectangle):
raise ValueError("Supplied Bounding Box is not a dlib.rectangle.")
self.x = d_rect.left()
self.w = d_rect.right() - d_rect.left()
self.y = d_rect.top()
self.h = d_rect.bottom() - d_rect.top()
if image is not None and image.any():
self.image_to_face(image)
logger.trace("Created from dlib_rectangle: (x: %s, w: %s, y: %s. h: %s)",
self.x, self.w, self.y, self.h)
def image_to_face(self, image):
""" Crop an image around bounding box to the face
and capture it's dimensions """
logger.trace("Cropping face from image")
self.image = image[self.y: self.y + self.h,
self.x: self.x + self.w]
def to_alignment(self):
""" Convert a detected face to alignment dict """
alignment = dict()
alignment["x"] = self.x
alignment["w"] = self.w
alignment["y"] = self.y
alignment["h"] = self.h
alignment["landmarksXY"] = self.landmarksXY
alignment["hash"] = self.hash
logger.trace("Returning: %s", alignment)
return alignment
def from_alignment(self, alignment, image=None):
""" Convert a face alignment to detected face object """
logger.trace("Creating from alignment: (alignment: %s, has_image: %s)",
alignment, bool(image is not None))
self.x = alignment["x"]
self.w = alignment["w"]
self.y = alignment["y"]
self.h = alignment["h"]
self.landmarksXY = alignment["landmarksXY"]
# Manual tool does not know the final hash so default to None
self.hash = alignment.get("hash", None)
if image is not None and image.any():
self.image_to_face(image)
logger.trace("Created from alignment: (x: %s, w: %s, y: %s. h: %s, "
"landmarks: %s)",
self.x, self.w, self.y, self.h, self.landmarksXY)
# <<< Aligned Face methods and properties >>> #
def load_aligned(self, image, size=256, align_eyes=False, dtype=None):
""" No need to load aligned information for all uses of this
class, so only call this to load the information for easy
reference to aligned properties for this face """
logger.trace("Loading aligned face: (size: %s, align_eyes: %s, dtype: %s)",
size, align_eyes, dtype)
padding = int(size * self.extract_ratio) // 2
self.aligned["size"] = size
self.aligned["padding"] = padding
self.aligned["align_eyes"] = align_eyes
self.aligned["matrix"] = get_align_mat(self, size, align_eyes)
if image is None:
self.aligned["face"] = None
else:
face = AlignerExtract().transform(
image,
self.aligned["matrix"],
size,
padding)
self.aligned["face"] = face if dtype is None else face.astype(dtype)
logger.trace("Loaded aligned face: %s", {key: val
for key, val in self.aligned.items()
if key != "face"})
def padding_from_coverage(self, size, coverage_ratio):
""" Return the image padding for a face from coverage_ratio set against a
pre-padded training image """
adjusted_ratio = coverage_ratio - (1 - self.extract_ratio)
padding = round((size * adjusted_ratio) / 2)
logger.trace(padding)
return padding
def load_feed_face(self, image, size=64, coverage_ratio=0.625, dtype=None):
""" Return a face in the correct dimensions for feeding into a NN
Coverage ratio should be the ratio of the extracted image that was used for
training """
logger.trace("Loading feed face: (size: %s, coverage_ratio: %s, dtype: %s)",
size, coverage_ratio, dtype)
self.feed["size"] = size
self.feed["padding"] = self.padding_from_coverage(size, coverage_ratio)
self.feed["matrix"] = get_align_mat(self, size, should_align_eyes=False)
face = np.clip(AlignerExtract().transform(image,
self.feed["matrix"],
size,
self.feed["padding"])[:, :, :3] / 255.0,
0.0, 1.0)
self.feed["face"] = face if dtype is None else face.astype(dtype)
logger.trace("Loaded feed face. (face_shape: %s, matrix: %s)",
self.feed_face.shape, self.feed_matrix)
def load_reference_face(self, image, size=64, coverage_ratio=0.625, dtype=None):
""" Return a face in the correct dimensions for reference to the output from a NN
Coverage ratio should be the ratio of the extracted image that was used for
training """
logger.trace("Loading reference face: (size: %s, coverage_ratio: %s, dtype: %s)",
size, coverage_ratio, dtype)
self.reference["size"] = size
self.reference["padding"] = self.padding_from_coverage(size, coverage_ratio)
self.reference["matrix"] = get_align_mat(self, size, should_align_eyes=False)
face = np.clip(AlignerExtract().transform(image,
self.reference["matrix"],
size,
self.reference["padding"])[:, :, :3] / 255.0,
0.0, 1.0)
self.reference["face"] = face if dtype is None else face.astype(dtype)
logger.trace("Loaded reference face. (face_shape: %s, matrix: %s)",
self.reference_face.shape, self.reference_matrix)
@property
def original_roi(self):
""" Return the square aligned box location on the original
image """
roi = AlignerExtract().get_original_roi(self.aligned["matrix"],
self.aligned["size"],
self.aligned["padding"])
logger.trace("Returning: %s", roi)
return roi
@property
def aligned_landmarks(self):
""" Return the landmarks location transposed to extracted face """
landmarks = AlignerExtract().transform_points(self.landmarksXY,
self.aligned["matrix"],
self.aligned["size"],
self.aligned["padding"])
logger.trace("Returning: %s", landmarks)
return landmarks
@property
def aligned_face(self):
""" Return aligned detected face """
return self.aligned["face"]
@property
def adjusted_matrix(self):
""" Return adjusted matrix for size/padding combination """
mat = AlignerExtract().transform_matrix(self.aligned["matrix"],
self.aligned["size"],
self.aligned["padding"])
logger.trace("Returning: %s", mat)
return mat
@property
def adjusted_interpolators(self):
""" Return the interpolator and reverse interpolator for the adjusted matrix """
return get_matrix_scaling(self.adjusted_matrix)
@property
def feed_face(self):
""" Return face for feeding into NN """
return self.feed["face"]
@property
def feed_matrix(self):
""" Return matrix for transforming feed face back to image """
mat = AlignerExtract().transform_matrix(self.feed["matrix"],
self.feed["size"],
self.feed["padding"])
logger.trace("Returning: %s", mat)
return mat
@property
def feed_interpolators(self):
""" Return the interpolators for an input face """
return get_matrix_scaling(self.feed_matrix)
@property
def reference_face(self):
""" Return source face at size of output from NN for reference """
return self.reference["face"]
@property
def reference_landmarks(self):
""" Return the landmarks location transposed to reference face """
landmarks = AlignerExtract().transform_points(self.landmarksXY,
self.reference["matrix"],
self.reference["size"],
self.reference["padding"])
logger.trace("Returning: %s", landmarks)
return landmarks
@property
def reference_matrix(self):
""" Return matrix for transforming output face back to image """
mat = AlignerExtract().transform_matrix(self.reference["matrix"],
self.reference["size"],
self.reference["padding"])
logger.trace("Returning: %s", mat)
return mat
@property
def reference_interpolators(self):
""" Return the interpolators for an output face """
return get_matrix_scaling(self.reference_matrix)