huge changes, inpainting in faces unit, change faces processing, change api, refactor, requires further testing
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import io
|
||||
from typing import List, Optional, Tuple, Union, Dict
|
||||
from typing import List, Optional, Union, Dict
|
||||
from PIL import Image
|
||||
import cv2
|
||||
import numpy as np
|
||||
@@ -10,14 +10,15 @@ from scripts.faceswaplab_globals import NSFW_SCORE_THRESHOLD
|
||||
from modules import processing
|
||||
import base64
|
||||
from collections import Counter
|
||||
from scripts.faceswaplab_utils.typing import BoxCoords, CV2ImgU8, PILImage
|
||||
|
||||
|
||||
def check_against_nsfw(img: Image.Image) -> bool:
|
||||
def check_against_nsfw(img: PILImage) -> bool:
|
||||
"""
|
||||
Check if an image exceeds the Not Safe for Work (NSFW) score.
|
||||
|
||||
Parameters:
|
||||
img (PIL.Image.Image): The image to be checked.
|
||||
img (PILImage): The image to be checked.
|
||||
|
||||
Returns:
|
||||
bool: True if any part of the image is considered NSFW, False otherwise.
|
||||
@@ -32,33 +33,33 @@ def check_against_nsfw(img: Image.Image) -> bool:
|
||||
return any(shapes)
|
||||
|
||||
|
||||
def pil_to_cv2(pil_img: Image.Image) -> np.ndarray: # type: ignore
|
||||
def pil_to_cv2(pil_img: PILImage) -> CV2ImgU8: # type: ignore
|
||||
"""
|
||||
Convert a PIL Image into an OpenCV image (cv2).
|
||||
|
||||
Args:
|
||||
pil_img (PIL.Image.Image): An image in PIL format.
|
||||
pil_img (PILImage): An image in PIL format.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The input image converted to OpenCV format (BGR).
|
||||
CV2ImgU8: The input image converted to OpenCV format (BGR).
|
||||
"""
|
||||
return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
|
||||
|
||||
|
||||
def cv2_to_pil(cv2_img: np.ndarray) -> Image.Image: # type: ignore
|
||||
def cv2_to_pil(cv2_img: CV2ImgU8) -> PILImage: # type: ignore
|
||||
"""
|
||||
Convert an OpenCV image (cv2) into a PIL Image.
|
||||
|
||||
Args:
|
||||
cv2_img (np.ndarray): An image in OpenCV format (BGR).
|
||||
cv2_img (CV2ImgU8): An image in OpenCV format (BGR).
|
||||
|
||||
Returns:
|
||||
PIL.Image.Image: The input image converted to PIL format (RGB).
|
||||
PILImage: The input image converted to PIL format (RGB).
|
||||
"""
|
||||
return Image.fromarray(cv2.cvtColor(cv2_img, cv2.COLOR_BGR2RGB))
|
||||
|
||||
|
||||
def torch_to_pil(images: torch.Tensor) -> List[Image.Image]:
|
||||
def torch_to_pil(tensor: torch.Tensor) -> List[PILImage]:
|
||||
"""
|
||||
Converts a tensor image or a batch of tensor images to a PIL image or a list of PIL images.
|
||||
|
||||
@@ -72,7 +73,7 @@ def torch_to_pil(images: torch.Tensor) -> List[Image.Image]:
|
||||
list
|
||||
A list of PIL images.
|
||||
"""
|
||||
images = images.cpu().permute(0, 2, 3, 1).numpy()
|
||||
images: CV2ImgU8 = tensor.cpu().permute(0, 2, 3, 1).numpy()
|
||||
if images.ndim == 3:
|
||||
images = images[None, ...]
|
||||
images = (images * 255).round().astype("uint8")
|
||||
@@ -80,13 +81,13 @@ def torch_to_pil(images: torch.Tensor) -> List[Image.Image]:
|
||||
return pil_images
|
||||
|
||||
|
||||
def pil_to_torch(pil_images: Union[Image.Image, List[Image.Image]]) -> torch.Tensor:
|
||||
def pil_to_torch(pil_images: Union[PILImage, List[PILImage]]) -> torch.Tensor:
|
||||
"""
|
||||
Converts a PIL image or a list of PIL images to a torch tensor or a batch of torch tensors.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pil_images : Union[Image.Image, List[Image.Image]]
|
||||
pil_images : Union[PILImage, List[PILImage]]
|
||||
A PIL image or a list of PIL images.
|
||||
|
||||
Returns
|
||||
@@ -104,7 +105,7 @@ def pil_to_torch(pil_images: Union[Image.Image, List[Image.Image]]) -> torch.Ten
|
||||
return torch_image
|
||||
|
||||
|
||||
def create_square_image(image_list: List[Image.Image]) -> Optional[Image.Image]:
|
||||
def create_square_image(image_list: List[PILImage]) -> Optional[PILImage]:
|
||||
"""
|
||||
Creates a square image by combining multiple images in a grid pattern.
|
||||
|
||||
@@ -156,7 +157,7 @@ def create_square_image(image_list: List[Image.Image]) -> Optional[Image.Image]:
|
||||
return None
|
||||
|
||||
|
||||
# def create_mask(image : Image.Image, box_coords : Tuple[int, int, int, int]) -> Image.Image:
|
||||
# def create_mask(image : PILImage, box_coords : Tuple[int, int, int, int]) -> PILImage:
|
||||
# width, height = image.size
|
||||
# mask = Image.new("L", (width, height), 255)
|
||||
# x1, y1, x2, y2 = box_coords
|
||||
@@ -170,19 +171,20 @@ def create_square_image(image_list: List[Image.Image]) -> Optional[Image.Image]:
|
||||
|
||||
|
||||
def create_mask(
|
||||
image: Image.Image, box_coords: Tuple[int, int, int, int]
|
||||
) -> Image.Image:
|
||||
image: PILImage,
|
||||
box_coords: BoxCoords,
|
||||
) -> PILImage:
|
||||
"""
|
||||
Create a binary mask for a given image and bounding box coordinates.
|
||||
|
||||
Args:
|
||||
image (PIL.Image.Image): The input image.
|
||||
image (PILImage): The input image.
|
||||
box_coords (Tuple[int, int, int, int]): A tuple of 4 integers defining the bounding box.
|
||||
It follows the pattern (x1, y1, x2, y2), where (x1, y1) is the top-left coordinate of the
|
||||
box and (x2, y2) is the bottom-right coordinate of the box.
|
||||
|
||||
Returns:
|
||||
PIL.Image.Image: A binary mask of the same size as the input image, where pixels within
|
||||
PILImage: A binary mask of the same size as the input image, where pixels within
|
||||
the bounding box are white (255) and pixels outside the bounding box are black (0).
|
||||
"""
|
||||
width, height = image.size
|
||||
@@ -195,8 +197,8 @@ def create_mask(
|
||||
|
||||
|
||||
def apply_mask(
|
||||
img: Image.Image, p: processing.StableDiffusionProcessing, batch_index: int
|
||||
) -> Image.Image:
|
||||
img: PILImage, p: processing.StableDiffusionProcessing, batch_index: int
|
||||
) -> PILImage:
|
||||
"""
|
||||
Apply mask overlay and color correction to an image if enabled
|
||||
|
||||
@@ -213,7 +215,7 @@ def apply_mask(
|
||||
overlays = p.overlay_images
|
||||
if overlays is None or batch_index >= len(overlays):
|
||||
return img
|
||||
overlay: Image.Image = overlays[batch_index]
|
||||
overlay: PILImage = overlays[batch_index]
|
||||
overlay = overlay.resize((img.size), resample=Image.Resampling.LANCZOS)
|
||||
img = img.copy()
|
||||
img.paste(overlay, (0, 0), overlay)
|
||||
@@ -227,9 +229,7 @@ def apply_mask(
|
||||
return img
|
||||
|
||||
|
||||
def prepare_mask(
|
||||
mask: Image.Image, p: processing.StableDiffusionProcessing
|
||||
) -> Image.Image:
|
||||
def prepare_mask(mask: PILImage, p: processing.StableDiffusionProcessing) -> PILImage:
|
||||
"""
|
||||
Prepare an image mask for the inpainting process. (This comes from controlnet)
|
||||
|
||||
@@ -243,12 +243,12 @@ def prepare_mask(
|
||||
apply a Gaussian blur to the mask with a radius equal to 'mask_blur'.
|
||||
|
||||
Args:
|
||||
mask (Image.Image): The input mask as a PIL Image object.
|
||||
mask (PILImage): The input mask as a PIL Image object.
|
||||
p (processing.StableDiffusionProcessing): An instance of the StableDiffusionProcessing class
|
||||
containing the processing parameters.
|
||||
|
||||
Returns:
|
||||
mask (Image.Image): The prepared mask as a PIL Image object.
|
||||
mask (PILImage): The prepared mask as a PIL Image object.
|
||||
"""
|
||||
mask = mask.convert("L")
|
||||
# FIXME : Properly fix blur
|
||||
@@ -257,7 +257,7 @@ def prepare_mask(
|
||||
return mask
|
||||
|
||||
|
||||
def base64_to_pil(base64str: Optional[str]) -> Optional[Image.Image]:
|
||||
def base64_to_pil(base64str: Optional[str]) -> Optional[PILImage]:
|
||||
"""
|
||||
Converts a base64 string to a PIL Image object.
|
||||
|
||||
@@ -267,7 +267,7 @@ def base64_to_pil(base64str: Optional[str]) -> Optional[Image.Image]:
|
||||
will return None.
|
||||
|
||||
Returns:
|
||||
Optional[Image.Image]: A PIL Image object created from the base64 string. If the input is None,
|
||||
Optional[PILImage]: A PIL Image object created from the base64 string. If the input is None,
|
||||
the function returns None.
|
||||
|
||||
Raises:
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
from typing import Tuple
|
||||
from numpy import uint8
|
||||
from numpy.typing import NDArray
|
||||
from insightface.app.common import Face as IFace
|
||||
from PIL import Image
|
||||
|
||||
PILImage = Image.Image
|
||||
CV2ImgU8 = NDArray[uint8]
|
||||
Face = IFace
|
||||
BoxCoords = Tuple[int, int, int, int]
|
||||
@@ -0,0 +1,39 @@
|
||||
from dataclasses import fields, is_dataclass
|
||||
from typing import *
|
||||
|
||||
|
||||
def dataclass_from_flat_list(cls: type, values: Tuple[Any, ...]) -> Any:
|
||||
if not is_dataclass(cls):
|
||||
raise TypeError(f"{cls} is not a dataclass")
|
||||
|
||||
idx = 0
|
||||
init_values = {}
|
||||
for field in fields(cls):
|
||||
if is_dataclass(field.type):
|
||||
inner_values = [values[idx + i] for i in range(len(fields(field.type)))]
|
||||
init_values[field.name] = field.type(*inner_values)
|
||||
idx += len(inner_values)
|
||||
else:
|
||||
value = values[idx]
|
||||
init_values[field.name] = value
|
||||
idx += 1
|
||||
return cls(**init_values)
|
||||
|
||||
|
||||
def dataclasses_from_flat_list(
|
||||
classes_mapping: List[type], values: Tuple[Any, ...]
|
||||
) -> List[Any]:
|
||||
instances = []
|
||||
idx = 0
|
||||
for cls in classes_mapping:
|
||||
num_fields = sum(
|
||||
len(fields(field.type)) if is_dataclass(field.type) else 1
|
||||
for field in fields(cls)
|
||||
)
|
||||
instance = dataclass_from_flat_list(cls, values[idx : idx + num_fields])
|
||||
instances.append(instance)
|
||||
idx += num_fields
|
||||
assert [
|
||||
isinstance(i, t) for i, t in zip(instances, classes_mapping)
|
||||
], "Instances should match types"
|
||||
return instances
|
||||
Reference in New Issue
Block a user