bugfix: Alignment tool, auto-detect alignments

- Random linting and typing
This commit is contained in:
torzdf 2024-04-19 11:33:52 +01:00
parent d75898f718
commit 2bad105dc8
11 changed files with 258 additions and 210 deletions

View File

@ -579,7 +579,7 @@ def encode_image(image: np.ndarray,
Returns Returns
------- -------
encoded_image: bytes encoded_image: bytes
The image encoded into the correct file format The image encoded into the correct file format as bytes
Example Example
------- -------
@ -591,10 +591,10 @@ def encode_image(image: np.ndarray,
raise ValueError("Metadata is only supported for .png and .tif images") raise ValueError("Metadata is only supported for .png and .tif images")
args = tuple() if encoding_args is None else encoding_args args = tuple() if encoding_args is None else encoding_args
retval = cv2.imencode(extension, image, args)[1] retval = cv2.imencode(extension, image, args)[1].tobytes()
if metadata: if metadata:
func = {".png": png_write_meta, ".tif": tiff_write_meta}[extension] func = {".png": png_write_meta, ".tif": tiff_write_meta}[extension]
retval = func(retval.tobytes(), metadata) # type:ignore[arg-type] retval = func(retval, metadata)
return retval return retval
@ -624,7 +624,7 @@ def png_write_meta(image: bytes, data: PNGHeaderDict | dict[str, T.Any] | bytes)
return retval return retval
def tiff_write_meta(image: bytes, data: dict[str, T.Any] | bytes) -> bytes: def tiff_write_meta(image: bytes, data: PNGHeaderDict | dict[str, T.Any] | bytes) -> bytes:
""" Write Faceswap information to a tiff's image_description field. """ Write Faceswap information to a tiff's image_description field.
Parameters Parameters

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" Processes the augmentation of images for feeding into a Faceswap model. """ """ Processes the augmentation of images for feeding into a Faceswap model. """
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
import logging import logging
import typing as T import typing as T
@ -11,6 +10,7 @@ import numpy as np
from scipy.interpolate import griddata from scipy.interpolate import griddata
from lib.image import batch_convert_color from lib.image import batch_convert_color
from lib.logger import parse_class_init
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
from lib.config import ConfigValueType from lib.config import ConfigValueType
@ -18,49 +18,152 @@ if T.TYPE_CHECKING:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@dataclass class AugConstants: # pylint:disable=too-many-instance-attributes,too-few-public-methods
class AugConstants:
""" Dataclass for holding constants for Image Augmentation. """ Dataclass for holding constants for Image Augmentation.
Parameters Parameters
---------- ----------
clahe_base_contrast: int config: dict[str, ConfigValueType]
The base number for Contrast Limited Adaptive Histogram Equalization The user training configuration options
clahe_chance: float pricessing_size: int:
Probability to perform Contrast Limited Adaptive Histogram Equilization The size of image to augment the data for
clahe_max_size: int batch_size: int
Maximum clahe window size The batch size that augmented data is being prepared for
lab_adjust: np.ndarray
Adjustment amounts for L*A*B augmentation
transform_rotation: int
Rotation range for transformations
transform_zoom: float
Zoom range for transformations
transform_shift: float
Shift range for transformations
warp_maps: :class:`numpy.ndarray`
The stacked (x, y) mappings for image warping
warp_pads: tuple
The padding to apply for image warping
warp_slices: slice
The slices for extracting a warped image
warp_lm_edge_anchors: :class:`numpy.ndarray`
The edge anchors for landmark based warping
warp_lm_grids: :class:`numpy.ndarray`
The grids for landmark based warping
""" """
clahe_base_contrast: int def __init__(self,
clahe_chance: float config: dict[str, ConfigValueType],
clahe_max_size: int processing_size: int,
lab_adjust: np.ndarray batch_size: int) -> None:
transform_rotation: int logger.debug(parse_class_init(locals()))
transform_zoom: float self.clahe_base_contrast: int = 0
transform_shift: float """int: The base number for Contrast Limited Adaptive Histogram Equalization"""
warp_maps: np.ndarray self.clahe_chance: float = 0.0
warp_pad: tuple[int, int] """float: Probability to perform Contrast Limited Adaptive Histogram Equilization"""
warp_slices: slice self.clahe_max_size: int = 0
warp_lm_edge_anchors: np.ndarray """int: Maximum clahe window size"""
warp_lm_grids: np.ndarray
self.lab_adjust: np.ndarray
""":class:`numpy.ndarray`: Adjustment amounts for L*A*B augmentation"""
self.transform_rotation: int = 0
"""int: Rotation range for transformations"""
self.transform_zoom: float = 0.0
"""float: Zoom range for transformations"""
self.transform_shift: float = 0.0
"""float: Shift range for transformations"""
self.warp_maps: np.ndarray
""":class:`numpy.ndarray`The stacked (x, y) mappings for image warping"""
self.warp_pad: tuple[int, int] = (0, 0)
""":tuple[int, int]: The padding to apply for image warping"""
self.warp_slices: slice
""":slice: The slices for extracting a warped image"""
self.warp_lm_edge_anchors: np.ndarray
"""::class:`numpy.ndarray`: The edge anchors for landmark based warping"""
self.warp_lm_grids: np.ndarray
"""::class:`numpy.ndarray`: The grids for landmark based warping"""
self._config = config
self._size = processing_size
self._load_config(batch_size)
logger.debug("Initialized: %s", self.__class__.__name__)
def _load_clahe(self) -> None:
""" Load the CLAHE constants from user config """
color_clahe_chance = self._config.get("color_clahe_chance", 50)
color_clahe_max_size = self._config.get("color_clahe_max_size", 4)
assert isinstance(color_clahe_chance, int)
assert isinstance(color_clahe_max_size, int)
self.clahe_base_contrast = max(2, self._size // 128)
self.clahe_chance = color_clahe_chance / 100
self.clahe_max_size = color_clahe_max_size
logger.debug("clahe_base_contrast: %s, clahe_chance: %s, clahe_max_size: %s",
self.clahe_base_contrast, self.clahe_chance, self.clahe_max_size)
def _load_lab(self) -> None:
""" Load the random L*A*B augmentation constants """
color_lightness = self._config.get("color_lightness", 30)
color_ab = self._config.get("color_ab", 8)
assert isinstance(color_lightness, int)
assert isinstance(color_ab, int)
amount_l = int(color_lightness) / 100
amount_ab = int(color_ab) / 100
self.lab_adjust = np.array([amount_l, amount_ab, amount_ab], dtype="float32")
logger.debug("lab_adjust: %s", self.lab_adjust)
def _load_transform(self) -> None:
""" Load the random transform constants """
shift_range = self._config.get("shift_range", 5)
rotation_range = self._config.get("rotation_range", 10)
zoom_amount = self._config.get("zoom_amount", 5)
assert isinstance(shift_range, int)
assert isinstance(rotation_range, int)
assert isinstance(zoom_amount, int)
self.transform_shift = (shift_range / 100) * self._size
self.transform_rotation = rotation_range
self.transform_zoom = zoom_amount / 100
logger.debug("transform_shift: %s, transform_rotation: %s, transform_zoom: %s",
self.transform_shift, self.transform_rotation, self.transform_zoom)
def _load_warp(self, batch_size: int) -> None:
""" Load the warp augmentation constants
Parameters
----------
batch_size: int
The batch size that augmented data is being prepared for
"""
warp_range = np.linspace(0, self._size, 5, dtype='float32')
warp_mapx = np.broadcast_to(warp_range, (batch_size, 5, 5)).astype("float32")
warp_mapy = np.broadcast_to(warp_mapx[0].T, (batch_size, 5, 5)).astype("float32")
warp_pad = int(1.25 * self._size)
self.warp_maps = np.stack((warp_mapx, warp_mapy), axis=1)
self.warp_pad = (warp_pad, warp_pad)
self.warp_slices = slice(warp_pad // 10, -warp_pad // 10)
logger.debug("warp_maps: (%s, %s), warp_pad: %s, warp_slices: %s",
self.warp_maps.shape, self.warp_maps.dtype,
self.warp_pad, self.warp_slices)
def _load_warp_to_landmarks(self, batch_size: int) -> None:
""" Load the warp-to-landmarks augmentation constants
Parameters
----------
batch_size: int
The batch size that augmented data is being prepared for
"""
p_mx = self._size - 1
p_hf = (self._size // 2) - 1
edge_anchors = np.array([(0, 0), (0, p_mx), (p_mx, p_mx), (p_mx, 0),
(p_hf, 0), (p_hf, p_mx), (p_mx, p_hf), (0, p_hf)]).astype("int32")
edge_anchors = np.broadcast_to(edge_anchors, (batch_size, 8, 2))
grids = np.mgrid[0: p_mx: complex(self._size), # type:ignore[misc]
0: p_mx: complex(self._size)] # type:ignore[misc]
self.warp_lm_edge_anchors = edge_anchors
self.warp_lm_grids = grids
logger.debug("warp_lm_edge_anchors: (%s, %s), warp_lm_grids: (%s, %s)",
self.warp_lm_edge_anchors.shape, self.warp_lm_edge_anchors.dtype,
self.warp_lm_grids.shape, self.warp_lm_grids.dtype)
def _load_config(self, batch_size: int) -> None:
""" Load the constants into the class from user config
Parameters
----------
batch_size: int
The batch size that augmented data is being prepared for
"""
logger.debug("Loading augmentation constants")
self._load_clahe()
self._load_lab()
self._load_transform()
self._load_warp(batch_size)
self._load_warp_to_landmarks(batch_size)
logger.debug("Loaded augmentation constants")
class ImageAugmentation(): class ImageAugmentation():
@ -68,7 +171,7 @@ class ImageAugmentation():
Parameters Parameters
---------- ----------
batchsize: int batch_size: int
The number of images that will be fed through the augmentation functions at once. The number of images that will be fed through the augmentation functions at once.
processing_size: int processing_size: int
The largest input or output size of the model. This is the size that images are processed The largest input or output size of the model. This is the size that images are processed
@ -78,88 +181,25 @@ class ImageAugmentation():
plugin configuration options. plugin configuration options.
""" """
def __init__(self, def __init__(self,
batchsize: int, batch_size: int,
processing_size: int, processing_size: int,
config: dict[str, ConfigValueType]) -> None: config: dict[str, ConfigValueType]) -> None:
logger.debug("Initializing %s: (batchsize: %s, processing_size: %s, " logger.debug(parse_class_init(locals()))
"config: %s)",
self.__class__.__name__, batchsize, processing_size, config)
self._processing_size = processing_size self._processing_size = processing_size
self._batchsize = batchsize self._batch_size = batch_size
self._config = config
# flip_args
flip_chance = config.get("random_flip", 50)
assert isinstance(flip_chance, int)
self._flip_chance = flip_chance
# Warp args # Warp args
self._warp_scale = 5 / 256 * self._processing_size # Normal random variable scale self._warp_scale = 5 / 256 * self._processing_size # Normal random variable scale
self._warp_lm_scale = 2 / 256 * self._processing_size # Normal random variable scale self._warp_lm_scale = 2 / 256 * self._processing_size # Normal random variable scale
self._constants = self._get_constants() self._constants = AugConstants(config, processing_size, batch_size)
logger.debug("Initialized %s", self.__class__.__name__) logger.debug("Initialized %s", self.__class__.__name__)
def _get_constants(self) -> AugConstants:
""" Initializes the caching of constants for use in various image augmentations.
Returns
-------
dict
Cached constants that are used for various augmentations
"""
logger.debug("Initializing constants.")
# Config variables typing check
shift_range = self._config.get("shift_range", 5)
color_lightness = self._config.get("color_lightness", 30)
color_ab = self._config.get("color_ab", 8)
color_clahe_chance = self._config.get("color_clahe_chance", 50)
color_clahe_max_size = self._config.get("color_clahe_max_size", 4)
rotation_range = self._config.get("rotation_range", 10)
zoom_amount = self._config.get("zoom_amount", 5)
assert isinstance(shift_range, int)
assert isinstance(color_lightness, int)
assert isinstance(color_ab, int)
assert isinstance(color_clahe_chance, int)
assert isinstance(color_clahe_max_size, int)
assert isinstance(rotation_range, int)
assert isinstance(zoom_amount, int)
# Transform
tform_shift = (shift_range / 100) * self._processing_size
# Color Aug
amount_l = int(color_lightness) / 100
amount_ab = int(color_ab) / 100
lab_adjust = np.array([amount_l, amount_ab, amount_ab], dtype="float32")
# Random Warp
warp_range = np.linspace(0, self._processing_size, 5, dtype='float32')
warp_mapx = np.broadcast_to(warp_range, (self._batchsize, 5, 5)).astype("float32")
warp_mapy = np.broadcast_to(warp_mapx[0].T, (self._batchsize, 5, 5)).astype("float32")
warp_pad = int(1.25 * self._processing_size)
# Random Warp Landmarks
p_mx = self._processing_size - 1
p_hf = (self._processing_size // 2) - 1
edge_anchors = np.array([(0, 0), (0, p_mx), (p_mx, p_mx), (p_mx, 0),
(p_hf, 0), (p_hf, p_mx), (p_mx, p_hf), (0, p_hf)]).astype("int32")
edge_anchors = np.broadcast_to(edge_anchors, (self._batchsize, 8, 2))
grids = np.mgrid[0: p_mx: complex(self._processing_size), # type: ignore
0: p_mx: complex(self._processing_size)] # type: ignore
retval = AugConstants(clahe_base_contrast=max(2, self._processing_size // 128),
clahe_chance=color_clahe_chance / 100,
clahe_max_size=color_clahe_max_size,
lab_adjust=lab_adjust,
transform_rotation=rotation_range,
transform_zoom=zoom_amount / 100,
transform_shift=tform_shift,
warp_maps=np.stack((warp_mapx, warp_mapy), axis=1),
warp_pad=(warp_pad, warp_pad),
warp_slices=slice(warp_pad // 10, -warp_pad // 10),
warp_lm_edge_anchors=edge_anchors,
warp_lm_grids=grids)
logger.debug("Initialized constants: %s", retval)
return retval
# <<< COLOR AUGMENTATION >>> # # <<< COLOR AUGMENTATION >>> #
def color_adjust(self, batch: np.ndarray) -> np.ndarray: def color_adjust(self, batch: np.ndarray) -> np.ndarray:
""" Perform color augmentation on the passed in batch. """ Perform color augmentation on the passed in batch.
@ -178,7 +218,7 @@ class ImageAugmentation():
A 4-dimensional array of the same shape as :attr:`batch` with color augmentation A 4-dimensional array of the same shape as :attr:`batch` with color augmentation
applied. applied.
""" """
logger.trace("Augmenting color") # type: ignore logger.trace("Augmenting color") # type:ignore[attr-defined]
batch = batch_convert_color(batch, "BGR2LAB") batch = batch_convert_color(batch, "BGR2LAB")
self._random_lab(batch) self._random_lab(batch)
self._random_clahe(batch) self._random_clahe(batch)
@ -190,7 +230,7 @@ class ImageAugmentation():
a batch of images """ a batch of images """
base_contrast = self._constants.clahe_base_contrast base_contrast = self._constants.clahe_base_contrast
batch_random = np.random.rand(self._batchsize) batch_random = np.random.rand(self._batch_size)
indices = np.where(batch_random < self._constants.clahe_chance)[0] indices = np.where(batch_random < self._constants.clahe_chance)[0]
if not np.any(indices): if not np.any(indices):
return return
@ -198,9 +238,9 @@ class ImageAugmentation():
size=indices.shape[0], size=indices.shape[0],
dtype="uint8") dtype="uint8")
grid_sizes = (grid_bases * (base_contrast // 2)) + base_contrast grid_sizes = (grid_bases * (base_contrast // 2)) + base_contrast
logger.trace("Adjusting Contrast. Grid Sizes: %s", grid_sizes) # type: ignore logger.trace("Adjusting Contrast. Grid Sizes: %s", grid_sizes) # type:ignore[attr-defined]
clahes = [cv2.createCLAHE(clipLimit=2.0, # pylint:disable=no-member clahes = [cv2.createCLAHE(clipLimit=2.0,
tileGridSize=(grid_size, grid_size)) tileGridSize=(grid_size, grid_size))
for grid_size in grid_sizes] for grid_size in grid_sizes]
@ -212,8 +252,8 @@ class ImageAugmentation():
images """ images """
randoms = np.random.uniform(-self._constants.lab_adjust, randoms = np.random.uniform(-self._constants.lab_adjust,
self._constants.lab_adjust, self._constants.lab_adjust,
size=(self._batchsize, 1, 1, 3)).astype("float32") size=(self._batch_size, 1, 1, 3)).astype("float32")
logger.trace("Random LAB adjustments: %s", randoms) # type: ignore logger.trace("Random LAB adjustments: %s", randoms) # type:ignore[attr-defined]
# Iterating through the images and channels is much faster than numpy.where and slightly # Iterating through the images and channels is much faster than numpy.where and slightly
# faster than numexpr.where. # faster than numexpr.where.
for image, rand in zip(batch, randoms): for image, rand in zip(batch, randoms):
@ -236,18 +276,18 @@ class ImageAugmentation():
The batch should be a 4-dimensional array of shape (`batchsize`, `height`, `width`, The batch should be a 4-dimensional array of shape (`batchsize`, `height`, `width`,
`channels`) and in `BGR` format. `channels`) and in `BGR` format.
""" """
logger.trace("Randomly transforming image") # type: ignore logger.trace("Randomly transforming image") # type:ignore[attr-defined]
rotation = np.random.uniform(-self._constants.transform_rotation, rotation = np.random.uniform(-self._constants.transform_rotation,
self._constants.transform_rotation, self._constants.transform_rotation,
size=self._batchsize).astype("float32") size=self._batch_size).astype("float32")
scale = np.random.uniform(1 - self._constants.transform_zoom, scale = np.random.uniform(1 - self._constants.transform_zoom,
1 + self._constants.transform_zoom, 1 + self._constants.transform_zoom,
size=self._batchsize).astype("float32") size=self._batch_size).astype("float32")
tform = np.random.uniform(-self._constants.transform_shift, tform = np.random.uniform(-self._constants.transform_shift,
self._constants.transform_shift, self._constants.transform_shift,
size=(self._batchsize, 2)).astype("float32") size=(self._batch_size, 2)).astype("float32")
mats = np.array( mats = np.array(
[cv2.getRotationMatrix2D((self._processing_size // 2, self._processing_size // 2), [cv2.getRotationMatrix2D((self._processing_size // 2, self._processing_size // 2),
rot, rot,
@ -262,7 +302,7 @@ class ImageAugmentation():
dst=image, dst=image,
borderMode=cv2.BORDER_REPLICATE) borderMode=cv2.BORDER_REPLICATE)
logger.trace("Randomly transformed image") # type: ignore logger.trace("Randomly transformed image") # type:ignore[attr-defined]
def random_flip(self, batch: np.ndarray): def random_flip(self, batch: np.ndarray):
""" Perform random horizontal flipping on the passed in batch. """ Perform random horizontal flipping on the passed in batch.
@ -275,14 +315,12 @@ class ImageAugmentation():
The batch should be a 4-dimensional array of shape (`batchsize`, `height`, `width`, The batch should be a 4-dimensional array of shape (`batchsize`, `height`, `width`,
`channels`) and in `BGR` format. `channels`) and in `BGR` format.
""" """
logger.trace("Randomly flipping image") # type: ignore logger.trace("Randomly flipping image") # type:ignore[attr-defined]
randoms = np.random.rand(self._batchsize) randoms = np.random.rand(self._batch_size)
flip_chance = self._config.get("random_flip", 50) indices = np.where(randoms <= self._flip_chance / 100)[0]
assert isinstance(flip_chance, int)
indices = np.where(randoms > flip_chance / 100)[0]
batch[indices] = batch[indices, :, ::-1] batch[indices] = batch[indices, :, ::-1]
logger.trace("Randomly flipped %s images of %s", # type: ignore logger.trace("Randomly flipped %s images of %s", # type:ignore[attr-defined]
len(indices), self._batchsize) len(indices), self._batch_size)
def warp(self, batch: np.ndarray, to_landmarks: bool = False, **kwargs) -> np.ndarray: def warp(self, batch: np.ndarray, to_landmarks: bool = False, **kwargs) -> np.ndarray:
""" Perform random warping on the passed in batch by one of two methods. """ Perform random warping on the passed in batch by one of two methods.
@ -329,9 +367,9 @@ class ImageAugmentation():
:class:`numpy.ndarray` :class:`numpy.ndarray`
A 4-dimensional array of the same shape as :attr:`batch` with warping applied. A 4-dimensional array of the same shape as :attr:`batch` with warping applied.
""" """
logger.trace("Randomly warping batch") # type: ignore logger.trace("Randomly warping batch") # type:ignore[attr-defined]
slices = self._constants.warp_slices slices = self._constants.warp_slices
rands = np.random.normal(size=(self._batchsize, 2, 5, 5), rands = np.random.normal(size=(self._batch_size, 2, 5, 5),
scale=self._warp_scale).astype("float32") scale=self._warp_scale).astype("float32")
batch_maps = ne.evaluate("m + r", local_dict={"m": self._constants.warp_maps, "r": rands}) batch_maps = ne.evaluate("m + r", local_dict={"m": self._constants.warp_maps, "r": rands})
batch_interp = np.array([[cv2.resize(map_, self._constants.warp_pad)[slices, slices] batch_interp = np.array([[cv2.resize(map_, self._constants.warp_pad)[slices, slices]
@ -340,7 +378,7 @@ class ImageAugmentation():
warped_batch = np.array([cv2.remap(image, interp[0], interp[1], cv2.INTER_LINEAR) warped_batch = np.array([cv2.remap(image, interp[0], interp[1], cv2.INTER_LINEAR)
for image, interp in zip(batch, batch_interp)]) for image, interp in zip(batch, batch_interp)])
logger.trace("Warped image shape: %s", warped_batch.shape) # type: ignore logger.trace("Warped image shape: %s", warped_batch.shape) # type:ignore[attr-defined]
return warped_batch return warped_batch
def _random_warp_landmarks(self, def _random_warp_landmarks(self,
@ -364,7 +402,7 @@ class ImageAugmentation():
:class:`numpy.ndarray` :class:`numpy.ndarray`
A 4-dimensional array of the same shape as :attr:`batch` with warping applied. A 4-dimensional array of the same shape as :attr:`batch` with warping applied.
""" """
logger.trace("Randomly warping landmarks") # type: ignore logger.trace("Randomly warping landmarks") # type:ignore[attr-defined]
edge_anchors = self._constants.warp_lm_edge_anchors edge_anchors = self._constants.warp_lm_edge_anchors
grids = self._constants.warp_lm_grids grids = self._constants.warp_lm_grids
@ -389,15 +427,16 @@ class ImageAugmentation():
grid_z = np.array([griddata(dst, src, (grids[0], grids[1]), method="linear") grid_z = np.array([griddata(dst, src, (grids[0], grids[1]), method="linear")
for src, dst in zip(lbatch_src, lbatch_dst)]) for src, dst in zip(lbatch_src, lbatch_dst)])
maps = grid_z.reshape((self._batchsize, maps = grid_z.reshape((self._batch_size,
self._processing_size, self._processing_size,
self._processing_size, self._processing_size,
2)).astype("float32") 2)).astype("float32")
warped_batch = np.array([cv2.remap(image, warped_batch = np.array([cv2.remap(image,
map_[..., 1], map_[..., 1],
map_[..., 0], map_[..., 0],
cv2.INTER_LINEAR, cv2.INTER_LINEAR,
cv2.BORDER_TRANSPARENT) borderMode=cv2.BORDER_TRANSPARENT)
for image, map_ in zip(batch, maps)]) for image, map_ in zip(batch, maps)])
logger.trace("Warped batch shape: %s", warped_batch.shape) # type: ignore logger.trace("Warped batch shape: %s", warped_batch.shape) # type:ignore[attr-defined]
return warped_batch return warped_batch

View File

@ -57,7 +57,7 @@ class DataGenerator():
side: T.Literal["a", "b"], side: T.Literal["a", "b"],
images: list[str], images: list[str],
batch_size: int) -> None: batch_size: int) -> None:
logger.debug("Initializing %s: (model: %s, side: %s, images: %s , " # type: ignore logger.debug("Initializing %s: (model: %s, side: %s, images: %s , "
"batch_size: %s, config: %s)", self.__class__.__name__, model.name, side, "batch_size: %s, config: %s)", self.__class__.__name__, model.name, side,
len(images), batch_size, config) len(images), batch_size, config)
self._config = config self._config = config
@ -243,8 +243,9 @@ class DataGenerator():
raw_faces = read_image_batch(filenames) raw_faces = read_image_batch(filenames)
detected_faces = self._face_cache.get_items(filenames) detected_faces = self._face_cache.get_items(filenames)
logger.trace("filenames: %s, raw_faces: '%s', detected_faces: %s", # type: ignore logger.trace( # type:ignore[attr-defined]
filenames, raw_faces.shape, len(detected_faces)) "filenames: %s, raw_faces: '%s', detected_faces: %s",
filenames, raw_faces.shape, len(detected_faces))
return raw_faces, detected_faces return raw_faces, detected_faces
def _crop_to_coverage(self, def _crop_to_coverage(self,
@ -271,8 +272,8 @@ class DataGenerator():
batch: :class:`np.ndarray` batch: :class:`np.ndarray`
The pre-allocated array to hold this batch The pre-allocated array to hold this batch
""" """
logger.trace("Cropping training images info: (filenames: %s, side: '%s')", # type: ignore logger.trace( # type:ignore[attr-defined]
filenames, self._side) "Cropping training images info: (filenames: %s, side: '%s')", filenames, self._side)
with futures.ThreadPoolExecutor() as executor: with futures.ThreadPoolExecutor() as executor:
proc = {executor.submit(face.aligned.extract_face, img): idx proc = {executor.submit(face.aligned.extract_face, img): idx
@ -304,7 +305,7 @@ class DataGenerator():
masks = np.array([face.get_training_masks() for face in detected_faces]) masks = np.array([face.get_training_masks() for face in detected_faces])
batch[..., 3:] = masks batch[..., 3:] = masks
logger.trace("side: %s, masks: %s, batch: %s", # type: ignore logger.trace("side: %s, masks: %s, batch: %s", # type:ignore[attr-defined]
self._side, masks.shape, batch.shape) self._side, masks.shape, batch.shape)
def _process_batch(self, filenames: list[str]) -> BatchType: def _process_batch(self, filenames: list[str]) -> BatchType:
@ -333,9 +334,9 @@ class DataGenerator():
self._apply_mask(detected_faces, batch) self._apply_mask(detected_faces, batch)
feed, targets = self.process_batch(filenames, raw_faces, detected_faces, batch) feed, targets = self.process_batch(filenames, raw_faces, detected_faces, batch)
logger.trace("Processed %s batch side %s. (filenames: %s, feed: %s, " # type: ignore logger.trace( # type:ignore[attr-defined]
"targets: %s)", self.__class__.__name__, self._side, filenames, "Processed %s batch side %s. (filenames: %s, feed: %s, targets: %s)",
feed.shape, [t.shape for t in targets]) self.__class__.__name__, self._side, filenames, feed.shape, [t.shape for t in targets])
return feed, targets return feed, targets
@ -450,15 +451,19 @@ class TrainingDataGenerator(DataGenerator):
List of 4-dimensional target images, at all model output sizes, with masks compiled List of 4-dimensional target images, at all model output sizes, with masks compiled
into channels 4+ for each output size into channels 4+ for each output size
""" """
logger.trace("Compiling targets: batch shape: %s", batch.shape) # type: ignore logger.trace("Compiling targets: batch shape: %s", # type:ignore[attr-defined]
batch.shape)
if len(self._output_sizes) == 1 and self._output_sizes[0] == self._process_size: if len(self._output_sizes) == 1 and self._output_sizes[0] == self._process_size:
# Rolling buffer here makes next to no difference, so just create array on the fly # Rolling buffer here makes next to no difference, so just create array on the fly
retval = [self._to_float32(batch)] retval = [self._to_float32(batch)]
else: else:
retval = [self._to_float32(np.array([cv2.resize(image, (size, size), cv2.INTER_AREA) retval = [self._to_float32(np.array([cv2.resize(image,
(size, size),
interpolation=cv2.INTER_AREA)
for image in batch])) for image in batch]))
for size in self._output_sizes] for size in self._output_sizes]
logger.trace("Processed targets: %s", [t.shape for t in retval]) # type: ignore logger.trace("Processed targets: %s", # type:ignore[attr-defined]
[t.shape for t in retval])
return retval return retval
def process_batch(self, def process_batch(self,
@ -533,7 +538,7 @@ class TrainingDataGenerator(DataGenerator):
feed = self._to_float32(np.array([cv2.resize(image, feed = self._to_float32(np.array([cv2.resize(image,
(self._model_input_size, (self._model_input_size,
self._model_input_size), self._model_input_size),
cv2.INTER_AREA) interpolation=cv2.INTER_AREA)
for image in warped])) for image in warped]))
else: else:
feed = self._to_float32(warped) feed = self._to_float32(warped)
@ -556,8 +561,9 @@ class TrainingDataGenerator(DataGenerator):
:class:`np.ndarray` :class:`np.ndarray`
Randomly selected closest matches from the other side's landmarks Randomly selected closest matches from the other side's landmarks
""" """
logger.trace("Retrieving closest matched landmarks: (filenames: '%s', " # type: ignore logger.trace( # type:ignore[attr-defined]
"src_points: '%s')", filenames, batch_src_points) "Retrieving closest matched landmarks: (filenames: '%s', src_points: '%s')",
filenames, batch_src_points)
lm_side: T.Literal["a", "b"] = "a" if self._side == "b" else "b" lm_side: T.Literal["a", "b"] = "a" if self._side == "b" else "b"
other_cache = get_cache(lm_side) other_cache = get_cache(lm_side)
landmarks = other_cache.aligned_landmarks landmarks = other_cache.aligned_landmarks
@ -575,7 +581,8 @@ class TrainingDataGenerator(DataGenerator):
closest_matches = self._cache_closest_matches(filenames, batch_src_points, landmarks) closest_matches = self._cache_closest_matches(filenames, batch_src_points, landmarks)
batch_dst_points = np.array([landmarks[choice(fname)] for fname in closest_matches]) batch_dst_points = np.array([landmarks[choice(fname)] for fname in closest_matches])
logger.trace("Returning: (batch_dst_points: %s)", batch_dst_points.shape) # type: ignore logger.trace("Returning: (batch_dst_points: %s)", # type:ignore[attr-defined]
batch_dst_points.shape)
return batch_dst_points return batch_dst_points
def _cache_closest_matches(self, def _cache_closest_matches(self,
@ -648,8 +655,9 @@ class PreviewDataGenerator(DataGenerator):
list list
List of 4-dimensional target images, at final model output size List of 4-dimensional target images, at final model output size
""" """
logger.trace("Compiling samples: images shape: %s, detected_faces: %s ", # type: ignore logger.trace( # type:ignore[attr-defined]
images.shape, len(detected_faces)) "Compiling samples: images shape: %s, detected_faces: %s ",
images.shape, len(detected_faces))
output_size = self._output_sizes[-1] output_size = self._output_sizes[-1]
full_size = 2 * int(np.rint((output_size / self._coverage_ratio) / 2)) full_size = 2 * int(np.rint((output_size / self._coverage_ratio) / 2))
@ -665,7 +673,7 @@ class PreviewDataGenerator(DataGenerator):
is_aligned=True).face is_aligned=True).face
for idx, face in enumerate(detected_faces)])) for idx, face in enumerate(detected_faces)]))
logger.trace("Processed samples: %s", retval.shape) # type: ignore logger.trace("Processed samples: %s", retval.shape) # type:ignore[attr-defined]
return [retval] return [retval]
def process_batch(self, def process_batch(self,
@ -840,8 +848,9 @@ class Feeder():
side_feed, side_targets = next(self._feeds[side]) side_feed, side_targets = next(self._feeds[side])
if self._model.config["learn_mask"]: # Add the face mask as it's own target if self._model.config["learn_mask"]: # Add the face mask as it's own target
side_targets += [side_targets[-1][..., 3][..., None]] side_targets += [side_targets[-1][..., 3][..., None]]
logger.trace("side: %s, input_shapes: %s, target_shapes: %s", # type: ignore logger.trace( # type:ignore[attr-defined]
side, side_feed.shape, [i.shape for i in side_targets]) "side: %s, input_shapes: %s, target_shapes: %s",
side, side_feed.shape, [i.shape for i in side_targets])
model_inputs.append([side_feed]) model_inputs.append([side_feed])
model_targets.append(side_targets) model_targets.append(side_targets)

View File

@ -6,8 +6,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: faceswap.spanish\n" "Project-Id-Version: faceswap.spanish\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-12 12:10+0100\n" "POT-Creation-Date: 2024-04-19 11:28+0100\n"
"PO-Revision-Date: 2024-04-12 12:14+0100\n" "PO-Revision-Date: 2024-04-19 11:29+0100\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: tokafondo\n" "Language-Team: tokafondo\n"
"Language: es_ES\n" "Language: es_ES\n"
@ -37,29 +37,29 @@ msgstr ""
"archivo de alineación." "archivo de alineación."
#: tools/alignments/cli.py:43 #: tools/alignments/cli.py:43
msgid " Must Pass in a frames folder/source video file (-fr)." msgid " Must Pass in a frames folder/source video file (-r)."
msgstr "" msgstr ""
" Debe indicar una carpeta de fotogramas o archivo de vídeo de origen (-fr)." " Debe indicar una carpeta de fotogramas o archivo de vídeo de origen (-r)."
#: tools/alignments/cli.py:44 #: tools/alignments/cli.py:44
msgid " Must Pass in a faces folder (-fc)." msgid " Must Pass in a faces folder (-c)."
msgstr " Debe indicar una carpeta de caras (-fc)." msgstr " Debe indicar una carpeta de caras (-c)."
#: tools/alignments/cli.py:45 #: tools/alignments/cli.py:45
msgid "" msgid ""
" Must Pass in either a frames folder/source video file OR a faces folder (-" " Must Pass in either a frames folder/source video file OR a faces folder (-r "
"fr or -fc)." "or -c)."
msgstr "" msgstr ""
" Debe indicar una carpeta de fotogramas o archivo de vídeo de origen, o una " " Debe indicar una carpeta de fotogramas o archivo de vídeo de origen, o una "
"carpeta de caras (-fr o -fc)." "carpeta de caras (-r o -c)."
#: tools/alignments/cli.py:47 #: tools/alignments/cli.py:47
msgid "" msgid ""
" Must Pass in a frames folder/source video file AND a faces folder (-fr and -" " Must Pass in a frames folder/source video file AND a faces folder (-r and -"
"fc)." "c)."
msgstr "" msgstr ""
" Debe indicar una carpeta de fotogramas o archivo de vídeo de origen, y una " " Debe indicar una carpeta de fotogramas o archivo de vídeo de origen, y una "
"carpeta de caras (-fr y -fc)." "carpeta de caras (-r y -c)."
#: tools/alignments/cli.py:49 #: tools/alignments/cli.py:49
msgid " Use the output option (-o) to process results." msgid " Use the output option (-o) to process results."

View File

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-12 12:10+0100\n" "POT-Creation-Date: 2024-04-19 11:28+0100\n"
"PO-Revision-Date: 2024-04-12 12:17+0100\n" "PO-Revision-Date: 2024-04-19 11:30+0100\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: ko_KR\n" "Language: ko_KR\n"
@ -35,29 +35,29 @@ msgstr ""
"용하거나 여러 작업을 수행할 수 있습니다." "용하거나 여러 작업을 수행할 수 있습니다."
#: tools/alignments/cli.py:43 #: tools/alignments/cli.py:43
msgid " Must Pass in a frames folder/source video file (-fr)." msgid " Must Pass in a frames folder/source video file (-r)."
msgstr "" msgstr ""
" 프레임들이 저장된 폴더나 원본 비디오 파일을 무조건 전달해야 합니다 (-fr)." " 프레임들이 저장된 폴더나 원본 비디오 파일을 무조건 전달해야 합니다 (-r)."
#: tools/alignments/cli.py:44 #: tools/alignments/cli.py:44
msgid " Must Pass in a faces folder (-fc)." msgid " Must Pass in a faces folder (-c)."
msgstr " 얼굴 폴더를 무조건 전달해야 합니다 (-fc)." msgstr " 얼굴 폴더를 무조건 전달해야 합니다 (-c)."
#: tools/alignments/cli.py:45 #: tools/alignments/cli.py:45
msgid "" msgid ""
" Must Pass in either a frames folder/source video file OR a faces folder (-" " Must Pass in either a frames folder/source video file OR a faces folder (-r "
"fr or -fc)." "or -c)."
msgstr "" msgstr ""
" 프레임 폴더나 원본 비디오 파일 또는 얼굴 폴더중 하나를 무조건 전달해야 합니" " 프레임 폴더나 원본 비디오 파일 또는 얼굴 폴더중 하나를 무조건 전달해야 합니"
"다 (-fr and -fc)." "다 (-r and -c)."
#: tools/alignments/cli.py:47 #: tools/alignments/cli.py:47
msgid "" msgid ""
" Must Pass in a frames folder/source video file AND a faces folder (-fr and -" " Must Pass in a frames folder/source video file AND a faces folder (-r and -"
"fc)." "c)."
msgstr "" msgstr ""
" 프레임 폴더나 원본 비디오 파일 그리고 얼굴 폴더를 무조건 전달해야 합니다 (-" " 프레임 폴더나 원본 비디오 파일 그리고 얼굴 폴더를 무조건 전달해야 합니다 (-"
"fr and -fc)." "r and -c)."
#: tools/alignments/cli.py:49 #: tools/alignments/cli.py:49
msgid " Use the output option (-o) to process results." msgid " Use the output option (-o) to process results."

View File

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-12 12:10+0100\n" "POT-Creation-Date: 2024-04-19 11:28+0100\n"
"PO-Revision-Date: 2024-04-12 12:13+0100\n" "PO-Revision-Date: 2024-04-19 11:31+0100\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: ru\n" "Language: ru\n"
@ -38,28 +38,28 @@ msgstr ""
"кадров." "кадров."
#: tools/alignments/cli.py:43 #: tools/alignments/cli.py:43
msgid " Must Pass in a frames folder/source video file (-fr)." msgid " Must Pass in a frames folder/source video file (-r)."
msgstr " Должен проходить в папке с кадрами/исходным видеофайлом (-fr)." msgstr " Должен проходить в папке с кадрами/исходным видеофайлом (-r)."
#: tools/alignments/cli.py:44 #: tools/alignments/cli.py:44
msgid " Must Pass in a faces folder (-fc)." msgid " Must Pass in a faces folder (-c)."
msgstr " Должен проходить в папке с лицами (-fc)." msgstr " Должен проходить в папке с лицами (-c)."
#: tools/alignments/cli.py:45 #: tools/alignments/cli.py:45
msgid "" msgid ""
" Must Pass in either a frames folder/source video file OR a faces folder (-" " Must Pass in either a frames folder/source video file OR a faces folder (-r "
"fr or -fc)." "or -c)."
msgstr "" msgstr ""
" Должно передаваться либо в папку с кадрами/исходным видеофайлом, либо в " " Должно передаваться либо в папку с кадрами/исходным видеофайлом, либо в "
"папку с лицами (-fr или -fc)." "папку с лицами (-r или -c)."
#: tools/alignments/cli.py:47 #: tools/alignments/cli.py:47
msgid "" msgid ""
" Must Pass in a frames folder/source video file AND a faces folder (-fr and -" " Must Pass in a frames folder/source video file AND a faces folder (-r and -"
"fc)." "c)."
msgstr "" msgstr ""
" Должно передаваться либо в папку с кадрами/исходным видеофайлом И в папку с " " Должно передаваться либо в папку с кадрами/исходным видеофайлом И в папку с "
"лицами (-fr и -fc)." "лицами (-r и -c)."
#: tools/alignments/cli.py:49 #: tools/alignments/cli.py:49
msgid " Use the output option (-o) to process results." msgid " Use the output option (-o) to process results."

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-12 12:10+0100\n" "POT-Creation-Date: 2024-04-19 11:28+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -30,23 +30,23 @@ msgid ""
msgstr "" msgstr ""
#: tools/alignments/cli.py:43 #: tools/alignments/cli.py:43
msgid " Must Pass in a frames folder/source video file (-fr)." msgid " Must Pass in a frames folder/source video file (-r)."
msgstr "" msgstr ""
#: tools/alignments/cli.py:44 #: tools/alignments/cli.py:44
msgid " Must Pass in a faces folder (-fc)." msgid " Must Pass in a faces folder (-c)."
msgstr "" msgstr ""
#: tools/alignments/cli.py:45 #: tools/alignments/cli.py:45
msgid "" msgid ""
" Must Pass in either a frames folder/source video file OR a faces folder (-" " Must Pass in either a frames folder/source video file OR a faces folder (-r "
"fr or -fc)." "or -c)."
msgstr "" msgstr ""
#: tools/alignments/cli.py:47 #: tools/alignments/cli.py:47
msgid "" msgid ""
" Must Pass in a frames folder/source video file AND a faces folder (-fr and -" " Must Pass in a frames folder/source video file AND a faces folder (-r and -"
"fc)." "c)."
msgstr "" msgstr ""
#: tools/alignments/cli.py:49 #: tools/alignments/cli.py:49

View File

@ -40,12 +40,12 @@ class AlignmentsArgs(FaceSwapArgs):
dict dict
The argparse command line options for processing by argparse The argparse command line options for processing by argparse
""" """
frames_dir = _(" Must Pass in a frames folder/source video file (-fr).") frames_dir = _(" Must Pass in a frames folder/source video file (-r).")
faces_dir = _(" Must Pass in a faces folder (-fc).") faces_dir = _(" Must Pass in a faces folder (-c).")
frames_or_faces_dir = _(" Must Pass in either a frames folder/source video file OR a " frames_or_faces_dir = _(" Must Pass in either a frames folder/source video file OR a "
"faces folder (-fr or -fc).") "faces folder (-r or -c).")
frames_and_faces_dir = _(" Must Pass in a frames folder/source video file AND a faces " frames_and_faces_dir = _(" Must Pass in a frames folder/source video file AND a faces "
"folder (-fr and -fc).") "folder (-r and -c).")
output_opts = _(" Use the output option (-o) to process results.") output_opts = _(" Use the output option (-o) to process results.")
argument_list = [] argument_list = []
argument_list.append({ argument_list.append({
@ -118,7 +118,7 @@ class AlignmentsArgs(FaceSwapArgs):
"group": _("data"), "group": _("data"),
# hacky solution to not require alignments file if creating alignments from faces: # hacky solution to not require alignments file if creating alignments from faces:
"required": not any(val in sys.argv for val in ["from-faces", "required": not any(val in sys.argv for val in ["from-faces",
"-fr", "-r",
"-frames_folder"]), "-frames_folder"]),
"filetypes": "alignments", "filetypes": "alignments",
"help": _( "help": _(