From 45028244812de1052ab58ef318a070b62b30a9d5 Mon Sep 17 00:00:00 2001 From: torzdf <36920800+torzdf@users.noreply.github.com> Date: Tue, 26 Mar 2024 18:06:48 +0000 Subject: [PATCH] Add mask dilation/erosion option for training --- lib/align/detected_face.py | 169 +++++++++++------- lib/training/cache.py | 4 +- locales/plugins.train._config.pot | 20 ++- .../ru/LC_MESSAGES/plugins.train._config.mo | Bin 59441 -> 59901 bytes .../ru/LC_MESSAGES/plugins.train._config.po | 26 ++- plugins/train/_config.py | 15 ++ 6 files changed, 156 insertions(+), 78 deletions(-) diff --git a/lib/align/detected_face.py b/lib/align/detected_face.py index c1a7560350..92e17d1dad 100644 --- a/lib/align/detected_face.py +++ b/lib/align/detected_face.py @@ -88,8 +88,8 @@ def __init__(self, landmarks_xy: np.ndarray | None = None, mask: dict[str, "Mask"] | None = None, filename: str | None = None) -> None: - logger.trace("Initializing %s: (image: %s, left: %s, width: %s, top: %s, " # type: ignore - "height: %s, landmarks_xy: %s, mask: %s, filename: %s)", + logger.trace("Initializing %s: (image: %s, left: %s, " # type:ignore[attr-defined] + "width: %s, top: %s, height: %s, landmarks_xy: %s, mask: %s, filename: %s)", self.__class__.__name__, image.shape if image is not None and image.any() else image, left, width, top, height, landmarks_xy, mask, filename) @@ -105,7 +105,7 @@ def __init__(self, self._training_masks: tuple[bytes, tuple[int, int, int]] | None = None self._aligned: AlignedFace | None = None - logger.trace("Initialized %s", self.__class__.__name__) # type: ignore + logger.trace("Initialized %s", self.__class__.__name__) # type:ignore[attr-defined] @property def aligned(self) -> AlignedFace: @@ -167,7 +167,7 @@ def add_mask(self, The centering to store the mask at. One of `"legacy"`, `"face"`, `"head"`. Default: `"face"` """ - logger.trace("name: '%s', mask shape: %s, affine_matrix: %s, " # type: ignore + logger.trace("name: '%s', mask shape: %s, affine_matrix: %s, " # type:ignore[attr-defined] "interpolator: %s, storage_size: %s, storage_centering: %s)", name, mask.shape, affine_matrix, interpolator, storage_size, storage_centering) fsmask = Mask(storage_size=storage_size, storage_centering=storage_centering) @@ -183,7 +183,7 @@ def add_landmarks_xy(self, landmarks: np.ndarray) -> None: landmarks: :class:`numpy.ndarray` The 68 point face landmarks to add for the face """ - logger.trace("landmarks shape: '%s'", landmarks.shape) # type: ignore + logger.trace("landmarks shape: '%s'", landmarks.shape) # type:ignore[attr-defined] self._landmarks_xy = landmarks def add_identity(self, name: str, embedding: np.ndarray, ) -> None: @@ -197,7 +197,7 @@ def add_identity(self, name: str, embedding: np.ndarray, ) -> None: embedding: numpy.ndarray The identity embedding """ - logger.trace("name: '%s', embedding shape: %s", # type: ignore + logger.trace("name: '%s', embedding shape: %s", # type:ignore[attr-defined] name, embedding.shape) assert name == "vggface2" assert embedding.shape[0] == 512 @@ -210,7 +210,7 @@ def clear_all_identities(self) -> None: def get_landmark_mask(self, area: T.Literal["eye", "face", "mouth"], blur_kernel: int, - dilation: int) -> np.ndarray: + dilation: float) -> np.ndarray: """ Add a :class:`LandmarksMask` to this detected face Landmark based masks are generated from face Aligned Face landmark points. An aligned @@ -224,8 +224,8 @@ def get_landmark_mask(self, specific areas blur_kernel: int The size of the kernel for blurring the mask edges - dilation: int - The amount of dilation to apply to the mask. `0` for none. Default: `0` + dilation: float + The amount of dilation to apply to the mask. as a percentage of the mask size Returns ------- @@ -233,7 +233,7 @@ def get_landmark_mask(self, The generated landmarks mask for the selected area """ # TODO Face mask generation from landmarks - logger.trace("area: %s, dilation: %s", area, dilation) # type: ignore + logger.trace("area: %s, dilation: %s", area, dilation) # type:ignore[attr-defined] areas = {"mouth": [slice(48, 60)], "eye": [slice(36, 42), slice(42, 48)]} points = [self.aligned.landmarks[zone] for zone in areas[area]] @@ -307,7 +307,7 @@ def to_alignment(self) -> AlignmentFileDict: for name, mask in self.mask.items()}, identity={k: v.tolist() for k, v in self._identity.items()}, thumb=self.thumbnail) - logger.trace("Returning: %s", alignment) # type: ignore + logger.trace("Returning: %s", alignment) # type:ignore[attr-defined] return alignment def from_alignment(self, alignment: AlignmentFileDict, @@ -332,8 +332,8 @@ def from_alignment(self, alignment: AlignmentFileDict, Default: ``False`` """ - logger.trace("Creating from alignment: (alignment: %s, has_image: %s)", # type: ignore - alignment, bool(image is not None)) + logger.trace("Creating from alignment: (alignment: %s," # type:ignore[attr-defined] + " has_image: %s)", alignment, bool(image is not None)) self.left = alignment["x"] self.width = alignment["w"] self.top = alignment["y"] @@ -358,9 +358,9 @@ def from_alignment(self, alignment: AlignmentFileDict, self.mask[name].from_dict(mask_dict) if image is not None and image.any(): self._image_to_face(image) - logger.trace("Created from alignment: (left: %s, width: %s, top: %s, " # type: ignore - "height: %s, landmarks: %s, mask: %s)", self.left, self.width, self.top, - self.height, self.landmarks_xy, self.mask) + logger.trace("Created from alignment: (left: %s, width: %s, " # type:ignore[attr-defined] + "top: %s, height: %s, landmarks: %s, mask: %s)", + self.left, self.width, self.top, self.height, self.landmarks_xy, self.mask) def to_png_meta(self) -> PNGHeaderAlignmentsDict: """ Return the detected face formatted for insertion into a png itxt header. @@ -403,14 +403,14 @@ def from_png_meta(self, alignment: PNGHeaderAlignmentsDict) -> None: for key, val in alignment.get("identity", {}).items(): assert key in ["vggface2"] self._identity[T.cast(T.Literal["vggface2"], key)] = np.array(val, dtype="float32") - logger.trace("Created from png exif header: (left: %s, width: %s, top: %s " # type: ignore - " height: %s, landmarks: %s, mask: %s, identity: %s)", self.left, self.width, - self.top, self.height, self.landmarks_xy, self.mask, + logger.trace("Created from png exif header: (left: %s, " # type:ignore[attr-defined] + "width: %s, top: %s height: %s, landmarks: %s, mask: %s, identity: %s)", + self.left, self.width, self.top, self.height, self.landmarks_xy, self.mask, {k: v.shape for k, v in self._identity.items()}) def _image_to_face(self, image: np.ndarray) -> None: """ set self.image to be the cropped face from detected bounding box """ - logger.trace("Cropping face from image") # type: ignore + logger.trace("Cropping face from image") # type:ignore[attr-defined] self.image = image[self.top: self.bottom, self.left: self.right] @@ -467,10 +467,11 @@ def load_aligned(self, """ if self._aligned and not force: # Don't reload an already aligned face - logger.trace("Skipping alignment calculation for already aligned face") # type: ignore + logger.trace("Skipping alignment calculation for already " # type:ignore[attr-defined] + "aligned face") else: - logger.trace("Loading aligned face: (size: %s, dtype: %s)", # type: ignore - size, dtype) + logger.trace("Loading aligned face: (size: %s, " # type:ignore[attr-defined] + "dtype: %s)", size, dtype) self._aligned = AlignedFace(self.landmarks_xy, image=image, centering=centering, @@ -507,7 +508,8 @@ class Mask(): def __init__(self, storage_size: int = 128, storage_centering: CenteringType = "face") -> None: - logger.trace("Initializing: %s (storage_size: %s, storage_centering: %s)", # type: ignore + logger.trace("Initializing: %s (storage_size: %s, " # type:ignore[attr-defined] + "storage_centering: %s)", self.__class__.__name__, storage_size, storage_centering) self.stored_size = storage_size self.stored_centering = storage_centering @@ -520,19 +522,21 @@ def __init__(self, self._blur_passes: int = 0 self._blur_kernel: float | int = 0 self._threshold = 0.0 + self._dilation: tuple[T.Literal["erode", "dilate"], np.ndarray | None] = ("erode", None) self._sub_crop_size = 0 self._sub_crop_slices: dict[T.Literal["in", "out"], list[slice]] = {} self.set_blur_and_threshold() - logger.trace("Initialized: %s", self.__class__.__name__) # type: ignore + logger.trace("Initialized: %s", self.__class__.__name__) # type:ignore[attr-defined] @property def mask(self) -> np.ndarray: """ :class:`numpy.ndarray`: The mask at the size of :attr:`stored_size` with any requested blurring, threshold amount and centering applied.""" mask = self.stored_mask - if self._threshold != 0.0 or self._blur_kernel != 0: + if self._dilation[-1] is not None or self._threshold != 0.0 or self._blur_kernel != 0: mask = mask.copy() + self._dilate_mask(mask) if self._threshold != 0.0: mask[mask < self._threshold] = 0.0 mask[mask > 255.0 - self._threshold] = 255.0 @@ -546,7 +550,7 @@ def mask(self) -> np.ndarray: slice_in, slice_out = self._sub_crop_slices["in"], self._sub_crop_slices["out"] out[slice_out[0], slice_out[1], :] = mask[slice_in[0], slice_in[1], :] mask = out - logger.trace("mask shape: %s", mask.shape) # type: ignore + logger.trace("mask shape: %s", mask.shape) # type:ignore[attr-defined] return mask @property @@ -556,7 +560,7 @@ def stored_mask(self) -> np.ndarray: assert self._mask is not None dims = (self.stored_size, self.stored_size, 1) mask = np.frombuffer(decompress(self._mask), dtype="uint8").reshape(dims) - logger.trace("stored mask shape: %s", mask.shape) # type: ignore + logger.trace("stored mask shape: %s", mask.shape) # type:ignore[attr-defined] return mask @property @@ -567,9 +571,9 @@ def original_roi(self) -> np.ndarray: [0, self.stored_size - 1], [self.stored_size - 1, self.stored_size - 1], [self.stored_size - 1, 0]], np.int32).reshape((-1, 1, 2)) - matrix = cv2.invertAffineTransform(self._affine_matrix) + matrix = cv2.invertAffineTransform(self.affine_matrix) roi = cv2.transform(points, matrix).reshape((4, 2)) - logger.trace("Returning: %s", roi) # type: ignore + logger.trace("Returning: %s", roi) # type:ignore[attr-defined] return roi @property @@ -584,6 +588,22 @@ def interpolator(self) -> int: assert self._interpolator is not None return self._interpolator + def _dilate_mask(self, mask: np.ndarray) -> None: + """ Erode/Dilate the mask. The action is performed in-place on the given mask. + + No action is performed if a dilation amount has not been set + + Parameters + ---------- + mask: :class:`numpy.ndarray` + The mask to be eroded/dilated + """ + if self._dilation[-1] is None: + return + + func = cv2.erode if self._dilation[0] == "erode" else cv2.dilate + func(mask, self._dilation[-1], dst=mask, iterations=1) + def get_full_frame_mask(self, width: int, height: int) -> np.ndarray: """ Return the stored mask in a full size frame of the given dimensions @@ -600,13 +620,13 @@ def get_full_frame_mask(self, width: int, height: int) -> np.ndarray: """ frame = np.zeros((width, height, 1), dtype="uint8") mask = cv2.warpAffine(self.mask, - self._affine_matrix, + self.affine_matrix, (width, height), frame, - flags=cv2.WARP_INVERSE_MAP | self._interpolator, + flags=cv2.WARP_INVERSE_MAP | self.interpolator, borderMode=cv2.BORDER_CONSTANT) - logger.trace("mask shape: %s, mask dtype: %s, mask min: %s, mask max: %s", # type: ignore - mask.shape, mask.dtype, mask.min(), mask.max()) + logger.trace("mask shape: %s, mask dtype: %s, mask min: %s, " # type:ignore[attr-defined] + "mask max: %s", mask.shape, mask.dtype, mask.min(), mask.max()) return mask def add(self, mask: np.ndarray, affine_matrix: np.ndarray, interpolator: int) -> None: @@ -624,9 +644,9 @@ def add(self, mask: np.ndarray, affine_matrix: np.ndarray, interpolator: int) -> interpolator, int: The CV2 interpolator required to transform this mask to it's original frame """ - logger.trace("mask shape: %s, mask dtype: %s, mask min: %s, mask max: %s, " # type: ignore - "affine_matrix: %s, interpolator: %s)", mask.shape, mask.dtype, mask.min(), - affine_matrix, mask.max(), interpolator) + logger.trace("mask shape: %s, mask dtype: %s, mask min: %s, " # type:ignore[attr-defined] + "mask max: %s, affine_matrix: %s, interpolator: %s)", + mask.shape, mask.dtype, mask.min(), affine_matrix, mask.max(), interpolator) self._affine_matrix = self._adjust_affine_matrix(mask.shape[0], affine_matrix) self._interpolator = interpolator self.replace_mask(mask) @@ -645,6 +665,26 @@ def replace_mask(self, mask: np.ndarray) -> None: interpolation=cv2.INTER_AREA) * 255.0).astype("uint8") self._mask = compress(mask.tobytes()) + def set_dilation(self, amount: float) -> None: + """ Set the internal dilation object for returned masks + + Parameters + ---------- + amount: float + The amount of erosion/dilation to apply as a percentage of the total mask size. + Negative values erode the mask. Positive values dilate the mask + """ + if amount == 0: + self._dilation = ("erode", None) + return + + action: T.Literal["erode", "dilate"] = "erode" if amount < 0 else "dilate" + kernel = int(round(self.stored_size * abs(amount / 100.), 0)) + self._dilation = (action, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (kernel, kernel))) + + logger.trace("action: '%s', amount: %s, kernel: %s, ", # type:ignore[attr-defined] + action, amount, kernel) + def set_blur_and_threshold(self, blur_kernel: int = 0, blur_type: T.Literal["gaussian", "normalized"] | None = "gaussian", @@ -666,8 +706,9 @@ def set_blur_and_threshold(self, The threshold amount to minimize/maximize mask values to 0 and 100. Percentage value. Default: 0 """ - logger.trace("blur_kernel: %s, blur_type: %s, blur_passes: %s, " # type: ignore - "threshold: %s", blur_kernel, blur_type, blur_passes, threshold) + logger.trace("blur_kernel: %s, blur_type: %s, " # type:ignore[attr-defined] + "blur_passes: %s, threshold: %s", + blur_kernel, blur_type, blur_passes, threshold) if blur_type is not None: blur_kernel += 0 if blur_kernel == 0 or blur_kernel % 2 == 1 else 1 self._blur_kernel = blur_kernel @@ -719,9 +760,9 @@ def set_sub_crop(self, slice(max(roi[0] * -1, 0), crop_size - min(crop_size, max(0, roi[2] - self.stored_size)))] - logger.trace("src_size: %s, coverage_ratio: %s, sub_crop_size: %s, " # type: ignore - "sub_crop_slices: %s", roi, coverage_ratio, self._sub_crop_size, - self._sub_crop_slices) + logger.trace("src_size: %s, coverage_ratio: %s, " # type:ignore[attr-defined] + "sub_crop_size: %s, sub_crop_slices: %s", + roi, coverage_ratio, self._sub_crop_size, self._sub_crop_slices) def _adjust_affine_matrix(self, mask_size: int, affine_matrix: np.ndarray) -> np.ndarray: """ Adjust the affine matrix for the mask's storage size @@ -741,7 +782,7 @@ def _adjust_affine_matrix(self, mask_size: int, affine_matrix: np.ndarray) -> np zoom = self.stored_size / mask_size zoom_mat = np.array([[zoom, 0, 0.], [0, zoom, 0.]]) adjust_mat = np.dot(zoom_mat, np.concatenate((affine_matrix, np.array([[0., 0., 1.]])))) - logger.trace("storage_size: %s, mask_size: %s, zoom: %s, " # type: ignore + logger.trace("storage_size: %s, mask_size: %s, zoom: %s, " # type:ignore[attr-defined] "original matrix: %s, adjusted_matrix: %s", self.stored_size, mask_size, zoom, affine_matrix.shape, adjust_mat.shape) return adjust_mat @@ -768,7 +809,8 @@ def to_dict(self, is_png=False) -> MaskAlignmentsFileDict: interpolator=self.interpolator, stored_size=self.stored_size, stored_centering=self.stored_centering) - logger.trace({k: v if k != "mask" else type(v) for k, v in retval.items()}) # type: ignore + logger.trace({k: v if k != "mask" else type(v) # type:ignore[attr-defined] + for k, v in retval.items()}) return retval def to_png_meta(self) -> MaskAlignmentsFileDict: @@ -799,7 +841,7 @@ def from_dict(self, mask_dict: MaskAlignmentsFileDict) -> None: self.stored_size = mask_dict["stored_size"] centering = mask_dict.get("stored_centering") self.stored_centering = "face" if centering is None else centering - logger.trace({k: v if k != "mask" else type(v) # type: ignore + logger.trace({k: v if k != "mask" else type(v) # type:ignore[attr-defined] for k, v in mask_dict.items()}) @@ -826,17 +868,17 @@ class LandmarksMask(Mask): storage_centering, str (optional): The centering to store the mask at. One of `"legacy"`, `"face"`, `"head"`. Default: `"face"` - dilation: int, optional - The amount of dilation to apply to the mask. `0` for none. Default: `0` + dilation: float, optional + The amount of dilation to apply to the mask. as a percentage of the mask size. Default: 0.0 """ def __init__(self, points: list[np.ndarray], storage_size: int = 128, storage_centering: CenteringType = "face", - dilation: int = 0) -> None: + dilation: float = 0.0) -> None: super().__init__(storage_size=storage_size, storage_centering=storage_centering) self._points = points - self._dilation = dilation + self.set_dilation(dilation) @property def mask(self) -> np.ndarray: @@ -862,17 +904,15 @@ def generate_mask(self, affine_matrix: np.ndarray, interpolator: int) -> None: for landmarks in self._points: lms = np.rint(landmarks).astype("int") cv2.fillConvexPoly(mask, cv2.convexHull(lms), 1.0, lineType=cv2.LINE_AA) - if self._dilation != 0: - mask = cv2.dilate(mask, - cv2.getStructuringElement(cv2.MORPH_ELLIPSE, - (self._dilation, self._dilation)), - iterations=1) + if self._dilation[-1] is not None: + self._dilate_mask(mask) if self._blur_kernel != 0 and self._blur_type is not None: mask = BlurMask(self._blur_type, mask, self._blur_kernel, passes=self._blur_passes).blurred - logger.trace("mask: (shape: %s, dtype: %s)", mask.shape, mask.dtype) # type: ignore + logger.trace("mask: (shape: %s, dtype: %s)", # type:ignore[attr-defined] + mask.shape, mask.dtype) self.add(mask, affine_matrix, interpolator) @@ -911,15 +951,16 @@ def __init__(self, kernel: int | float, is_ratio: bool = False, passes: int = 1) -> None: - logger.trace("Initializing %s: (blur_type: '%s', mask_shape: %s, " # type: ignore - "kernel: %s, is_ratio: %s, passes: %s)", self.__class__.__name__, blur_type, + logger.trace("Initializing %s: (blur_type: '%s', " # type:ignore[attr-defined] + "mask_shape: %s, kernel: %s, is_ratio: %s, passes: %s)", + self.__class__.__name__, blur_type, mask.shape, kernel, is_ratio, passes) self._blur_type = blur_type self._mask = mask self._passes = passes kernel_size = self._get_kernel_size(kernel, is_ratio) self._kernel_size = self._get_kernel_tuple(kernel_size) - logger.trace("Initialized %s", self.__class__.__name__) # type: ignore + logger.trace("Initialized %s", self.__class__.__name__) # type:ignore[attr-defined] @property def blurred(self) -> np.ndarray: @@ -930,12 +971,14 @@ def blurred(self) -> np.ndarray: for i in range(self._passes): assert isinstance(kwargs["ksize"], tuple) ksize = int(kwargs["ksize"][0]) - logger.trace("Pass: %s, kernel_size: %s", i + 1, (ksize, ksize)) # type: ignore + logger.trace("Pass: %s, kernel_size: %s", # type:ignore[attr-defined] + i + 1, (ksize, ksize)) blurred = func(blurred, **kwargs) ksize = int(round(ksize * self._multipass_factor)) kwargs["ksize"] = self._get_kernel_tuple(ksize) blurred = blurred[..., None] - logger.trace("Returning blurred mask. Shape: %s", blurred.shape) # type: ignore + logger.trace("Returning blurred mask. Shape: %s", # type:ignore[attr-defined] + blurred.shape) return blurred @property @@ -991,7 +1034,7 @@ def _get_kernel_size(self, kernel: int | float, is_ratio: bool) -> int: mask_diameter = np.sqrt(np.sum(self._mask)) radius = round(max(1., mask_diameter * kernel / 100.)) kernel_size = int(radius * 2 + 1) - logger.trace("kernel_size: %s", kernel_size) # type: ignore + logger.trace("kernel_size: %s", kernel_size) # type:ignore[attr-defined] return kernel_size @staticmethod @@ -1010,14 +1053,14 @@ def _get_kernel_tuple(kernel_size: int) -> tuple[int, int]: """ kernel_size += 1 if kernel_size % 2 == 0 else 0 retval = (kernel_size, kernel_size) - logger.trace(retval) # type: ignore + logger.trace(retval) # type:ignore[attr-defined] return retval def _get_kwargs(self) -> dict[str, int | tuple[int, int]]: """ dict: the valid keyword arguments for the requested :attr:`_blur_type` """ retval = {kword: self._kwarg_mapping[kword] for kword in self._kwarg_requirements[self._blur_type]} - logger.trace("BlurMask kwargs: %s", retval) # type: ignore + logger.trace("BlurMask kwargs: %s", retval) # type:ignore[attr-defined] return retval diff --git a/lib/training/cache.py b/lib/training/cache.py index 875f10fa16..a2fa1d28d4 100644 --- a/lib/training/cache.py +++ b/lib/training/cache.py @@ -428,8 +428,10 @@ def _get_face_mask(self, filename: str, detected_face: DetectedFace) -> np.ndarr f"The masks that exist for this face are: {list(detected_face.mask)}") mask = detected_face.mask[str(self._config["mask_type"])] + assert isinstance(self._config["mask_dilation"], float) assert isinstance(self._config["mask_blur_kernel"], int) assert isinstance(self._config["mask_threshold"], int) + mask.set_dilation(self._config["mask_dilation"]) mask.set_blur_and_threshold(blur_kernel=self._config["mask_blur_kernel"], threshold=self._config["mask_threshold"]) @@ -467,7 +469,7 @@ def _get_localized_mask(self, assert isinstance(multiplier, int) if not self._config["penalized_mask_loss"] or multiplier <= 1: return None - mask = detected_face.get_landmark_mask(area, self._size // 16, self._size // 32) + mask = detected_face.get_landmark_mask(area, self._size // 16, 2.5) logger.trace("Caching localized '%s' mask for: %s %s", # type: ignore area, filename, mask.shape) return mask diff --git a/locales/plugins.train._config.pot b/locales/plugins.train._config.pot index 29a8ac7178..38e94f3152 100644 --- a/locales/plugins.train._config.pot +++ b/locales/plugins.train._config.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-08-20 14:54+0100\n" +"POT-Creation-Date: 2024-03-26 17:37+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -539,8 +539,9 @@ msgid "" "attention on the core face area." msgstr "" -#: plugins/train/_config.py:600 plugins/train/_config.py:642 -#: plugins/train/_config.py:656 plugins/train/_config.py:665 +#: plugins/train/_config.py:600 plugins/train/_config.py:643 +#: plugins/train/_config.py:656 plugins/train/_config.py:671 +#: plugins/train/_config.py:680 msgid "mask" msgstr "" @@ -582,7 +583,14 @@ msgid "" "performance." msgstr "" -#: plugins/train/_config.py:644 +#: plugins/train/_config.py:645 +msgid "" +"Dilate or erode the mask. Negative values erode the mask (make it smaller). " +"Positive values dilate the mask (make it larger). The value given is a " +"percentage of the total mask size." +msgstr "" + +#: plugins/train/_config.py:658 msgid "" "Apply gaussian blur to the mask input. This has the effect of smoothing the " "edges of the mask, which can help with poorly calculated masks and give less " @@ -592,13 +600,13 @@ msgid "" "number." msgstr "" -#: plugins/train/_config.py:658 +#: plugins/train/_config.py:673 msgid "" "Sets pixels that are near white to white and near black to black. Set to 0 " "for off." msgstr "" -#: plugins/train/_config.py:667 +#: plugins/train/_config.py:682 msgid "" "Dedicate a portion of the model to learning how to duplicate the input mask. " "Increases VRAM usage in exchange for learning a quick ability to try to " diff --git a/locales/ru/LC_MESSAGES/plugins.train._config.mo b/locales/ru/LC_MESSAGES/plugins.train._config.mo index 0b922cd0d0d7543e45d7d687341a2114189dfb7a..a1032a91d1a0e62524d07cf7f2125b2825715bd0 100644 GIT binary patch delta 1829 zcmai!3v82B6vzM2JwP_a@leFc$9Qc*=U^RdQ@}UKOM$7xX|QhJMx~?L)@hWa+dvp$ zNED|?6wv4r#p!Sq3pgSsMB|I^<6+DQ5;Z|Es6k9fh(v$49k`enZ*uy(=bm%VIrrTA zb-UxaaL5z;E+?&3jH$@ANLpWMAw2Ejhf$g?*{}@W)<@a`o8XTbQiAoGeo`Ec=r4_f zN8l~kFTnTU^a0W|_-mHb4!xI4GstoD3h6bEVj7hrm0;L6MEa8e=Y~on(f_zg`VGgS z!=!cS8B{kI`-{0!CHmX22|ay;^et?L(^>ah(gyU`M@rk+Uy>)i!}|B5B}{4=D{X@9 zu$latN!jMaE<6Fp>0{W4{`jpF7Q^8zlM>p!`F2i;es~5(9FESEz9Qh?svF8s^pNglI*zC>J|Tfp_fzaU*!8mHO^2SKxbq=((m_#cNcu1P;JISOCyRDj-ACAFeEJe)_# zToaIX@Lt?HI*ol*RLX|k_4Gf)0%dKI z*0ZqtRmq1wYqK;K#f+3D|8~~OX(Z(y&F3yVbhAkd@cl>eS~`@@%e5ZBM!b}QeGx^f1ED@ zeHt(HI{b?{PCvK+UIQzkd8G9)3w{47=`wf_8oy(Z4@)N?ZplxbcbmHZAxuUiW>BO? zC1*U!9$v)Yo(YXe4nAR(BUd6O_*BGW{gGBjb$Vg zK5_B?As=GeR*FnOCL&y;ZbHn+NkO@tx#?#;u|UutwXIOtvcsWj+lnr+tvY{XX`waG zuJuO)^|n>-4?bZ>{$;oF>-^8X#Y*@uwlM{FLxGheXyVi+Y&WBDHi>q1e!eRZTAntP}@TDMDzO%z@ zbUI=wOWb|lnAzsGP)wX2Ia{2)ck;$9MU8_lqmnbRP%sEmaO;Vi;`x;pU% zvlNzLTSX8SISk`QTF@W$hfz_916i{dq0ka5NDH#WKIeCD2fn}0dCqyx+jE}tyFMCw z_jD}z{fw+bSeB?V_3s2&BfpLLvD9S4<8q~iAC@ibq!4Yq3+HNa(gs0sSR#4T`1|377^_W3qA zJ_)YL9oi!u>5auZjX=^+jCDbd1M*kE79EQ3gfE?NkG!tE_AV&5f6d+Sf&N>2Af_E( z4bDsAKFE@7GG8v0AIqLhdx7^E`io@nFO}XBl`?tFO0AU;%IaS4(1mZvaoKS{Jmx}W z4}ev+uXpFRkI8jzWamTXh4%O(o?bg^BW!Y|p+0!e{K($yrtSZ*AEwFtr@a-viw>5(u*dl95uxcJMOKQT-f=SA2yM-e;ab)CL5l8#na2wYp~K2jKA(?WYv*0 zH@|K2xnSxTeC>nOe~{h|pPhhT&G6!r@P~Hn6s)rUFZr^O==L;cUGV&A_|*Or zXM82jmrT07HW#k@#w66?iE}W|OIr0ET-EOX2~2SME|_?7x11vn$dH^zr7w)XFG8-& z@z}wyQ2L;lBXz?^g!vlv70FvvC?Wrt7gdc!hW}$`reYwX5|Ya#X1Xd?OH?QeRH?d2 zc@}1?8LD1|a;sVt)nI;xdD34&GRnx%R;j`XnuB5Sl|`k*Z;i@Ho6$E!Ae_#KN2S_t zgQ`=lYOyL;3DvGbsZvIq3gyd=qS_d?9-cyrs#T$caNZu(3}c36sd)C4SnBMQ2UkvS rjMulu8|o_(P3_ICwe^X?&fX*0jdcyV3?59Cq_Wm_O-l7_IKKZs%x%3+ diff --git a/locales/ru/LC_MESSAGES/plugins.train._config.po b/locales/ru/LC_MESSAGES/plugins.train._config.po index 1439600ec9..04191e5e70 100644 --- a/locales/ru/LC_MESSAGES/plugins.train._config.po +++ b/locales/ru/LC_MESSAGES/plugins.train._config.po @@ -7,15 +7,15 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-08-20 14:54+0100\n" -"PO-Revision-Date: 2023-08-20 14:58+0100\n" +"POT-Creation-Date: 2024-03-26 17:37+0000\n" +"PO-Revision-Date: 2024-03-26 17:40+0000\n" "Last-Translator: \n" "Language-Team: \n" "Language: ru_RU\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 3.3.2\n" +"X-Generator: Poedit 3.4.2\n" #: plugins/train/_config.py:17 msgid "" @@ -851,8 +851,9 @@ msgstr "" "время как область лица с маской является приоритетной. Может повысить общее " "качество за счет концентрации внимания на основной области лица." -#: plugins/train/_config.py:600 plugins/train/_config.py:642 -#: plugins/train/_config.py:656 plugins/train/_config.py:665 +#: plugins/train/_config.py:600 plugins/train/_config.py:643 +#: plugins/train/_config.py:656 plugins/train/_config.py:671 +#: plugins/train/_config.py:680 msgid "mask" msgstr "маска" @@ -928,7 +929,16 @@ msgstr "" "сообщества и для дальнейшего описания нуждается в тестировании. Профильные " "лица могут иметь низкую производительность." -#: plugins/train/_config.py:644 +#: plugins/train/_config.py:645 +msgid "" +"Dilate or erode the mask. Negative values erode the mask (make it smaller). " +"Positive values dilate the mask (make it larger). The value given is a " +"percentage of the total mask size." +msgstr "" +"Расширяет или сужает маску. Отрицательные значения сужают маску (делают её " +"меньше). Положительные значения расширяют маску (делают её больше). " + +#: plugins/train/_config.py:658 msgid "" "Apply gaussian blur to the mask input. This has the effect of smoothing the " "edges of the mask, which can help with poorly calculated masks and give less " @@ -944,7 +954,7 @@ msgstr "" "должно быть нечетным, если передано четное число, то оно будет округлено до " "следующего нечетного числа." -#: plugins/train/_config.py:658 +#: plugins/train/_config.py:673 msgid "" "Sets pixels that are near white to white and near black to black. Set to 0 " "for off." @@ -952,7 +962,7 @@ msgstr "" "Устанавливает пиксели, которые почти белые - в белые и которые почти черные " "- в черные. Установите 0, чтобы выключить." -#: plugins/train/_config.py:667 +#: plugins/train/_config.py:682 msgid "" "Dedicate a portion of the model to learning how to duplicate the input mask. " "Increases VRAM usage in exchange for learning a quick ability to try to " diff --git a/plugins/train/_config.py b/plugins/train/_config.py index bb0b57f7e8..404e0d8e06 100644 --- a/plugins/train/_config.py +++ b/plugins/train/_config.py @@ -632,6 +632,19 @@ def _set_loss(self) -> None: "faces. The mask model has been trained by community members and will need " "testing for further description. Profile faces may result in sub-par " "performance.")) + self.add_item( + section=section, + title="mask_dilation", + datatype=float, + min_max=(-5.0, 5.0), + rounding=1, + default=0, + fixed=False, + group=_("mask"), + info=_( + "Dilate or erode the mask. Negative values erode the mask (make it smaller). " + "Positive values dilate the mask (make it larger). The value given is a " + "percentage of the total mask size.")) self.add_item( section=section, title="mask_blur_kernel", @@ -639,6 +652,7 @@ def _set_loss(self) -> None: min_max=(0, 9), rounding=1, default=3, + fixed=False, group=_("mask"), info=_( "Apply gaussian blur to the mask input. This has the effect of smoothing the " @@ -653,6 +667,7 @@ def _set_loss(self) -> None: default=4, min_max=(0, 50), rounding=1, + fixed=False, group=_("mask"), info=_( "Sets pixels that are near white to white and near black to black. Set to 0 for "