12 Commits

Author SHA1 Message Date
Tran Xen 42d1c75b68 Merge pull request #96 from glucauze/v1.2.7
v1.2.7 

    remove dill
    add warnings in model install and checking
    add gender selection in build
2023-09-10 16:06:47 +02:00
Tran Xen bba4845dbf add gender selection in build 2023-09-09 16:25:06 +02:00
Tran Xen c94d2aab4d v1.2.7 2023-09-09 10:51:36 +02:00
Tran Xen 0d0242e0ac Merge pull request #83 from glucauze/v1.2.6
fix inpainting in auto dev version
2023-08-31 14:46:25 +02:00
Tran Xen bf8ef5e9ec fix inpainting in auto dev version 2023-08-30 17:08:33 +02:00
Tran Xen 806086b533 Merge pull request #71 from glucauze/v1.2.5
add seed selection
2023-08-25 10:48:03 +02:00
Tran Xen 38f46d3f01 add seed selection 2023-08-24 23:56:03 +02:00
Tran Xen 9977168136 Merge pull request #68 from glucauze/v1.2.4
V1.2.4 :

Fix default settings by marking only managed field as do_not_save.

See the discussion here : #62
2023-08-24 19:35:23 +02:00
Tran Xen 2e718ee4ae fix default settings by not marking non managed fields as do_not_save 2023-08-24 19:20:31 +02:00
Tran Xen d0c56ae6ef remove override_settings in inpainting i2i_pp.py as they are not supported in some cases, i.e. sdnext for return_mask_composite and cause bugs 2023-08-24 15:47:24 +02:00
Tran Xen c4badb7894 Merge pull request #66 from glucauze/v1.2.3
fix install
2023-08-24 10:02:25 +02:00
Tran Xen 54adcc0fc6 fix install 2023-08-24 09:54:57 +02:00
21 changed files with 203 additions and 187 deletions
+10
View File
@@ -1,3 +1,13 @@
# 1.2.5
Allow seed selection in inpainting.
# 1.2.4
Fix default settings by marking only managed field as do_not_save.
See the discussion here : https://github.com/glucauze/sd-webui-faceswaplab/issues/62
# 1.2.3
Speed up ui : change the way default settings are manage by not storing them in ui-config.json
+26
View File
@@ -32,6 +32,32 @@ More on this here : https://glucauze.github.io/sd-webui-faceswaplab/
## Quick Start
Here are some gifs to explain (non cherry picked, just random pictures) :
## Simple Usage (roop like)
This use codeformer on all faces (including non swapped)
[simple.webm](https://github.com/glucauze/sd-webui-faceswaplab/assets/137925069/de00b685-d441-44f9-bae3-71cd7abef113)
## Advanced options
This is use to improve results. This use upscaling and codeformer only on swapped faces
[advanced.webm](https://github.com/glucauze/sd-webui-faceswaplab/assets/137925069/50630311-bd25-487f-871b-0a44eecd435d)
## Inpainting
This add inpainting on faces :
[inpainting.webm](https://github.com/glucauze/sd-webui-faceswaplab/assets/137925069/3d3508e9-5be4-4566-8c41-8301b2d08355)
## Build and use checkpoints :
[build.webm](https://github.com/glucauze/sd-webui-faceswaplab/assets/137925069/e84e9a3c-840d-4536-9fbb-09ed256406d7)
### Simple
1. Put a face in the reference.
+1
View File
@@ -48,6 +48,7 @@ class InpaintingOptions(BaseModel):
inpainting_model: str = Field(
description="Inpainting model", examples=["Current"], default="Current"
)
inpainting_seed: int = Field(description="Inpainting Seed", ge=-1, default=-1)
class InswappperOptions(BaseModel):
Binary file not shown.
+9 -4
View File
@@ -2,12 +2,11 @@ import launch
import os
import sys
import pkg_resources
from modules import shared
from packaging.version import parse
def check_install() -> None:
use_gpu = not getattr(shared.cmd_opts, "use-cpu", False)
use_gpu = True
if use_gpu and sys.platform != "darwin":
print("Faceswaplab : Use GPU requirements")
@@ -63,5 +62,11 @@ def check_install() -> None:
import timeit
check_time = timeit.timeit(check_install, number=1)
print(check_time)
try:
check_time = timeit.timeit(check_install, number=1)
print(check_time)
except Exception as e:
print("FaceswapLab install failed", e)
print(
"You can try to install dependencies manually by activating venv and installing requirements.txt or requirements-gpu.txt"
)
-5
View File
@@ -8,8 +8,3 @@ def preload(parser: ArgumentParser) -> None:
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
help="Set the log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)",
)
parser.add_argument(
"--faceswaplab_gpu",
action="store_true",
help="Enable GPU if set, disable if not set",
)
-1
View File
@@ -1,5 +1,4 @@
cython
dill
ifnude
insightface==0.7.3
onnx>=1.14.0
-1
View File
@@ -1,6 +1,5 @@
protobuf>=3.20.2
cython
dill
ifnude
insightface==0.7.3
onnx>=1.14.0
+20 -15
View File
@@ -25,21 +25,26 @@ def check_configuration() -> None:
model_path = os.path.join(models_dir, model_name)
def download(url: str, path: str) -> None:
request = urllib.request.urlopen(url)
total = int(request.headers.get("Content-Length", 0))
with tqdm(
total=total,
desc="Downloading inswapper model",
unit="B",
unit_scale=True,
unit_divisor=1024,
) as progress:
urllib.request.urlretrieve(
url,
path,
reporthook=lambda count, block_size, total_size: progress.update(
block_size
),
try:
request = urllib.request.urlopen(url)
total = int(request.headers.get("Content-Length", 0))
with tqdm(
total=total,
desc="Downloading inswapper model",
unit="B",
unit_scale=True,
unit_divisor=1024,
) as progress:
urllib.request.urlretrieve(
url,
path,
reporthook=lambda count, block_size, total_size: progress.update(
block_size
),
)
except:
logger.error(
"Failed to download inswapper_128.onnx model, please download it manually and put it in the (<sdwebui>/models/faceswaplab/inswapper_128.onnx) directory"
)
os.makedirs(models_dir, exist_ok=True)
-3
View File
@@ -111,9 +111,6 @@ class FaceSwapScript(scripts.Script):
# If the order is modified, the before_process should be changed accordingly.
components = components + post_processing
# Ask sd to not store in ui-config.json
for component in components:
setattr(component, "do_not_save_to_config", True)
return components
+1 -1
View File
@@ -16,7 +16,7 @@ REFERENCE_PATH = os.path.join(
)
# Defining the version flag for the application
VERSION_FLAG: str = "v1.2.3"
VERSION_FLAG: str = "v1.2.7"
# Defining the path for 'sd-webui-faceswaplab' inside the 'extensions' directory
EXTENSION_PATH = os.path.join("extensions", "sd-webui-faceswaplab")
@@ -12,6 +12,7 @@ class InpaintingOptions:
inpainting_steps: int = 20
inpainting_sampler: str = "Euler"
inpainting_model: str = "Current"
inpainting_seed: int = -1
@staticmethod
def from_gradio(components: List[gr.components.Component]) -> "InpaintingOptions":
@@ -38,4 +39,5 @@ class InpaintingOptions:
inpainting_steps=dto.inpainting_steps,
inpainting_sampler=dto.inpainting_sampler,
inpainting_model=dto.inpainting_model,
inpainting_seed=dto.inpainting_seed,
)
+4 -10
View File
@@ -52,6 +52,7 @@ inpainting_steps : {options.inpainting_steps}
)
i2i_kwargs = {
"init_images": [img],
"sampler_name": options.inpainting_sampler,
"do_not_save_samples": True,
"steps": options.inpainting_steps,
@@ -63,23 +64,16 @@ inpainting_steps : {options.inpainting_steps}
"prompt": prompt,
"negative_prompt": negative_prompt,
"denoising_strength": options.inpainting_denoising_strengh,
"override_settings": {
"return_mask_composite": False,
"save_images_before_face_restoration": False,
"save_images_before_highres_fix": False,
"save_images_before_color_correction": False,
"save_mask": False,
"save_mask_composite": False,
"samples_save": False,
},
"seed": options.inpainting_seed,
}
current_model_checkpoint = shared.opts.sd_model_checkpoint
if options.inpainting_model and options.inpainting_model != "Current":
# Change checkpoint
shared.opts.sd_model_checkpoint = options.inpainting_model
sd_models.select_checkpoint
sd_models.load_model()
i2i_p = StableDiffusionProcessingImg2Img([img], **i2i_kwargs)
i2i_p = StableDiffusionProcessingImg2Img(**i2i_kwargs)
i2i_processed = processing.process_images(i2i_p)
if options.inpainting_model and options.inpainting_model != "Current":
# Restore checkpoint
@@ -14,7 +14,6 @@ from scripts.faceswaplab_utils import imgutils
from scripts.faceswaplab_utils.models_utils import get_swap_models
import traceback
import dill as pickle # will be removed in future versions
from scripts.faceswaplab_swapping import swapper
from pprint import pformat
import re
@@ -40,6 +39,7 @@ def sanitize_name(name: str) -> str:
def build_face_checkpoint_and_save(
images: List[PILImage],
name: str,
gender: Gender = Gender.AUTO,
overwrite: bool = False,
path: Optional[str] = None,
) -> Optional[PILImage]:
@@ -65,7 +65,7 @@ def build_face_checkpoint_and_save(
logger.error("No source faces found")
return None
blended_face: Optional[Face] = swapper.blend_faces(faces)
blended_face: Optional[Face] = swapper.blend_faces(faces, gender=gender)
preview_path = os.path.join(
scripts.basedir(), "extensions", "sd-webui-faceswaplab", "references"
)
@@ -174,20 +174,13 @@ def load_face(name: str) -> Optional[Face]:
if filename.endswith(".pkl"):
logger.warning(
"Pkl files for faces are deprecated to enhance safety, they will be unsupported in future versions."
"Pkl files for faces are deprecated to enhance safety, you need to convert them"
)
logger.warning("The file will be converted to .safetensors")
logger.warning(
"You can also use this script https://gist.github.com/glucauze/4a3c458541f2278ad801f6625e5b9d3d"
)
with open(filename, "rb") as file:
logger.info("Load pkl")
face = Face(pickle.load(file))
logger.warning(
"Convert to safetensors, you can remove the pkl version once you have ensured that the safetensor is working"
)
save_face(face, filename.replace(".pkl", ".safetensors"))
return face
return None
elif filename.endswith(".safetensors"):
face = {}
+12 -3
View File
@@ -33,7 +33,7 @@ from scripts.faceswaplab_postprocessing.postprocessing_options import (
PostProcessingOptions,
)
from scripts.faceswaplab_utils.models_utils import get_current_swap_model
from scripts.faceswaplab_utils.typing import CV2ImgU8, PILImage, Face
from scripts.faceswaplab_utils.typing import CV2ImgU8, Gender, PILImage, Face
from scripts.faceswaplab_inpainting.i2i_pp import img2img_diffusion
from modules import shared
import onnxruntime
@@ -559,7 +559,7 @@ def get_faces_from_img_files(images: List[PILImage]) -> List[Face]:
return faces
def blend_faces(faces: List[Face]) -> Optional[Face]:
def blend_faces(faces: List[Face], gender: Gender = Gender.AUTO) -> Optional[Face]:
"""
Blends the embeddings of multiple faces into a single face.
@@ -587,10 +587,19 @@ def blend_faces(faces: List[Face]) -> Optional[Face]:
# Compute the mean of all embeddings
blended_embedding = np.mean(embeddings, axis=0)
if gender == Gender.AUTO:
int_gender: int = faces[0].gender # type: ignore
else:
int_gender: int = gender.value
assert -1 < int_gender < 2, "wrong gender"
logger.info("Int Gender : %s", int_gender)
# Create a new Face object using the properties of the first face in the list
# Assign the blended embedding to the blended Face object
blended = ISFace(
embedding=blended_embedding, gender=faces[0].gender, age=faces[0].age
embedding=blended_embedding, gender=int_gender, age=faces[0].age
)
return blended
@@ -4,65 +4,71 @@ from modules import sd_models, sd_samplers
from scripts.faceswaplab_utils.sd_utils import get_sd_option
def face_inpainting_ui(
name: str, id_prefix: str = "faceswaplab", description: str = ""
) -> List[gr.components.Component]:
with gr.Accordion(name, open=False):
gr.Markdown(description)
inpainting_denoising_strength = gr.Slider(
0,
def face_inpainting_ui(id_prefix: str = "faceswaplab") -> List[gr.components.Component]:
inpainting_denoising_strength = gr.Slider(
0,
1,
0,
step=0.01,
elem_id=f"{id_prefix}_pp_inpainting_denoising_strength",
label="Denoising strenght",
)
inpainting_denoising_prompt = gr.Textbox(
get_sd_option(
"faceswaplab_pp_default_inpainting_prompt", "Portrait of a [gender]"
),
elem_id=f"{id_prefix}_pp_inpainting_denoising_prompt",
label="Inpainting prompt use [gender] instead of men or woman",
)
inpainting_denoising_negative_prompt = gr.Textbox(
get_sd_option("faceswaplab_pp_default_inpainting_negative_prompt", "blurry"),
elem_id=f"{id_prefix}_pp_inpainting_denoising_neg_prompt",
label="Inpainting negative prompt use [gender] instead of men or woman",
)
with gr.Row():
samplers_names = [s.name for s in sd_samplers.all_samplers]
inpainting_sampler = gr.Dropdown(
choices=samplers_names,
value=[samplers_names[0]],
label="Inpainting Sampler",
elem_id=f"{id_prefix}_pp_inpainting_sampler",
)
inpainting_denoising_steps = gr.Slider(
1,
0,
step=0.01,
elem_id=f"{id_prefix}_pp_inpainting_denoising_strength",
label="Denoising strenght",
150,
20,
step=1,
label="Inpainting steps",
elem_id=f"{id_prefix}_pp_inpainting_steps",
)
inpainting_denoising_prompt = gr.Textbox(
get_sd_option(
"faceswaplab_pp_default_inpainting_prompt", "Portrait of a [gender]"
),
elem_id=f"{id_prefix}_pp_inpainting_denoising_prompt",
label="Inpainting prompt use [gender] instead of men or woman",
)
inpainting_denoising_negative_prompt = gr.Textbox(
get_sd_option(
"faceswaplab_pp_default_inpainting_negative_prompt", "blurry"
),
elem_id=f"{id_prefix}_pp_inpainting_denoising_neg_prompt",
label="Inpainting negative prompt use [gender] instead of men or woman",
)
with gr.Row():
samplers_names = [s.name for s in sd_samplers.all_samplers]
inpainting_sampler = gr.Dropdown(
choices=samplers_names,
value=[samplers_names[0]],
label="Inpainting Sampler",
elem_id=f"{id_prefix}_pp_inpainting_sampler",
)
inpainting_denoising_steps = gr.Slider(
1,
150,
20,
step=1,
label="Inpainting steps",
elem_id=f"{id_prefix}_pp_inpainting_steps",
)
inpaiting_model = gr.Dropdown(
choices=["Current"] + sd_models.checkpoint_tiles(),
default="Current",
label="sd model (experimental)",
elem_id=f"{id_prefix}_pp_inpainting_sd_model",
)
inpaiting_model = gr.Dropdown(
choices=["Current"] + sd_models.checkpoint_tiles(),
default="Current",
label="sd model (experimental)",
elem_id=f"{id_prefix}_pp_inpainting_sd_model",
)
inpaiting_seed = gr.Number(
label="Inpainting seed",
value=0,
minimum=0,
precision=0,
elem_id=f"{id_prefix}_pp_inpainting_seed",
)
gradio_components: List[gr.components.Component] = [
inpainting_denoising_strength,
inpainting_denoising_prompt,
inpainting_denoising_negative_prompt,
inpainting_denoising_steps,
inpainting_sampler,
inpaiting_model,
]
gradio_components: List[gr.components.Component] = [
inpainting_denoising_strength,
inpainting_denoising_prompt,
inpainting_denoising_negative_prompt,
inpainting_denoising_steps,
inpainting_sampler,
inpaiting_model,
inpaiting_seed,
]
return gradio_components
for component in gradio_components:
setattr(component, "do_not_save_to_config", True)
return gradio_components
@@ -1,9 +1,9 @@
from typing import List
import gradio as gr
import modules
from modules import shared, sd_models
from modules import shared
from scripts.faceswaplab_postprocessing.postprocessing_options import InpaintingWhen
from scripts.faceswaplab_utils.sd_utils import get_sd_option
from scripts.faceswaplab_ui.faceswaplab_inpainting_ui import face_inpainting_ui
def postprocessing_ui() -> List[gr.components.Component]:
@@ -66,9 +66,10 @@ def postprocessing_ui() -> List[gr.components.Component]:
label="Upscaler visibility (if scale = 1)",
elem_id="faceswaplab_pp_upscaler_visibility",
)
with gr.Accordion(f"Post Inpainting", open=True):
with gr.Accordion(label="Global-Inpainting (all faces)", open=False):
gr.Markdown(
"""Inpainting sends image to inpainting with a mask on face (once for each faces)."""
"Inpainting sends image to inpainting with a mask on face (once for each faces)."
)
inpainting_when = gr.Dropdown(
elem_id="faceswaplab_pp_inpainting_when",
@@ -76,52 +77,8 @@ def postprocessing_ui() -> List[gr.components.Component]:
value=[InpaintingWhen.BEFORE_RESTORE_FACE.value],
label="Enable/When",
)
inpainting_denoising_strength = gr.Slider(
0,
1,
0,
step=0.01,
elem_id="faceswaplab_pp_inpainting_denoising_strength",
label="Denoising strenght (will send face to img2img after processing)",
)
global_inpainting = face_inpainting_ui("faceswaplab_gpp")
inpainting_denoising_prompt = gr.Textbox(
get_sd_option(
"faceswaplab_pp_default_inpainting_prompt", "Portrait of a [gender]"
),
elem_id="faceswaplab_pp_inpainting_denoising_prompt",
label="Inpainting prompt use [gender] instead of men or woman",
)
inpainting_denoising_negative_prompt = gr.Textbox(
get_sd_option(
"faceswaplab_pp_default_inpainting_negative_prompt", "blurry"
),
elem_id="faceswaplab_pp_inpainting_denoising_neg_prompt",
label="Inpainting negative prompt use [gender] instead of men or woman",
)
with gr.Row():
samplers_names = [s.name for s in modules.sd_samplers.all_samplers] # type: ignore
inpainting_sampler = gr.Dropdown(
choices=samplers_names,
value=[samplers_names[0]],
label="Inpainting Sampler",
elem_id="faceswaplab_pp_inpainting_sampler",
)
inpainting_denoising_steps = gr.Slider(
1,
150,
20,
step=1,
label="Inpainting steps",
elem_id="faceswaplab_pp_inpainting_steps",
)
inpaiting_model = gr.Dropdown(
choices=["Current"] + sd_models.checkpoint_tiles(),
default="Current",
label="sd model (experimental)",
elem_id="faceswaplab_pp_inpainting_sd_model",
)
components = [
face_restorer_name,
face_restorer_visibility,
@@ -130,13 +87,7 @@ def postprocessing_ui() -> List[gr.components.Component]:
upscaler_scale,
upscaler_visibility,
inpainting_when,
inpainting_denoising_strength,
inpainting_denoising_prompt,
inpainting_denoising_negative_prompt,
inpainting_denoising_steps,
inpainting_sampler,
inpaiting_model,
]
] + global_inpainting
# Ask sd to not store in ui-config.json
for component in components:
+13 -3
View File
@@ -137,7 +137,7 @@ def analyse_faces(image: PILImage, det_threshold: float = 0.5) -> Optional[str]:
def build_face_checkpoint_and_save(
batch_files: List[gr.File], name: str, overwrite: bool
batch_files: List[gr.File], name: str, str_gender: str, overwrite: bool
) -> PILImage:
"""
Builds a face checkpoint using the provided image files, performs face swapping,
@@ -156,10 +156,13 @@ def build_face_checkpoint_and_save(
if not batch_files:
logger.error("No face found")
return None # type: ignore (Optional not really supported by old gradio)
gender = getattr(Gender, str_gender)
logger.info("Choosen gender : %s", gender)
images: list[PILImage] = [Image.open(file.name) for file in batch_files] # type: ignore
preview_image: PILImage | None = (
face_checkpoints.build_face_checkpoint_and_save(
images=images, name=name, overwrite=overwrite
images=images, name=name, overwrite=overwrite, gender=gender
)
)
except Exception as e:
@@ -266,6 +269,13 @@ def tools_ui() -> None:
label="Name of the character",
elem_id="faceswaplab_build_character_name",
)
build_gender = gr.Dropdown(
value=Gender.AUTO.name,
choices=[e.name for e in Gender],
placeholder="Gender of the character",
label="Gender of the character",
elem_id="faceswaplab_build_character_gender",
)
build_overwrite = gr.Checkbox(
False,
placeholder="overwrite",
@@ -387,7 +397,7 @@ def tools_ui() -> None:
compare_btn.click(compare, inputs=[img1, img2], outputs=[compare_result_text])
generate_checkpoint_btn.click(
build_face_checkpoint_and_save,
inputs=[build_batch_files, build_name, build_overwrite],
inputs=[build_batch_files, build_name, build_gender, build_overwrite],
outputs=[preview],
)
extract_btn.click(
+16 -15
View File
@@ -80,7 +80,7 @@ def faceswap_unit_advanced_options(
elem_id=f"{id_prefix}_face{unit_num}_erosion_factor",
)
return [
components = [
face_restorer_name,
face_restorer_visibility,
codeformer_weight,
@@ -91,6 +91,11 @@ def faceswap_unit_advanced_options(
erosion_factor,
]
for component in components:
setattr(component, "do_not_save_to_config", True)
return components
def faceswap_unit_ui(
is_img2img: bool, unit_num: int = 1, id_prefix: str = "faceswaplab"
@@ -244,19 +249,19 @@ Otherwise, read the [doc](https://glucauze.github.io/sd-webui-faceswaplab/doc/)
elem_id=f"{id_prefix}_face{unit_num}_min_ref_similarity",
)
pre_inpainting = face_inpainting_ui(
name="Pre-Inpainting (Before swapping)",
id_prefix=f"{id_prefix}_face{unit_num}_preinpainting",
description="Pre-inpainting sends face to inpainting before swapping",
)
with gr.Accordion(label="Pre-Inpainting (before swapping)", open=False):
gr.Markdown("Pre-inpainting sends face to inpainting before swapping")
pre_inpainting = face_inpainting_ui(
id_prefix=f"{id_prefix}_face{unit_num}_preinpainting",
)
options = faceswap_unit_advanced_options(is_img2img, unit_num, id_prefix)
post_inpainting = face_inpainting_ui(
name="Post-Inpainting (After swapping)",
id_prefix=f"{id_prefix}_face{unit_num}_postinpainting",
description="Post-inpainting sends face to inpainting after swapping",
)
with gr.Accordion(label="Post-Inpainting (After swapping)", open=False):
gr.Markdown("Pre-inpainting sends face to inpainting before swapping")
post_inpainting = face_inpainting_ui(
id_prefix=f"{id_prefix}_face{unit_num}_postinpainting",
)
gradio_components: List[gr.components.Component] = (
[
@@ -281,10 +286,6 @@ Otherwise, read the [doc](https://glucauze.github.io/sd-webui-faceswaplab/doc/)
+ post_inpainting
)
# Ask sd to not store in ui-config.json
for component in gradio_components:
setattr(component, "do_not_save_to_config", True)
# If changed, you need to change FaceSwapUnitSettings accordingly
# ORDER of parameters is IMPORTANT. It should match the result of FaceSwapUnitSettings
return gradio_components
+9 -3
View File
@@ -72,10 +72,16 @@ def get_current_swap_model() -> str:
models = get_swap_models()
model = models[0] if len(models) else None
logger.info("Try to use model : %s", model)
if not os.path.isfile(model): # type: ignore
logger.error("The model %s cannot be found or loaded", model)
try:
if not model or not os.path.isfile(model): # type: ignore
logger.error("The model %s cannot be found or loaded", model)
raise FileNotFoundError(
"No faceswap model found. Please add it to the faceswaplab directory. Ensure the model is in the proper directory (<sdwebui>/models/faceswaplab/inswapper_128.onnx)"
)
except:
raise FileNotFoundError(
"No faceswap model found. Please add it to the faceswaplab directory."
"No faceswap model found. Please add it to the faceswaplab directory. Ensure the model is in the proper directory (<sdwebui>/models/faceswaplab/inswapper_128.onnx)"
)
assert model is not None
return model
+7
View File
@@ -3,8 +3,15 @@ from numpy import uint8
from insightface.app.common import Face as IFace
from PIL import Image
import numpy as np
from enum import Enum
PILImage = Image.Image
CV2ImgU8 = np.ndarray[int, np.dtype[uint8]]
Face = IFace
BoxCoords = Tuple[int, int, int, int]
class Gender(Enum):
AUTO = -1
FEMALE = 0
MALE = 1