mirror of
https://github.com/deepfakes/faceswap.git
synced 2026-10-17 05:21:10 +08:00
184 lines
6.2 KiB
Python
184 lines
6.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Handles loading of faces/frames from source locations and pairing with alignments
|
|
information"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
import typing as T
|
|
|
|
import numpy as np
|
|
from tqdm import tqdm
|
|
|
|
from lib.align import alignments, DetectedFace
|
|
from lib.image import FacesLoader, ImagesLoader
|
|
from lib.utils import get_module_objects
|
|
from lib.infer.objects import FrameFaces
|
|
|
|
if T.TYPE_CHECKING:
|
|
from lib.align.objects import FileAlignments, PNGAlignments, PNGHeader
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Loader:
|
|
"""Loader for reading source data from disk, and yielding the output paired with alignment
|
|
information
|
|
|
|
Parameters
|
|
----------
|
|
location
|
|
Full path to the source files location
|
|
is_faces
|
|
``True`` if the source is a folder of faceswap extracted faces
|
|
"""
|
|
def __init__(self, location: str, is_faces: bool) -> None:
|
|
logger.debug("Initializing %s (location: %s, is_faces: %s)",
|
|
self.__class__.__name__, location, is_faces)
|
|
|
|
self._is_faces = is_faces
|
|
self._loader = FacesLoader(location) if is_faces else ImagesLoader(location)
|
|
self._alignments: alignments.Alignments | None = None
|
|
self._skip_count = 0
|
|
|
|
logger.debug("Initialized %s", self.__class__.__name__)
|
|
|
|
@property
|
|
def file_list(self) -> list[str]:
|
|
"""Full file list of source files to be loaded """
|
|
return self._loader.file_list
|
|
|
|
@property
|
|
def is_video(self) -> bool:
|
|
"""``True`` if the source is a video file otherwise ``False`` """
|
|
return self._loader.is_video
|
|
|
|
@property
|
|
def location(self) -> str:
|
|
"""Full path to the source folder/video file location """
|
|
return self._loader.location
|
|
|
|
@property
|
|
def skip_count(self) -> int:
|
|
"""The number of faces/frames that have been skipped due to no match in alignments file"""
|
|
return self._skip_count
|
|
|
|
def add_alignments(self, alignments_object: alignments.Alignments | None) -> None:
|
|
"""Add the loaded alignments to :attr:`_alignments` for content matching
|
|
|
|
Parameters
|
|
----------
|
|
alignments_object
|
|
The alignments file object or ``None`` if not provided
|
|
"""
|
|
logger.debug("Adding alignments to loader: %s", alignments_object)
|
|
self._alignments = alignments_object
|
|
|
|
def _process_face(self,
|
|
filename: str,
|
|
image: np.ndarray,
|
|
metadata: PNGHeader) -> FrameFaces | None:
|
|
"""Process a single face when masking from face images
|
|
|
|
Parameters
|
|
----------
|
|
filename
|
|
the filename currently being processed
|
|
image
|
|
The current face being processed
|
|
metadata
|
|
The source frame metadata from the PNG header
|
|
|
|
Returns
|
|
-------
|
|
the extract media object for the processed face or ``None`` if alignment information
|
|
could not be found
|
|
"""
|
|
frame_name = metadata.source.source_filename
|
|
face_index = metadata.source.face_index
|
|
|
|
if self._alignments is None: # mask from PNG header
|
|
lookup_index = 0
|
|
aligns: list[FileAlignments] | list[PNGAlignments] = [metadata.alignments]
|
|
else: # mask from Alignments file
|
|
lookup_index = face_index
|
|
aligns = self._alignments.get_faces_in_frame(frame_name)
|
|
if not aligns or face_index > len(aligns) - 1:
|
|
self._skip_count += 1
|
|
logger.warning("Skipping Face not found in alignments file: '%s'", filename)
|
|
return None
|
|
|
|
alignment = aligns[lookup_index]
|
|
retval = FrameFaces(filename, image, is_aligned=True, frame_metadata=metadata.source)
|
|
retval.detected_faces = [DetectedFace().from_alignment(alignment)]
|
|
return retval
|
|
|
|
def _from_faces(self) -> T.Generator[FrameFaces, None, None]:
|
|
"""Load content from pre-aligned faces and pair with corresponding metadata
|
|
|
|
Yields
|
|
------
|
|
The extract media object for the processed face
|
|
"""
|
|
for filename, image, metadata in tqdm(self._loader.load(), total=self._loader.count):
|
|
if not metadata:
|
|
self._skip_count += 1
|
|
logger.warning("Non-Faceswap extracted face found. Image skipped: '%s'",
|
|
filename)
|
|
continue
|
|
|
|
retval = self._process_face(filename, image, metadata)
|
|
if retval is None:
|
|
continue
|
|
|
|
yield retval
|
|
|
|
def _from_frames(self) -> T.Generator[FrameFaces, None, None]:
|
|
"""Load content from frames and and pair with corresponding metadata
|
|
|
|
Yields
|
|
------
|
|
The extract media object for the processed face
|
|
"""
|
|
assert self._alignments is not None
|
|
for filename, image in tqdm(self._loader.load(), total=self._loader.count):
|
|
frame = os.path.basename(filename)
|
|
|
|
if not self._alignments.frame_exists(frame):
|
|
self._skip_count += 1
|
|
logger.warning("Skipping frame not in alignments file: '%s'", frame)
|
|
continue
|
|
|
|
if not self._alignments.frame_has_faces(frame):
|
|
logger.debug("Skipping frame with no faces: '%s'", frame)
|
|
continue
|
|
|
|
faces_in_frame = self._alignments.get_faces_in_frame(frame)
|
|
detected_faces = [DetectedFace().from_alignment(alignment)
|
|
for alignment in faces_in_frame]
|
|
|
|
retval = FrameFaces(filename, image)
|
|
retval.detected_faces = detected_faces
|
|
|
|
yield retval
|
|
|
|
def load(self) -> T.Generator[FrameFaces, None, None]:
|
|
"""Load content from source and pair with corresponding alignment data
|
|
|
|
Yields
|
|
------
|
|
The extract media object for the processed face
|
|
"""
|
|
if self._is_faces:
|
|
iterator = self._from_faces
|
|
else:
|
|
iterator = self._from_frames
|
|
|
|
yield from iterator()
|
|
|
|
if self._skip_count > 0:
|
|
logger.warning("%s face(s) skipped due to not existing in the alignments file",
|
|
self._skip_count)
|
|
|
|
|
|
__all__ = get_module_objects(__name__)
|