mirror of
https://github.com/zebrajr/faceswap.git
synced 2026-01-15 12:15:15 +00:00
Alignments tool - Replace 'extract-large' with 'min-size'
This commit is contained in:
Binary file not shown.
@@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: faceswap.spanish\n"
|
||||
"POT-Creation-Date: 2021-05-08 11:34+0100\n"
|
||||
"PO-Revision-Date: 2021-05-08 11:37+0100\n"
|
||||
"PO-Revision-Date: 2022-05-21 16:57+0100\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: tokafondo\n"
|
||||
"Language: es_ES\n"
|
||||
@@ -14,7 +14,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
"X-Generator: Poedit 2.4.3\n"
|
||||
"X-Generator: Poedit 3.0\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: tools/alignments/cli.py:14
|
||||
@@ -180,12 +180,31 @@ msgstr ""
|
||||
msgid "[Extract only] The output size of extracted faces."
|
||||
msgstr "[Sólo extracción] El tamaño de salida de las caras extraídas."
|
||||
|
||||
#: tools/alignments/cli.py:129
|
||||
#: tools/alignments/cli.py:133
|
||||
msgid ""
|
||||
"[Extract only] Only extract faces that have not been upscaled to the "
|
||||
"required size (`-sz`, `--size). Useful for excluding low-res images from a "
|
||||
"training set."
|
||||
"[Extract only] Only extract faces that have been resized by this percent or "
|
||||
"more to meet the specified extract size (`-sz`, `--size`). Useful for "
|
||||
"excluding low-res images from a training set. Set to 0 to extract all faces. "
|
||||
"Eg: For an extract size of 512px, A setting of 50 will only include faces "
|
||||
"that have been resized from 256px or above. Setting to 100 will only extract "
|
||||
"faces that have been resized from 512px or above. A setting of 200 will only "
|
||||
"extract faces that have been downscaled from 1024px or above."
|
||||
msgstr ""
|
||||
"[Sólo extracción] Sólo extraer las caras que son de origen iguales como "
|
||||
"mínimo al tamaño de salida (`-sz`, `--size). Es útil para excluir las "
|
||||
"imágenes de baja resolución de un conjunto de entrenamiento."
|
||||
"[Sólo extracción] Solo extraiga las caras que hayan cambiado de tamaño en "
|
||||
"este porcentaje o más para cumplir con el tamaño de extracción especificado "
|
||||
"(`-sz`, `--size`). Útil para excluir imágenes de baja resolución de un "
|
||||
"conjunto de entrenamiento. Establézcalo en 0 para extraer todas las caras. "
|
||||
"Por ejemplo: para un tamaño de extracto de 512 px, una configuración de 50 "
|
||||
"solo incluirá caras cuyo tamaño haya cambiado de 256 px o más. Si se "
|
||||
"establece en 100, solo se extraerán las caras que se hayan redimensionado "
|
||||
"desde 512 px o más. Una configuración de 200 solo extraerá las caras que se "
|
||||
"han reducido de 1024 px o más."
|
||||
|
||||
#~ msgid ""
|
||||
#~ "[Extract only] Only extract faces that have not been upscaled to the "
|
||||
#~ "required size (`-sz`, `--size). Useful for excluding low-res images from "
|
||||
#~ "a training set."
|
||||
#~ msgstr ""
|
||||
#~ "[Sólo extracción] Sólo extraer las caras que son de origen iguales como "
|
||||
#~ "mínimo al tamaño de salida (`-sz`, `--size). Es útil para excluir las "
|
||||
#~ "imágenes de baja resolución de un conjunto de entrenamiento."
|
||||
|
||||
@@ -102,7 +102,7 @@ msgstr ""
|
||||
msgid "[Extract only] The output size of extracted faces."
|
||||
msgstr ""
|
||||
|
||||
#: tools/alignments/cli.py:129
|
||||
msgid "[Extract only] Only extract faces that have not been upscaled to the required size (`-sz`, `--size). Useful for excluding low-res images from a training set."
|
||||
#: tools/alignments/cli.py:133
|
||||
msgid "[Extract only] Only extract faces that have been resized by this percent or more to meet the specified extract size (`-sz`, `--size`). Useful for excluding low-res images from a training set. Set to 0 to extract all faces. Eg: For an extract size of 512px, A setting of 50 will only include faces that have been resized from 256px or above. Setting to 100 will only extract faces that have been resized from 512px or above. A setting of 200 will only extract faces that have been downscaled from 1024px or above."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class AlignmentsArgs(FaceSwapArgs):
|
||||
frames_and_faces_dir = _(" Must Pass in a frames folder/source video file AND a faces "
|
||||
"folder (-fr and -fc).")
|
||||
output_opts = _(" Use the output option (-o) to process results.")
|
||||
argument_list = list()
|
||||
argument_list = []
|
||||
argument_list.append(dict(
|
||||
opts=("-j", "--job"),
|
||||
action=Radio,
|
||||
@@ -117,16 +117,24 @@ class AlignmentsArgs(FaceSwapArgs):
|
||||
type=int,
|
||||
action=Slider,
|
||||
min_max=(256, 1024),
|
||||
rounding=64,
|
||||
default=512,
|
||||
group=_("extract"),
|
||||
rounding=64,
|
||||
help=_("[Extract only] The output size of extracted faces.")))
|
||||
argument_list.append(dict(
|
||||
opts=("-l", "--large"),
|
||||
action="store_true",
|
||||
opts=("-m", "--min-size"),
|
||||
type=int,
|
||||
action=Slider,
|
||||
min_max=(0, 200),
|
||||
rounding=1,
|
||||
default=0,
|
||||
dest="min_size",
|
||||
group=_("extract"),
|
||||
default=False,
|
||||
help=_("[Extract only] Only extract faces that have not been upscaled to the required "
|
||||
"size (`-sz`, `--size). Useful for excluding low-res images from a training "
|
||||
"set.")))
|
||||
help=_("[Extract only] Only extract faces that have been resized by this percent or "
|
||||
"more to meet the specified extract size (`-sz`, `--size`). Useful for "
|
||||
"excluding low-res images from a training set. Set to 0 to extract all faces. "
|
||||
"Eg: For an extract size of 512px, A setting of 50 will only include faces "
|
||||
"that have been resized from 256px or above. Setting to 100 will only extract "
|
||||
"faces that have been resized from 512px or above. A setting of 200 will only "
|
||||
"extract faces that have been downscaled from 1024px or above.")))
|
||||
return argument_list
|
||||
|
||||
@@ -5,6 +5,7 @@ import logging
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Optional, List
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
@@ -19,6 +20,10 @@ from plugins.extract.pipeline import Extractor, ExtractMedia
|
||||
|
||||
from .media import ExtractedFaces, Faces, Frames
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import argparse
|
||||
from .media import AlignmentData
|
||||
|
||||
logger = logging.getLogger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@@ -91,7 +96,7 @@ class Check():
|
||||
def _compile_output(self):
|
||||
""" Compile list of frames that meet criteria """
|
||||
action = self._job.replace("-", "_")
|
||||
processor = getattr(self, "_get_{}".format(action))
|
||||
processor = getattr(self, f"_get_{action}")
|
||||
logger.debug("Processor: %s", processor)
|
||||
return [item for item in processor()] # pylint:disable=unnecessary-comprehension
|
||||
|
||||
@@ -108,7 +113,7 @@ class Check():
|
||||
def _get_multi_faces(self):
|
||||
""" yield each frame or face that has multiple faces
|
||||
matched in alignments file """
|
||||
process_type = getattr(self, "_get_multi_faces_{}".format(self._type))
|
||||
process_type = getattr(self, f"_get_multi_faces_{self._type}")
|
||||
for item in process_type():
|
||||
yield item
|
||||
|
||||
@@ -170,8 +175,7 @@ class Check():
|
||||
# Strip the index for printed/file output
|
||||
items_output = [item[0] for item in items_output]
|
||||
output_message = "-----------------------------------------------\r\n"
|
||||
output_message += " {} ({})\r\n".format(self.output_message,
|
||||
len(items_output))
|
||||
output_message += f" {self.output_message} ({len(items_output)})\r\n"
|
||||
output_message += "-----------------------------------------------\r\n"
|
||||
output_message += "\r\n".join(items_output)
|
||||
if self._output == "console":
|
||||
@@ -191,31 +195,30 @@ class Check():
|
||||
""" Video name needs to be prefixed to filename if input is a
|
||||
video and processing frames """
|
||||
if self._is_video and self._type == "frames":
|
||||
return "{}_".format(os.path.basename(self._source_dir))
|
||||
return f"{os.path.basename(self._source_dir)}_"
|
||||
return ""
|
||||
|
||||
def output_file(self, output_message, items_discovered):
|
||||
""" Save the output to a text file in the frames directory """
|
||||
now = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
dst_dir = self._get_output_folder()
|
||||
filename = "{}{}_{}.txt".format(self._get_filename_prefix(),
|
||||
self.output_message.replace(" ", "_").lower(),
|
||||
now)
|
||||
filename = (f"{self._get_filename_prefix()}{self.output_message.replace(' ', '_').lower()}"
|
||||
f"_{now}.txt")
|
||||
output_file = os.path.join(dst_dir, filename)
|
||||
logger.info("Saving %s result(s) to '%s'", items_discovered, output_file)
|
||||
with open(output_file, "w") as f_output:
|
||||
with open(output_file, "w", encoding="utf8") as f_output:
|
||||
f_output.write(output_message)
|
||||
|
||||
def _move_file(self, items_output):
|
||||
""" Move the identified frames to a new sub folder """
|
||||
now = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
folder_name = "{}{}_{}".format(self._get_filename_prefix(),
|
||||
self.output_message.replace(" ", "_").lower(), now)
|
||||
folder_name = (f"{self._get_filename_prefix()}"
|
||||
f"{self.output_message.replace(' ','_').lower()}_{now}")
|
||||
dst_dir = self._get_output_folder()
|
||||
output_folder = os.path.join(dst_dir, folder_name)
|
||||
logger.debug("Creating folder: '%s'", output_folder)
|
||||
os.makedirs(output_folder)
|
||||
move = getattr(self, "_move_{}".format(self._type))
|
||||
move = getattr(self, f"_move_{self._type}")
|
||||
logger.debug("Move function: %s", move)
|
||||
move(output_folder, items_output)
|
||||
|
||||
@@ -282,7 +285,7 @@ class Draw(): # pylint:disable=too-few-public-methods
|
||||
|
||||
"""
|
||||
now = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
folder_name = "drawn_landmarks_{}".format(now)
|
||||
folder_name = f"drawn_landmarks_{now}"
|
||||
if self._frames.is_video:
|
||||
dest_folder = os.path.dirname(self._frames.folder)
|
||||
else:
|
||||
@@ -398,13 +401,14 @@ class Extract(): # pylint:disable=too-few-public-methods
|
||||
arguments: :class:`argparse.Namespace`
|
||||
The :mod:`argparse` arguments as passed in from :mod:`tools.py`
|
||||
"""
|
||||
def __init__(self, alignments, arguments):
|
||||
def __init__(self, alignments: "AlignmentData", arguments: "argparse.Namespace") -> None:
|
||||
logger.debug("Initializing %s: (arguments: %s)", self.__class__.__name__, arguments)
|
||||
self._arguments = arguments
|
||||
self._alignments = alignments
|
||||
self._is_legacy = self._alignments.version == 1.0 # pylint:disable=protected-access
|
||||
self._mask_pipeline = None
|
||||
self._faces_dir = arguments.faces_dir
|
||||
self._min_size = self._get_min_size(arguments.size, arguments.min_size)
|
||||
|
||||
self._frames = Frames(arguments.frames_dir, self._get_count())
|
||||
self._extracted_faces = ExtractedFaces(self._frames,
|
||||
@@ -413,7 +417,30 @@ class Extract(): # pylint:disable=too-few-public-methods
|
||||
self._saver = None
|
||||
logger.debug("Initialized %s", self.__class__.__name__)
|
||||
|
||||
def _get_count(self):
|
||||
@classmethod
|
||||
def _get_min_size(cls, extract_size: int, min_size: int) -> int:
|
||||
""" Obtain the minimum size that a face has been resized from to be included as a valid
|
||||
extract.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
extract_size: int
|
||||
The requested size of the extracted images
|
||||
min_size: int
|
||||
The percentage amount that has been supplied for valid faces (as a percentage of
|
||||
extract size)
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The minimum size, in pixels, that a face is resized from to be considered valid
|
||||
"""
|
||||
retval = 0 if min_size == 0 else max(4, int(extract_size * (min_size / 100.)))
|
||||
logger.debug("Extract size: %s, min percentage size: %s, min_size: %s",
|
||||
extract_size, min_size, retval)
|
||||
return retval
|
||||
|
||||
def _get_count(self) -> Optional[int]:
|
||||
""" If the alignments file has been run through the manual tool, then it will hold video
|
||||
meta information, meaning that the count of frames in the alignment file can be relied
|
||||
on to be accurate.
|
||||
@@ -429,16 +456,21 @@ class Extract(): # pylint:disable=too-few-public-methods
|
||||
logger.debug("Frame count from alignments file: (has_meta: %s, %s", has_meta, retval)
|
||||
return retval
|
||||
|
||||
def process(self):
|
||||
def process(self) -> None:
|
||||
""" Run the re-extraction from Alignments file process"""
|
||||
logger.info("[EXTRACT FACES]") # Tidy up cli output
|
||||
self._check_folder()
|
||||
if self._is_legacy:
|
||||
self._legacy_check()
|
||||
self._saver = ImagesSaver(self._faces_dir, as_bytes=True)
|
||||
|
||||
if self._min_size > 0:
|
||||
logger.info("Only selecting faces that have been resized from a minimum resolution "
|
||||
"of %spx", self._min_size)
|
||||
|
||||
self._export_faces()
|
||||
|
||||
def _check_folder(self):
|
||||
def _check_folder(self) -> None:
|
||||
""" Check that the faces folder doesn't pre-exist and create. """
|
||||
err = None
|
||||
if not self._faces_dir:
|
||||
@@ -447,21 +479,21 @@ class Extract(): # pylint:disable=too-few-public-methods
|
||||
logger.debug("Creating folder: '%s'", self._faces_dir)
|
||||
os.makedirs(self._faces_dir)
|
||||
elif os.listdir(self._faces_dir):
|
||||
err = "ERROR: Output faces folder should be empty: '{}'".format(self._faces_dir)
|
||||
err = f"ERROR: Output faces folder should be empty: '{self._faces_dir}'"
|
||||
if err:
|
||||
logger.error(err)
|
||||
sys.exit(0)
|
||||
logger.verbose("Creating output folder at '%s'", self._faces_dir)
|
||||
|
||||
def _legacy_check(self):
|
||||
def _legacy_check(self) -> None:
|
||||
""" Check whether the alignments file was created with the legacy extraction method.
|
||||
|
||||
If so, force user to re-extract all faces if any options have been specified, otherwise
|
||||
raise the appropriate warnings and set the legacy options.
|
||||
"""
|
||||
if self._arguments.large or self._arguments.extract_every_n != 1:
|
||||
if self._min_size > 0 or self._arguments.extract_every_n != 1:
|
||||
logger.warning("This alignments file was generated with the legacy extraction method.")
|
||||
logger.warning("You should run this extraction job, but with 'large' deselected and "
|
||||
logger.warning("You should run this extraction job, but with 'min_size' set to 0 and "
|
||||
"'extract-every-n' set to 1 to update the alignments file.")
|
||||
logger.warning("You can then re-run this extraction job with your chosen options.")
|
||||
sys.exit(0)
|
||||
@@ -482,11 +514,12 @@ class Extract(): # pylint:disable=too-few-public-methods
|
||||
# Update alignments versioning
|
||||
self._alignments._version = _VERSION # pylint:disable=protected-access
|
||||
|
||||
def _export_faces(self):
|
||||
def _export_faces(self) -> None:
|
||||
""" Export the faces to the output folder. """
|
||||
extracted_faces = 0
|
||||
skip_list = self._set_skip_list()
|
||||
count = self._frames.count if skip_list is None else self._frames.count - len(skip_list)
|
||||
|
||||
for filename, image in tqdm(self._frames.stream(skip_list=skip_list),
|
||||
total=count, desc="Saving extracted faces"):
|
||||
frame_name = os.path.basename(filename)
|
||||
@@ -494,11 +527,11 @@ class Extract(): # pylint:disable=too-few-public-methods
|
||||
logger.verbose("Skipping '%s' - Alignments not found", frame_name)
|
||||
continue
|
||||
extracted_faces += self._output_faces(frame_name, image)
|
||||
if self._is_legacy and extracted_faces != 0 and not self._arguments.large:
|
||||
if self._is_legacy and extracted_faces != 0 and self._min_size == 0:
|
||||
self._alignments.save()
|
||||
logger.info("%s face(s) extracted", extracted_faces)
|
||||
|
||||
def _set_skip_list(self):
|
||||
def _set_skip_list(self) -> Optional[List[int]]:
|
||||
""" Set the indices for frames that should be skipped based on the `extract_every_n`
|
||||
command line option.
|
||||
|
||||
@@ -521,7 +554,7 @@ class Extract(): # pylint:disable=too-few-public-methods
|
||||
logger.debug("Adding skip list: %s", skip_list)
|
||||
return skip_list
|
||||
|
||||
def _output_faces(self, filename, image):
|
||||
def _output_faces(self, filename: str, image: np.ndarray) -> int:
|
||||
""" For each frame save out the faces
|
||||
|
||||
Parameters
|
||||
@@ -546,7 +579,7 @@ class Extract(): # pylint:disable=too-few-public-methods
|
||||
faces = self._process_legacy(filename, image, faces)
|
||||
|
||||
for idx, face in enumerate(faces):
|
||||
output = "{}_{}.png".format(frame_name, str(idx))
|
||||
output = f"{frame_name}_{idx}.png"
|
||||
meta = dict(alignments=face.to_png_meta(),
|
||||
source=dict(alignments_version=self._alignments.version,
|
||||
original_filename=output,
|
||||
@@ -555,14 +588,14 @@ class Extract(): # pylint:disable=too-few-public-methods
|
||||
source_is_video=self._frames.is_video,
|
||||
source_frame_dims=image.shape[:2]))
|
||||
self._saver.save(output, encode_image(face.aligned.face, ".png", metadata=meta))
|
||||
if not self._arguments.large and self._is_legacy:
|
||||
if self._min_size == 0 and self._is_legacy:
|
||||
face.thumbnail = generate_thumbnail(face.aligned.face, size=96, quality=60)
|
||||
self._alignments.data[filename]["faces"][idx] = face.to_alignment()
|
||||
face_count += 1
|
||||
self._saver.close()
|
||||
return face_count
|
||||
|
||||
def _select_valid_faces(self, frame, image):
|
||||
def _select_valid_faces(self, frame: str, image: np.ndarray) -> List[DetectedFace]:
|
||||
""" Return the aligned faces from a frame that meet the selection criteria,
|
||||
|
||||
Parameters
|
||||
@@ -578,17 +611,20 @@ class Extract(): # pylint:disable=too-few-public-methods
|
||||
List of valid :class:`lib,align.DetectedFace` objects
|
||||
"""
|
||||
faces = self._extracted_faces.get_faces_in_frame(frame, image=image)
|
||||
if not self._arguments.large:
|
||||
if self._min_size == 0:
|
||||
valid_faces = faces
|
||||
else:
|
||||
sizes = self._extracted_faces.get_roi_size_for_frame(frame)
|
||||
valid_faces = [faces[idx] for idx, size in enumerate(sizes)
|
||||
if size >= self._extracted_faces.size]
|
||||
if size >= self._min_size]
|
||||
logger.trace("frame: '%s', total_faces: %s, valid_faces: %s",
|
||||
frame, len(faces), len(valid_faces))
|
||||
return valid_faces
|
||||
|
||||
def _process_legacy(self, filename, image, detected_faces):
|
||||
def _process_legacy(self,
|
||||
filename: str,
|
||||
image: np.ndarray,
|
||||
detected_faces: List[DetectedFace]) -> List[DetectedFace]:
|
||||
""" Process legacy face extractions to new extraction method.
|
||||
|
||||
Updates stored masks to new extract size
|
||||
@@ -601,6 +637,11 @@ class Extract(): # pylint:disable=too-few-public-methods
|
||||
The current image the contains the faces
|
||||
detected_faces: list
|
||||
list of :class:`lib.align.DetectedFace` objects for the current frame
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
The updated list of :class:`lib.align.DetectedFace` objects for the current frame
|
||||
"""
|
||||
# Update landmarks based masks for face centering
|
||||
mask_item = ExtractMedia(filename, image, detected_faces=detected_faces)
|
||||
@@ -613,7 +654,7 @@ class Extract(): # pylint:disable=too-few-public-methods
|
||||
return faces
|
||||
|
||||
@classmethod
|
||||
def _pad_legacy_masks(cls, detected_face):
|
||||
def _pad_legacy_masks(cls, detected_face: DetectedFace) -> None:
|
||||
""" Recenter legacy Neural Network based masks from legacy centering to face centering
|
||||
and pad accordingly.
|
||||
|
||||
@@ -667,7 +708,7 @@ class RemoveFaces(): # pylint:disable=too-few-public-methods
|
||||
logger.debug("Initializing %s: (arguments: %s)", self.__class__.__name__, arguments)
|
||||
self._alignments = alignments
|
||||
|
||||
kwargs = dict()
|
||||
kwargs = {}
|
||||
if alignments.version < 2.1:
|
||||
# Update headers of faces generated with hash based alignments
|
||||
kwargs["alignments"] = alignments
|
||||
@@ -722,7 +763,7 @@ class RemoveFaces(): # pylint:disable=too-few-public-methods
|
||||
fullpath, face_index, new_index)
|
||||
|
||||
# Update file_list_sorted for rename task
|
||||
orig_filename = "{}_{}.png".format(os.path.splitext(frame)[0], new_index)
|
||||
orig_filename = f"{os.path.splitext(frame)[0]}_{new_index}.png"
|
||||
file_info["face_index"] = new_index
|
||||
file_info["original_filename"] = orig_filename
|
||||
|
||||
@@ -758,7 +799,7 @@ class Rename(): # pylint:disable=too-few-public-methods
|
||||
self.__class__.__name__, arguments, faces)
|
||||
self._alignments = alignments
|
||||
|
||||
kwargs = dict()
|
||||
kwargs = {}
|
||||
if alignments.version < 2.1:
|
||||
# Update headers of faces generated with hash based alignments
|
||||
kwargs["alignments"] = alignments
|
||||
@@ -877,8 +918,8 @@ class Spatial():
|
||||
logger.debug("Initializing %s: (arguments: %s)", self.__class__.__name__, arguments)
|
||||
self.arguments = arguments
|
||||
self._alignments = alignments
|
||||
self.mappings = dict()
|
||||
self.normalized = dict()
|
||||
self.mappings = {}
|
||||
self.normalized = {}
|
||||
self.shapes_model = None
|
||||
logger.debug("Initialized %s", self.__class__.__name__)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user