diff --git a/cli/api-txt2img.py b/cli/api-txt2img.py index fe085e7f1..89d84be80 100755 --- a/cli/api-txt2img.py +++ b/cli/api-txt2img.py @@ -49,7 +49,7 @@ def generate(args): # pylint: disable=redefined-outer-name options['width'] = int(args.width) options['height'] = int(args.height) if args.faces: - options['restore_faces'] = args.faces + options['detailer'] = args.detailer options['denoising_strength'] = 0.5 options['hr_sampler_name'] = args.sampler data = post('/sdapi/v1/txt2img', options) @@ -75,7 +75,7 @@ if __name__ == "__main__": parser.add_argument('--height', required=False, default=512, help='image height') parser.add_argument('--steps', required=False, default=20, help='number of steps') parser.add_argument('--seed', required=False, default=-1, help='initial seed') - parser.add_argument('--faces', action='store_true', help='restore faces') + parser.add_argument('--detailer', action='store_true', help='run detailer') parser.add_argument('--sampler', required=False, default='Euler a', help='sampler name') parser.add_argument('--output', required=False, default=None, help='output image file') parser.add_argument('--model', required=False, help='model name') diff --git a/cli/create-previews.py b/cli/create-previews.py index 58c2136df..fd1d81a7f 100755 --- a/cli/create-previews.py +++ b/cli/create-previews.py @@ -46,7 +46,7 @@ options = Map({ }, # generate params 'generate': { - 'restore_faces': True, + 'detailer': True, 'prompt': '', 'negative_prompt': 'foggy, blurry, blurred, duplicate, ugly, mutilated, mutation, mutated, out of frame, bad anatomy, disfigured, deformed, censored, low res, low resolution, watermark, text, poorly drawn face, poorly drawn hands, signature', 'steps': 20, diff --git a/cli/generate.json b/cli/generate.json index 0bbcb1439..c379a6f64 100644 --- a/cli/generate.json +++ b/cli/generate.json @@ -8,7 +8,7 @@ }, "generate": { - "restore_faces": true, + "detailer": true, "prompt": "dynamic", "negative_prompt": "foggy, blurry, blurred, duplicate, ugly, mutilated, mutation, mutated, out of frame, bad anatomy, disfigured, deformed, censored, low res, watermark, text, poorly drawn face, signature", "steps": 30, diff --git a/cli/generate.py b/cli/generate.py index b8d5cac66..9b784373a 100755 --- a/cli/generate.py +++ b/cli/generate.py @@ -230,7 +230,7 @@ def args(): # parse cmd arguments parser.add_argument('--style', type = str, default = 'random', required = False, help = 'image style, used to guide dynamic prompt when prompt is not provided') parser.add_argument('--suffix', type = str, default = 'random', required = False, help = 'style suffix, used to guide dynamic prompt when prompt is not provided') parser.add_argument('--place', type = str, default = 'random', required = False, help = 'place locator, used to guide dynamic prompt when prompt is not provided') - parser.add_argument('--faces', default = False, action='store_true', help = 'restore faces during upscaling') + parser.add_argument('--detailer', default = False, action='store_true', help = 'run detailer') parser.add_argument('--steps', type = int, default = 0, required = False, help = 'number of steps') parser.add_argument('--batch', type = int, default = 0, required = False, help = 'batch size, limited by gpu vram') parser.add_argument('--n', type = int, default = 0, required = False, help = 'number of iterations') @@ -299,7 +299,7 @@ def args(): # parse cmd arguments _dynamic = prompt(params) sd.paths.root = params.path if params.path != '' else sd.paths.root - sd.generate.restore_faces = params.faces if params.faces is not None else sd.generate.restore_faces + sd.generate.detailer = params.detailer if params.detailer is not None else sd.generate.detailer sd.generate.seed = params.seed if params.seed > 0 else sd.generate.seed sd.generate.sampler_name = params.sampler if params.sampler != 'random' else sd.generate.sampler_name sd.generate.batch_size = params.batch if params.batch > 0 else sd.generate.batch_size @@ -309,7 +309,7 @@ def args(): # parse cmd arguments sd.generate.height = params.height if params.height > 0 else sd.generate.height sd.generate.steps = params.steps if params.steps > 0 else sd.generate.steps sd.upscale.upscaling_resize = params.upscale if params.upscale > 0 else sd.upscale.upscaling_resize - sd.upscale.codeformer_visibility = 1 if params.faces else sd.upscale.codeformer_visibility + sd.upscale.codeformer_visibility = 1 if params.detailer else sd.upscale.codeformer_visibility sd.options.sd_vae = params.vae if params.vae != '' else sd.options.sd_vae sd.options.sd_model_checkpoint = params.model if params.model != '' else sd.options.sd_model_checkpoint sd.upscale.upscaler_1 = 'SwinIR_4x' if params.upscale > 1 else sd.upscale.upscaler_1 diff --git a/extensions-builtin/sd-extension-system-info b/extensions-builtin/sd-extension-system-info index c88e83d40..5267e3264 160000 --- a/extensions-builtin/sd-extension-system-info +++ b/extensions-builtin/sd-extension-system-info @@ -1 +1 @@ -Subproject commit c88e83d403e1cae478df870fa2dd277d2028dc34 +Subproject commit 5267e326499cb996c8804a02e8844a2f6d77a0d4 diff --git a/javascript/contextMenus.js b/javascript/contextMenus.js index a0424f266..b15615a2a 100644 --- a/javascript/contextMenus.js +++ b/javascript/contextMenus.js @@ -129,7 +129,7 @@ async function initContextMenu() { id = `#${tab}_reprocess`; appendContextMenuOption(id, 'Decode full quality', () => reprocessClick(`${tab}`, 'reprocess_decode'), true); appendContextMenuOption(id, 'Refine & HiRes pass', () => reprocessClick(`${tab}`, 'reprocess_refine'), true); - appendContextMenuOption(id, 'Face restore', () => reprocessClick(`${tab}`, 'reprocess_face'), true); + appendContextMenuOption(id, 'Detailer pass', () => reprocessClick(`${tab}`, 'reprocess_detail'), true); } addContextMenuEventListener(); } diff --git a/modules/api/api.py b/modules/api/api.py index 890a18733..4b7c750fd 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -69,7 +69,7 @@ class Api: self.add_api_route("/sdapi/v1/upscalers", endpoints.get_upscalers, methods=["GET"], response_model=List[models.ItemUpscaler]) self.add_api_route("/sdapi/v1/sd-models", endpoints.get_sd_models, methods=["GET"], response_model=List[models.ItemModel]) self.add_api_route("/sdapi/v1/hypernetworks", endpoints.get_hypernetworks, methods=["GET"], response_model=List[models.ItemHypernetwork]) - self.add_api_route("/sdapi/v1/face-restorers", endpoints.get_face_restorers, methods=["GET"], response_model=List[models.ItemFaceRestorer]) + self.add_api_route("/sdapi/v1/face-restorers", endpoints.get_detailers, methods=["GET"], response_model=List[models.ItemDetailer]) self.add_api_route("/sdapi/v1/prompt-styles", endpoints.get_prompt_styles, methods=["GET"], response_model=List[models.ItemStyle]) self.add_api_route("/sdapi/v1/embeddings", endpoints.get_embeddings, methods=["GET"], response_model=models.ResEmbeddings) self.add_api_route("/sdapi/v1/sd-vae", endpoints.get_sd_vaes, methods=["GET"], response_model=List[models.ItemVae]) diff --git a/modules/api/endpoints.py b/modules/api/endpoints.py index 63c5764c0..310abed1e 100644 --- a/modules/api/endpoints.py +++ b/modules/api/endpoints.py @@ -23,8 +23,8 @@ def get_sd_models(): def get_hypernetworks(): return [{"name": name, "path": shared.hypernetworks[name]} for name in shared.hypernetworks] -def get_face_restorers(): - return [{"name":x.name(), "cmd_dir": getattr(x, "cmd_dir", None)} for x in shared.face_restorers] +def get_detailers(): + return [{"name":x.name(), "cmd_dir": getattr(x, "cmd_dir", None)} for x in shared.detailers] def get_prompt_styles(): return [{ 'name': v.name, 'prompt': v.prompt, 'negative_prompt': v.negative_prompt, 'extra': v.extra, 'filename': v.filename, 'preview': v.preview} for v in shared.prompt_styles.styles.values()] diff --git a/modules/api/models.py b/modules/api/models.py index 3c5fd6146..6e658b484 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -110,7 +110,7 @@ class ItemHypernetwork(BaseModel): name: str = Field(title="Name") path: Optional[str] = Field(title="Path") -class ItemFaceRestorer(BaseModel): +class ItemDetailer(BaseModel): name: str = Field(title="Name") cmd_dir: Optional[str] = Field(title="Path") diff --git a/modules/api/process.py b/modules/api/process.py index 6dd5e701e..505ba1374 100644 --- a/modules/api/process.py +++ b/modules/api/process.py @@ -107,7 +107,7 @@ class APIProcess(): return ResMask(mask=image) def post_face(self, req: ReqFace): - from scripts.face_details import yolo # pylint: disable=no-name-in-module + from scripts.detailer import yolo # pylint: disable=no-name-in-module image = decode_base64_to_image(req.image) shared.state.begin('API-FACE', api=True) images = [] diff --git a/modules/control/run.py b/modules/control/run.py index 4f8b48cd2..01779119e 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -60,7 +60,7 @@ def control_run(state: str = '', steps: int = 20, sampler_index: int = None, seed: int = -1, subseed: int = -1, subseed_strength: float = 0, seed_resize_from_h: int = -1, seed_resize_from_w: int = -1, cfg_scale: float = 6.0, clip_skip: float = 1.0, image_cfg_scale: float = 6.0, diffusers_guidance_rescale: float = 0.7, pag_scale: float = 0.0, pag_adaptive: float = 0.5, cfg_end: float = 1.0, - full_quality: bool = True, restore_faces: bool = False, tiling: bool = False, hidiffusion: bool = False, + full_quality: bool = True, detailer: bool = False, tiling: bool = False, hidiffusion: bool = False, hdr_mode: int = 0, hdr_brightness: float = 0, hdr_color: float = 0, hdr_sharpen: float = 0, hdr_clamp: bool = False, hdr_boundary: float = 4.0, hdr_threshold: float = 0.95, hdr_maximize: bool = False, hdr_max_center: float = 0.6, hdr_max_boundry: float = 1.0, hdr_color_picker: str = None, hdr_tint_ratio: float = 0, resize_mode_before: int = 0, resize_name_before: str = 'None', resize_context_before: str = 'None', width_before: int = 512, height_before: int = 512, scale_by_before: float = 1.0, selected_scale_tab_before: int = 0, @@ -129,7 +129,7 @@ def control_run(state: str = '', pag_scale = pag_scale, pag_adaptive = pag_adaptive, full_quality = full_quality, - restore_faces = restore_faces, + detailer = detailer, tiling = tiling, hidiffusion = hidiffusion, # resize diff --git a/modules/detailer.py b/modules/detailer.py new file mode 100644 index 000000000..86eae2230 --- /dev/null +++ b/modules/detailer.py @@ -0,0 +1,17 @@ +from modules import shared + + +class Detailer: + def name(self): + return "None" + + def restore(self, np_image): + return np_image + + +def detail(np_image, p=None): + detailers = [x for x in shared.detailers if x.name() == shared.opts.detailer_model or shared.opts.detailer_model is None] + if len(detailers) == 0: + return np_image + detailer = detailers[0] + return detailer.restore(np_image, p) diff --git a/modules/face/__init__.py b/modules/face/__init__.py index d5d51e647..aae3cdaa2 100644 --- a/modules/face/__init__.py +++ b/modules/face/__init__.py @@ -134,7 +134,7 @@ class Script(scripts.Script): from modules.face.insightface import get_app app=get_app('buffalo_l') from modules.face.faceswap import face_swap - if shared.opts.save_images_before_face_restoration and not p.do_not_save_samples: + if shared.opts.save_images_before_detailer and not p.do_not_save_samples: for i, image in enumerate(processed.images): info = processing.create_infotext(p, index=i) images.save_image(image, path=p.outpath_samples, seed=p.all_seeds[i], prompt=p.all_prompts[i], info=info, p=p, suffix="-before-faceswap") diff --git a/modules/face_restoration.py b/modules/face_restoration.py deleted file mode 100644 index d17191fdf..000000000 --- a/modules/face_restoration.py +++ /dev/null @@ -1,17 +0,0 @@ -from modules import shared - - -class FaceRestoration: - def name(self): - return "None" - - def restore(self, np_image): - return np_image - - -def restore_faces(np_image, p=None): - face_restorers = [x for x in shared.face_restorers if x.name() == shared.opts.face_restoration_model or shared.opts.face_restoration_model is None] - if len(face_restorers) == 0: - return np_image - face_restorer = face_restorers[0] - return face_restorer.restore(np_image, p) diff --git a/modules/img2img.py b/modules/img2img.py index 6e2986608..2417c01b3 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -120,7 +120,7 @@ def img2img(id_task: str, state: str, mode: int, sampler_index, mask_blur, mask_alpha, inpainting_fill, - full_quality, restore_faces, tiling, hidiffusion, + full_quality, detailer, tiling, hidiffusion, n_iter, batch_size, cfg_scale, image_cfg_scale, diffusers_guidance_rescale, @@ -144,7 +144,7 @@ def img2img(id_task: str, state: str, mode: int, shared.log.warning('Model not loaded') return [], '', '', 'Error: model not loaded' - debug(f'img2img: id_task={id_task}|mode={mode}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|init_img={init_img}|sketch={sketch}|init_img_with_mask={init_img_with_mask}|inpaint_color_sketch={inpaint_color_sketch}|inpaint_color_sketch_orig={inpaint_color_sketch_orig}|init_img_inpaint={init_img_inpaint}|init_mask_inpaint={init_mask_inpaint}|steps={steps}|sampler_index={sampler_index}||mask_blur={mask_blur}|mask_alpha={mask_alpha}|inpainting_fill={inpainting_fill}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|hidiffusion={hidiffusion}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|image_cfg_scale={image_cfg_scale}|clip_skip={clip_skip}|denoising_strength={denoising_strength}|seed={seed}|subseed{subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}|selected_scale_tab={selected_scale_tab}|height={height}|width={width}|scale_by={scale_by}|resize_mode={resize_mode}|resize_name={resize_name}|resize_context={resize_context}|inpaint_full_res={inpaint_full_res}|inpaint_full_res_padding={inpaint_full_res_padding}|inpainting_mask_invert={inpainting_mask_invert}|img2img_batch_files={img2img_batch_files}|img2img_batch_input_dir={img2img_batch_input_dir}|img2img_batch_output_dir={img2img_batch_output_dir}|img2img_batch_inpaint_mask_dir={img2img_batch_inpaint_mask_dir}|override_settings_texts={override_settings_texts}') + debug(f'img2img: id_task={id_task}|mode={mode}|prompt={prompt}|negative_prompt={negative_prompt}|prompt_styles={prompt_styles}|init_img={init_img}|sketch={sketch}|init_img_with_mask={init_img_with_mask}|inpaint_color_sketch={inpaint_color_sketch}|inpaint_color_sketch_orig={inpaint_color_sketch_orig}|init_img_inpaint={init_img_inpaint}|init_mask_inpaint={init_mask_inpaint}|steps={steps}|sampler_index={sampler_index}||mask_blur={mask_blur}|mask_alpha={mask_alpha}|inpainting_fill={inpainting_fill}|full_quality={full_quality}|detailer={detailer}|tiling={tiling}|hidiffusion={hidiffusion}|n_iter={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|image_cfg_scale={image_cfg_scale}|clip_skip={clip_skip}|denoising_strength={denoising_strength}|seed={seed}|subseed{subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}|selected_scale_tab={selected_scale_tab}|height={height}|width={width}|scale_by={scale_by}|resize_mode={resize_mode}|resize_name={resize_name}|resize_context={resize_context}|inpaint_full_res={inpaint_full_res}|inpaint_full_res_padding={inpaint_full_res_padding}|inpainting_mask_invert={inpainting_mask_invert}|img2img_batch_files={img2img_batch_files}|img2img_batch_input_dir={img2img_batch_input_dir}|img2img_batch_output_dir={img2img_batch_output_dir}|img2img_batch_inpaint_mask_dir={img2img_batch_inpaint_mask_dir}|override_settings_texts={override_settings_texts}') if mode == 5: if img2img_batch_files is None or len(img2img_batch_files) == 0: @@ -225,7 +225,7 @@ def img2img(id_task: str, state: str, mode: int, width=width, height=height, full_quality=full_quality, - restore_faces=restore_faces, + detailer=detailer, tiling=tiling, hidiffusion=hidiffusion, init_images=[image], diff --git a/modules/ipadapter.py b/modules/ipadapter.py index de8522195..a92a30459 100644 --- a/modules/ipadapter.py +++ b/modules/ipadapter.py @@ -83,7 +83,7 @@ def crop_images(images, crops): try: for i in range(len(images)): if crops[i]: - from scripts.face_details import yolo # pylint: disable=no-name-in-module + from scripts.detailer import yolo # pylint: disable=no-name-in-module yolo.load() cropped = [] for image in images[i]: diff --git a/modules/paths.py b/modules/paths.py index 52bec2d0f..6f864da7f 100644 --- a/modules/paths.py +++ b/modules/paths.py @@ -123,6 +123,7 @@ def create_paths(opts): create_path(fix_path('outdir_save')) create_path(fix_path('outdir_video')) create_path(fix_path('styles_dir')) + create_path(fix_path('yolo_dir')) create_path(fix_path('wildcards_dir')) diff --git a/modules/postprocess/codeformer_model.py b/modules/postprocess/codeformer_model.py index e661335f9..4493ab8a3 100644 --- a/modules/postprocess/codeformer_model.py +++ b/modules/postprocess/codeformer_model.py @@ -1,7 +1,7 @@ import os import cv2 import torch -import modules.face_restoration +import modules.detailer from modules import shared, devices, modelloader, errors from modules.paths import models_path @@ -27,7 +27,7 @@ def setup_model(dirname): return try: - class FaceRestorerCodeFormer(modules.face_restoration.FaceRestoration): + class FaceRestorerCodeFormer(modules.detailer.Detailer): def name(self): return "CodeFormer" @@ -38,7 +38,7 @@ def setup_model(dirname): def create_models(self): from modules.postprocess.codeformer_arch import CodeFormer - from facelib.utils.face_restoration_helper import FaceRestoreHelper + from facelib.utils.detailer_helper import FaceRestoreHelper from facelib.detection.retinaface import retinaface if self.net is not None and self.face_helper is not None: self.net.to(devices.device) @@ -100,7 +100,7 @@ def setup_model(dirname): if original_resolution != restored_img.shape[0:2]: restored_img = cv2.resize(restored_img, (0, 0), fx=original_resolution[1]/restored_img.shape[1], fy=original_resolution[0]/restored_img.shape[0], interpolation=cv2.INTER_LINEAR) self.face_helper.clean_all() - if shared.opts.face_restoration_unload: + if shared.opts.detailer_unload: self.send_model_to(devices.cpu) return restored_img @@ -108,7 +108,7 @@ def setup_model(dirname): have_codeformer = True global codeformer # pylint: disable=global-statement codeformer = FaceRestorerCodeFormer(dirname) - shared.face_restorers.append(codeformer) + shared.detailers.append(codeformer) except Exception as e: errors.display(e, 'codeformer') diff --git a/modules/postprocess/gfpgan_model.py b/modules/postprocess/gfpgan_model.py index f17c91b2e..03274b7f4 100644 --- a/modules/postprocess/gfpgan_model.py +++ b/modules/postprocess/gfpgan_model.py @@ -56,7 +56,7 @@ def gfpgan_fix_faces(np_image): model.face_helper.clean_all() - if shared.opts.face_restoration_unload: + if shared.opts.detailer_unload: send_model_to(model, devices.cpu) return np_image @@ -76,7 +76,7 @@ def setup_model(dirname): install('gfpgan', quiet=True) import gfpgan import facexlib - import modules.face_restoration + import modules.detailer global user_path # pylint: disable=global-statement global have_gfpgan # pylint: disable=global-statement @@ -101,13 +101,13 @@ def setup_model(dirname): have_gfpgan = True gfpgan_constructor = gfpgan.GFPGANer - class FaceRestorerGFPGAN(modules.face_restoration.FaceRestoration): + class FaceRestorerGFPGAN(modules.detailer.Detailer): def name(self): return "GFPGAN" def restore(self, np_image, p=None): # pylint: disable=unused-argument return gfpgan_fix_faces(np_image) - shared.face_restorers.append(FaceRestorerGFPGAN()) + shared.detailers.append(FaceRestorerGFPGAN()) except Exception as e: errors.log.error(f'GFPGan failed to initialize: {e}') diff --git a/modules/processing.py b/modules/processing.py index c26668763..e0b89ef47 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -4,7 +4,7 @@ import time from contextlib import nullcontext import numpy as np from PIL import Image, ImageOps -from modules import shared, devices, errors, images, scripts, memstats, lowvram, script_callbacks, extra_networks, face_restoration, sd_hijack_freeu, sd_models, sd_vae, processing_helpers, timer +from modules import shared, devices, errors, images, scripts, memstats, lowvram, script_callbacks, extra_networks, detailer, sd_hijack_freeu, sd_models, sd_vae, processing_helpers, timer from modules.sd_hijack_hypertile import context_hypertile_vae, context_hypertile_unet from modules.processing_class import StableDiffusionProcessing, StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, StableDiffusionProcessingControl # pylint: disable=unused-import from modules.processing_info import create_infotext @@ -50,8 +50,8 @@ class Processed: self.image_cfg_scale = p.image_cfg_scale or 0 self.steps = p.steps or 0 self.batch_size = max(1, p.batch_size) - self.restore_faces = p.restore_faces or False - self.face_restoration_model = shared.opts.face_restoration_model if p.restore_faces else None + self.detailer = p.detailer or False + self.detailer_model = shared.opts.detailer_model if p.detailer else None self.sd_model_hash = getattr(shared.sd_model, 'sd_model_hash', '') if model_data.sd_model is not None else '' self.seed_resize_from_w = p.seed_resize_from_w self.seed_resize_from_h = p.seed_resize_from_h @@ -96,8 +96,8 @@ class Processed: "cfg_scale": self.cfg_scale, "steps": self.steps, "batch_size": self.batch_size, - "restore_faces": self.restore_faces, - "face_restoration_model": self.face_restoration_model, + "detailer": self.detailer, + "detailer_model": self.detailer_model, "sd_model_hash": self.sd_model_hash, "seed_resize_from_w": self.seed_resize_from_w, "seed_resize_from_h": self.seed_resize_from_h, @@ -359,11 +359,11 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: else: sample = validate_sample(sample) image = Image.fromarray(sample) - if p.restore_faces: - if not p.do_not_save_samples and shared.opts.save_images_before_face_restoration: + if p.detailer: + if not p.do_not_save_samples and shared.opts.save_images_before_detailer: images.save_image(Image.fromarray(sample), path=p.outpath_samples, basename="", seed=p.seeds[i], prompt=p.prompts[i], extension=shared.opts.samples_format, info=info, p=p, suffix="-before-face-restore") p.ops.append('face') - sample = face_restoration.restore_faces(sample, p) + sample = detailer.detail(sample, p) if sample is not None: image = Image.fromarray(sample) if p.scripts is not None and isinstance(p.scripts, scripts.ScriptRunner): @@ -439,8 +439,8 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: p.image_mask = ImageOps.invert(p.image_mask) output_images.append(p.image_mask) elif getattr(p, 'image_mask', None) is not None and isinstance(p.image_mask, Image.Image): - if getattr(p, 'mask_for_facehires', None) is not None: - output_images.append(p.mask_for_facehires) + if getattr(p, 'mask_for_detailer', None) is not None: + output_images.append(p.mask_for_detailer) else: output_images.append(p.image_mask) diff --git a/modules/processing_class.py b/modules/processing_class.py index dcf003f11..3eb35aa31 100644 --- a/modules/processing_class.py +++ b/modules/processing_class.py @@ -20,7 +20,7 @@ class StableDiffusionProcessing: """ The first set of paramaters: sd_models -> do_not_reload_embeddings represent the minimum required to create a StableDiffusionProcessing """ - def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt: str = "", styles: List[str] = None, seed: int = -1, subseed: int = -1, subseed_strength: float = 0, seed_resize_from_h: int = -1, seed_resize_from_w: int = -1, seed_enable_extras: bool = True, sampler_name: str = None, hr_sampler_name: str = None, batch_size: int = 1, n_iter: int = 1, steps: int = 50, cfg_scale: float = 7.0, image_cfg_scale: float = None, clip_skip: int = 1, width: int = 512, height: int = 512, full_quality: bool = True, restore_faces: bool = False, tiling: bool = False, hidiffusion: bool = False, do_not_save_samples: bool = False, do_not_save_grid: bool = False, extra_generation_params: Dict[Any, Any] = None, overlay_images: Any = None, negative_prompt: str = None, eta: float = None, do_not_reload_embeddings: bool = False, denoising_strength: float = 0, diffusers_guidance_rescale: float = 0.7, pag_scale: float = 0.0, pag_adaptive: float = 0.5, cfg_end: float = 1, resize_mode: int = 0, resize_name: str = 'None', resize_context: str = 'None', scale_by: float = 0, selected_scale_tab: int = 0, hdr_mode: int = 0, hdr_brightness: float = 0, hdr_color: float = 0, hdr_sharpen: float = 0, hdr_clamp: bool = False, hdr_boundary: float = 4.0, hdr_threshold: float = 0.95, hdr_maximize: bool = False, hdr_max_center: float = 0.6, hdr_max_boundry: float = 1.0, hdr_color_picker: str = None, hdr_tint_ratio: float = 0, override_settings: Dict[str, Any] = None, override_settings_restore_afterwards: bool = True, sampler_index: int = None, script_args: list = None): # pylint: disable=unused-argument + def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt: str = "", styles: List[str] = None, seed: int = -1, subseed: int = -1, subseed_strength: float = 0, seed_resize_from_h: int = -1, seed_resize_from_w: int = -1, seed_enable_extras: bool = True, sampler_name: str = None, hr_sampler_name: str = None, batch_size: int = 1, n_iter: int = 1, steps: int = 50, cfg_scale: float = 7.0, image_cfg_scale: float = None, clip_skip: int = 1, width: int = 512, height: int = 512, full_quality: bool = True, detailer: bool = False, tiling: bool = False, hidiffusion: bool = False, do_not_save_samples: bool = False, do_not_save_grid: bool = False, extra_generation_params: Dict[Any, Any] = None, overlay_images: Any = None, negative_prompt: str = None, eta: float = None, do_not_reload_embeddings: bool = False, denoising_strength: float = 0, diffusers_guidance_rescale: float = 0.7, pag_scale: float = 0.0, pag_adaptive: float = 0.5, cfg_end: float = 1, resize_mode: int = 0, resize_name: str = 'None', resize_context: str = 'None', scale_by: float = 0, selected_scale_tab: int = 0, hdr_mode: int = 0, hdr_brightness: float = 0, hdr_color: float = 0, hdr_sharpen: float = 0, hdr_clamp: bool = False, hdr_boundary: float = 4.0, hdr_threshold: float = 0.95, hdr_maximize: bool = False, hdr_max_center: float = 0.6, hdr_max_boundry: float = 1.0, hdr_color_picker: str = None, hdr_tint_ratio: float = 0, override_settings: Dict[str, Any] = None, override_settings_restore_afterwards: bool = True, sampler_index: int = None, script_args: list = None): # pylint: disable=unused-argument self.state: str = '' self.skip = [] self.outpath_samples: str = outpath_samples @@ -50,7 +50,7 @@ class StableDiffusionProcessing: self.width: int = width self.height: int = height self.full_quality: bool = full_quality - self.restore_faces: bool = restore_faces + self.detailer: bool = detailer self.tiling: bool = tiling self.hidiffusion: bool = hidiffusion self.do_not_save_samples: bool = do_not_save_samples diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 22293fb02..c06c3d3de 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -17,7 +17,7 @@ orig_pipeline = shared.sd_model def restore_state(p: processing.StableDiffusionProcessing): - if p.state in ['reprocess_refine', 'reprocess_face']: + if p.state in ['reprocess_refine', 'reprocess_detail']: # validate if last_p is None: shared.log.warning(f'Restore state: op={p.state} last state missing') @@ -43,9 +43,9 @@ def restore_state(p: processing.StableDiffusionProcessing): p.hr_scale, p.hr_upscaler, p.hr_resize_mode, p.hr_resize_context, p.hr_resize_x, p.hr_resize_y, p.hr_upscale_to_x, p.hr_upscale_to_y = hr_scale, hr_upscaler, hr_resize_mode, hr_resize_context, hr_resize_x, hr_resize_y, hr_upscale_to_x, hr_upscale_to_y p.height, p.width, p.scale_by, p.resize_mode, p.resize_name, p.resize_context = height, width, scale_by, resize_mode, resize_name, resize_context p.init_images = None - if state == 'reprocess_face': + if state == 'reprocess_detail': p.skip = ['encode', 'base', 'hires'] - p.restore_faces = True + p.detailer = True shared.log.info(f'Restore state: op={p.state} skip={p.skip}') return p diff --git a/modules/processing_info.py b/modules/processing_info.py index ad0a455fa..b0f4f20d8 100644 --- a/modules/processing_info.py +++ b/modules/processing_info.py @@ -124,7 +124,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts=None, all_seeds=No args['Size scale mask'] = p.scale_by_mask args['Size name mask'] = p.resize_name_mask if 'face' in p.ops: - args["Face restoration"] = shared.opts.face_restoration_model + args["Face restoration"] = shared.opts.detailer_model if 'color' in p.ops: args["Color correction"] = True # embeddings diff --git a/modules/processing_original.py b/modules/processing_original.py index bd6a8b466..852eb9a37 100644 --- a/modules/processing_original.py +++ b/modules/processing_original.py @@ -90,11 +90,11 @@ def sample_txt2img(p: processing.StableDiffusionProcessingTxt2Img, conditioning, for i, x_sample in enumerate(decoded_samples): x_sample = validate_sample(x_sample) image = Image.fromarray(x_sample) - bak_extra_generation_params, bak_restore_faces = p.extra_generation_params, p.restore_faces + bak_extra_generation_params, bak_detailer = p.extra_generation_params, p.detailer p.extra_generation_params = {} - p.restore_faces = False + p.detailer = False info = processing.create_infotext(p, p.all_prompts, p.all_seeds, p.all_subseeds, [], iteration=p.iteration, position_in_batch=i) - p.extra_generation_params, p.restore_faces = bak_extra_generation_params, bak_restore_faces + p.extra_generation_params, p.detailer = bak_extra_generation_params, bak_detailer images.save_image(image, p.outpath_samples, "", seeds[i], prompts[i], shared.opts.samples_format, info=info, suffix="-before-hires") if latent_scale_mode is None or p.hr_force: # non-latent upscaling shared.state.job = 'Upscale' diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index cf72301e4..0ea6dbdee 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -54,7 +54,7 @@ def create_sampler(name, model): model.prior_pipe.scheduler = copy.deepcopy(model.default_scheduler) model.prior_pipe.scheduler.config.clip_sample = False config = {k: v for k, v in model.scheduler.config.items() if not k.startswith('_')} - shared.log.debug(f'Sampler default {type(model.scheduler).__name__}: {config}') + shared.log.debug(f'Sampler: sampler=default class={model.scheduler.__class__.__name__}: {config}') return model.scheduler config = find_sampler_config(name) if config is None or config.constructor is None: diff --git a/modules/shared.py b/modules/shared.py index abcfbdbab..aba93bbaf 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -43,7 +43,8 @@ locking_available = True clip_model = None interrogator = modules.interrogate.InterrogateModels(os.path.join("models", "interrogate")) sd_upscalers = [] -face_restorers = [] +detailers = [] +yolo = None tab_names = [] extra_networks = [] options_templates = {} @@ -592,6 +593,7 @@ options_templates.update(options_section(('system-paths', "System Paths"), { "embeddings_dir": OptionInfo(os.path.join(paths.models_path, 'embeddings'), "Folder with textual inversion embeddings", folder=True), "hypernetwork_dir": OptionInfo(os.path.join(paths.models_path, 'hypernetworks'), "Folder with Hypernetwork models", folder=True), "control_dir": OptionInfo(os.path.join(paths.models_path, 'control'), "Folder with Control models", folder=True), + "yolo_dir": OptionInfo(os.path.join(paths.models_path, 'yolo'), "Folder with Yolo models", folder=True), "codeformer_models_path": OptionInfo(os.path.join(paths.models_path, 'Codeformer'), "Folder with codeformer models", folder=True), "gfpgan_models_path": OptionInfo(os.path.join(paths.models_path, 'GFPGAN'), "Folder with GFPGAN models", folder=True), "esrgan_models_path": OptionInfo(os.path.join(paths.models_path, 'ESRGAN'), "Folder with ESRGAN models", folder=True), @@ -644,7 +646,7 @@ options_templates.update(options_section(('saving-images', "Image Options"), { "save_init_img": OptionInfo(False, "Save init images"), "save_images_before_highres_fix": OptionInfo(False, "Save image before hires"), "save_images_before_refiner": OptionInfo(False, "Save image before refiner"), - "save_images_before_face_restoration": OptionInfo(False, "Save image before face restoration"), + "save_images_before_detailer": OptionInfo(False, "Save image before detailer"), "save_images_before_color_correction": OptionInfo(False, "Save image before color correction"), "save_mask": OptionInfo(False, "Save inpainting mask"), "save_mask_composite": OptionInfo(False, "Save inpainting masked composite"), @@ -782,18 +784,19 @@ options_templates.update(options_section(('postprocessing', "Postprocessing"), { "img2img_extra_noise": OptionInfo(0.0, "Extra noise multiplier for img2img", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 8, "step": 1, "visible": False}), - "postprocessing_sep_face_restoration": OptionInfo("

Face Restoration

", "", gr.HTML), - "face_restoration_model": OptionInfo("Face HiRes", "Face restoration model", gr.Radio, lambda: {"choices": [x.name() for x in face_restorers]}), - "facehires_sep": OptionInfo("

Face restore

", "", gr.HTML), - "facehires_conf": OptionInfo(0.6, "Min confidence", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.05}), - "facehires_max": OptionInfo(5, "Max faces", gr.Slider, {"minimum": 1, "maximum": 10, "step": 1}), - "facehires_iou": OptionInfo(0.5, "Max face overlap", gr.Slider, {"minimum": 0, "maximum": 1.0, "step": 0.05}), - "facehires_min_size": OptionInfo(0, "Min face size", gr.Slider, {"minimum": 0, "maximum": 1024, "step": 1}), - "facehires_max_size": OptionInfo(0, "Max face size", gr.Slider, {"minimum": 0, "maximum": 1024, "step": 1}), - "facehires_padding": OptionInfo(20, "Face padding", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1}), - "facehires_strength": OptionInfo(0.0, "Face restore strength", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), + "postprocessing_sep_detailer": OptionInfo("

Detailer

", "", gr.HTML), + "detailer_model": OptionInfo("Detailer", "Detailer model", gr.Radio, lambda: {"choices": [x.name() for x in detailers]}), + "detailer_sep": OptionInfo("

Detailer

", "", gr.HTML), + "detailer_conf": OptionInfo(0.6, "Min confidence", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.05}), + "detailer_max": OptionInfo(5, "Max detected", gr.Slider, {"minimum": 1, "maximum": 10, "step": 1}), + "detailer_iou": OptionInfo(0.5, "Max overlap", gr.Slider, {"minimum": 0, "maximum": 1.0, "step": 0.05}), + "detailer_min_size": OptionInfo(0, "Min object size", gr.Slider, {"minimum": 0, "maximum": 1024, "step": 1}), + "detailer_max_size": OptionInfo(0, "Max object size", gr.Slider, {"minimum": 0, "maximum": 1024, "step": 1}), + "detailer_padding": OptionInfo(20, "Object padding", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1}), + "detailer_strength": OptionInfo(0.0, "Detailer strength", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), + "detailer_models": OptionInfo(['Face yolo-8n'], "Detailer models", gr.Dropdown, lambda: {"multiselect":True, "choices": list(yolo.list)}), "code_former_weight": OptionInfo(0.2, "CodeFormer weight parameter", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}), - "face_restoration_unload": OptionInfo(False, "Move model to CPU when complete"), + "detailer_unload": OptionInfo(False, "Move detailer model to CPU when complete"), "postprocessing_sep_upscalers": OptionInfo("

Upscaling

", "", gr.HTML), "upscaler_unload": OptionInfo(False, "Unload upscaler after processing"), diff --git a/modules/txt2img.py b/modules/txt2img.py index e23b3efd7..077a3db9e 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -11,7 +11,7 @@ debug('Trace: PROCESS') def txt2img(id_task, state, prompt, negative_prompt, prompt_styles, steps, sampler_index, hr_sampler_index, - full_quality, restore_faces, tiling, hidiffusion, + full_quality, detailer, tiling, hidiffusion, n_iter, batch_size, cfg_scale, image_cfg_scale, diffusers_guidance_rescale, pag_scale, pag_adaptive, cfg_end, clip_skip, @@ -24,7 +24,7 @@ def txt2img(id_task, state, override_settings_texts, *args): - debug(f'txt2img: id_task={id_task}|prompt={prompt}|negative={negative_prompt}|styles={prompt_styles}|steps={steps}|sampler_index={sampler_index}|hr_sampler_index={hr_sampler_index}|full_quality={full_quality}|restore_faces={restore_faces}|tiling={tiling}|hidiffusion={hidiffusion}|batch_count={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|clip_skip={clip_skip}|seed={seed}|subseed={subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}|height={height}|width={width}|enable_hr={enable_hr}|denoising_strength={denoising_strength}|hr_resize_mode={hr_resize_mode}|hr_resize_context={hr_resize_context}|hr_scale={hr_scale}|hr_upscaler={hr_upscaler}|hr_force={hr_force}|hr_second_pass_steps={hr_second_pass_steps}|hr_resize_x={hr_resize_x}|hr_resize_y={hr_resize_y}|image_cfg_scale={image_cfg_scale}|diffusers_guidance_rescale={diffusers_guidance_rescale}|refiner_steps={refiner_steps}|refiner_start={refiner_start}|refiner_prompt={refiner_prompt}|refiner_negative={refiner_negative}|override_settings={override_settings_texts}') + debug(f'txt2img: id_task={id_task}|prompt={prompt}|negative={negative_prompt}|styles={prompt_styles}|steps={steps}|sampler_index={sampler_index}|hr_sampler_index={hr_sampler_index}|full_quality={full_quality}|detailer={detailer}|tiling={tiling}|hidiffusion={hidiffusion}|batch_count={n_iter}|batch_size={batch_size}|cfg_scale={cfg_scale}|clip_skip={clip_skip}|seed={seed}|subseed={subseed}|subseed_strength={subseed_strength}|seed_resize_from_h={seed_resize_from_h}|seed_resize_from_w={seed_resize_from_w}|height={height}|width={width}|enable_hr={enable_hr}|denoising_strength={denoising_strength}|hr_resize_mode={hr_resize_mode}|hr_resize_context={hr_resize_context}|hr_scale={hr_scale}|hr_upscaler={hr_upscaler}|hr_force={hr_force}|hr_second_pass_steps={hr_second_pass_steps}|hr_resize_x={hr_resize_x}|hr_resize_y={hr_resize_y}|image_cfg_scale={image_cfg_scale}|diffusers_guidance_rescale={diffusers_guidance_rescale}|refiner_steps={refiner_steps}|refiner_start={refiner_start}|refiner_prompt={refiner_prompt}|refiner_negative={refiner_negative}|override_settings={override_settings_texts}') if shared.sd_model is None: shared.log.warning('Model not loaded') @@ -65,7 +65,7 @@ def txt2img(id_task, state, width=width, height=height, full_quality=full_quality, - restore_faces=restore_faces, + detailer=detailer, tiling=tiling, hidiffusion=hidiffusion, enable_hr=enable_hr, diff --git a/modules/ui_control.py b/modules/ui_control.py index a65a6ef8f..cb7ae2921 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -102,7 +102,7 @@ def create_ui(_blocks: gr.Blocks=None): with gr.Row(elem_id='control_settings'): - full_quality, restore_faces, tiling, hidiffusion = ui_sections.create_options('control') + full_quality, detailer, tiling, hidiffusion = ui_sections.create_options('control') state = gr.Textbox(value='', visible=False) with gr.Accordion(open=False, label="Input", elem_id="control_input", elem_classes=["small-accordion"]): @@ -534,7 +534,7 @@ def create_ui(_blocks: gr.Blocks=None): prompt, negative, styles, steps, sampler_index, seed, subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, - cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, pag_scale, pag_adaptive, cfg_end, full_quality, restore_faces, tiling, hidiffusion, + cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, pag_scale, pag_adaptive, cfg_end, full_quality, detailer, tiling, hidiffusion, hdr_mode, hdr_brightness, hdr_color, hdr_sharpen, hdr_clamp, hdr_boundary, hdr_threshold, hdr_maximize, hdr_max_center, hdr_max_boundry, hdr_color_picker, hdr_tint_ratio, resize_mode_before, resize_name_before, resize_context_before, width_before, height_before, scale_by_before, selected_scale_tab_before, resize_mode_after, resize_name_after, resize_context_after, width_after, height_after, scale_by_after, selected_scale_tab_after, @@ -616,7 +616,7 @@ def create_ui(_blocks: gr.Blocks=None): (image_cfg_scale, "Image CFG scale"), (diffusers_guidance_rescale, "CFG rescale"), (full_quality, "Full quality"), - (restore_faces, "Face restoration"), + (detailer, "Face restoration"), (tiling, "Tiling"), (hidiffusion, "HiDiffusion"), # second pass diff --git a/modules/ui_img2img.py b/modules/ui_img2img.py index 1cbf30345..ec37f5e23 100644 --- a/modules/ui_img2img.py +++ b/modules/ui_img2img.py @@ -130,7 +130,7 @@ def create_ui(): refiner_start = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Denoise start', value=0.0, elem_id="img2img_refiner_start") cfg_scale, clip_skip, image_cfg_scale, diffusers_guidance_rescale, pag_scale, pag_adaptive, cfg_end = ui_sections.create_advanced_inputs('img2img') - full_quality, restore_faces, tiling, hidiffusion = ui_sections.create_options('img2img') + full_quality, detailer, tiling, hidiffusion = ui_sections.create_options('img2img') hdr_mode, hdr_brightness, hdr_color, hdr_sharpen, hdr_clamp, hdr_boundary, hdr_threshold, hdr_maximize, hdr_max_center, hdr_max_boundry, hdr_color_picker, hdr_tint_ratio = ui_sections.create_correction_inputs('img2img') # with gr.Group(elem_id="inpaint_controls", visible=False) as inpaint_controls: @@ -177,7 +177,7 @@ def create_ui(): sampler_index, mask_blur, mask_alpha, inpainting_fill, - full_quality, restore_faces, tiling, hidiffusion, + full_quality, detailer, tiling, hidiffusion, batch_count, batch_size, cfg_scale, image_cfg_scale, diffusers_guidance_rescale, pag_scale, pag_adaptive, cfg_end, @@ -267,7 +267,7 @@ def create_ui(): (clip_skip, "Clip skip"), (diffusers_guidance_rescale, "CFG rescale"), (full_quality, "Full quality"), - (restore_faces, "Face restoration"), + (detailer, "Detailer"), (tiling, "Tiling"), (hidiffusion, "HiDiffusion"), # inpaint diff --git a/modules/ui_sections.py b/modules/ui_sections.py index c30605cfd..aa076b1ea 100644 --- a/modules/ui_sections.py +++ b/modules/ui_sections.py @@ -32,7 +32,7 @@ def create_toprow(is_img2img: bool = False, id_part: str = None): reprocess.append(gr.Button('Reprocess', elem_id=f"{id_part}_reprocess", variant='primary', visible=True)) reprocess.append(gr.Button('Reprocess decode', elem_id=f"{id_part}_reprocess_decode", variant='primary', visible=False)) reprocess.append(gr.Button('Reprocess refine', elem_id=f"{id_part}_reprocess_refine", variant='primary', visible=False)) - reprocess.append(gr.Button('Reprocess face', elem_id=f"{id_part}_reprocess_face", variant='primary', visible=False)) + reprocess.append(gr.Button('Reprocess face', elem_id=f"{id_part}_reprocess_detail", variant='primary', visible=False)) with gr.Row(elem_id=f"{id_part}_generate_line2"): interrupt = gr.Button('Stop', elem_id=f"{id_part}_interrupt") interrupt.click(fn=lambda: shared.state.interrupt(), _js="requestInterrupt", inputs=[], outputs=[]) @@ -149,10 +149,10 @@ def create_seed_inputs(tab, reuse_visible=True): def create_options(tab): with gr.Row(elem_id=f"{tab}_advanced_options"): full_quality = gr.Checkbox(label='Full quality', value=True, elem_id=f"{tab}_full_quality") - restore_faces = gr.Checkbox(label='Face restore', value=False, elem_id=f"{tab}_restore_faces") + detailer = gr.Checkbox(label='Detailer', value=False, elem_id=f"{tab}_detailer") tiling = gr.Checkbox(label='Tiling', value=False, elem_id=f"{tab}_tiling") hidiffusion = gr.Checkbox(label='HiDiffusion', value=False, elem_id=f"{tab}_hidiffusion") - return full_quality, restore_faces, tiling, hidiffusion + return full_quality, detailer, tiling, hidiffusion def create_cfg_inputs(tab): diff --git a/modules/ui_txt2img.py b/modules/ui_txt2img.py index f4ecf3e49..171bc9ac1 100644 --- a/modules/ui_txt2img.py +++ b/modules/ui_txt2img.py @@ -39,7 +39,7 @@ def create_ui(): batch_count, batch_size = ui_sections.create_batch_inputs('txt2img', accordion=False) cfg_scale, cfg_end = ui_sections.create_cfg_inputs('txt2img') steps, sampler_index = ui_sections.create_sampler_and_steps_selection(None, "txt2img") - full_quality, restore_faces, tiling, hidiffusion = ui_sections.create_options('txt2img') + full_quality, detailer, tiling, hidiffusion = ui_sections.create_options('txt2img') with gr.Group(elem_classes="settings-accordion"): with gr.Accordion(open=False, label="Samplers", elem_classes=["small-accordion"], elem_id="txt2img_sampler_group"): @@ -64,7 +64,7 @@ def create_ui(): dummy_component, state, txt2img_prompt, txt2img_negative_prompt, txt2img_prompt_styles, steps, sampler_index, hr_sampler_index, - full_quality, restore_faces, tiling, hidiffusion, + full_quality, detailer, tiling, hidiffusion, batch_count, batch_size, cfg_scale, image_cfg_scale, diffusers_guidance_rescale, pag_scale, pag_adaptive, cfg_end, clip_skip, @@ -120,7 +120,7 @@ def create_ui(): (image_cfg_scale, "Image CFG scale"), (diffusers_guidance_rescale, "CFG rescale"), (full_quality, "Full quality"), - (restore_faces, "Face restoration"), + (detailer, "Detailer"), (tiling, "Tiling"), (hidiffusion, "HiDiffusion"), # second pass diff --git a/scripts/face_details.py b/scripts/detailer.py similarity index 58% rename from scripts/face_details.py rename to scripts/detailer.py index 0d803ccb3..e9f94667d 100644 --- a/scripts/face_details.py +++ b/scripts/detailer.py @@ -2,34 +2,49 @@ import os import numpy as np from PIL import Image, ImageDraw from modules import shared, processing -from modules.face_restoration import FaceRestoration +from modules.detailer import Detailer from modules import devices, processing_class -class YoLoResult: - def __init__(self, score: float, box: list[int], mask: Image.Image = None, face: Image.Image = None, size: float = 0, width = 0, height = 0, args = {}): +PREDEFINED = { # + 'Face yolo-8n': 'https://huggingface.co/vladmandic/yolo-detailers/resolve/main/face-yolo8n.pt', + 'Eyefull paired v2': 'https://huggingface.co/vladmandic/yolo-detailers/resolve/main/eyeful-paired-v2.pt', +} + + +class YoloResult: + def __init__(self, score: float, box: list[int], mask: Image.Image = None, item: Image.Image = None, size: float = 0, width = 0, height = 0, args = {}): self.score = score self.box = box self.mask = mask - self.face = face + self.item = item self.size = size self.width = width self.height = height self.args = args -class FaceRestorerYolo(FaceRestoration): - def name(self): - return "Face HiRes" - +class YoloRestorer(Detailer): def __init__(self): - from modules import paths - self.model = None - self.model_dir = os.path.join(paths.models_path, 'yolo') - self.model_name = 'yolov8n-face.pt' - self.model_url = 'https://github.com/akanametov/yolov8-face/releases/download/v0.0.0/yolov8n-face.pt' - # self.model_name = 'yolov9-c-face.pt' - # self.model_url = 'https://github.com/akanametov/yolov9-face/releases/download/1.0/yolov9-c-face.pt' + super().__init__() + self.models = {} + self.list = {} + self.enumerate() + + def name(self): + return "Detailer" + + def enumerate(self): + self.list.clear() + files = [] + for k, v in PREDEFINED.items(): + self.list[k] = v + files.append(os.path.basename(v)) + for f in os.listdir(shared.opts.yolo_dir): + if f not in files: + name = os.path.basename(f) + self.list[name] = os.path.join(shared.opts.yolo_dir, f) + shared.log.info(f'Available Yolo: path="{shared.opts.yolo_dir} items={len(list(self.list))}') def dependencies(self): import installer @@ -37,6 +52,7 @@ class FaceRestorerYolo(FaceRestoration): def predict( self, + model, image: Image.Image, imgsz: int = 640, half: bool = True, @@ -45,16 +61,16 @@ class FaceRestorerYolo(FaceRestoration): agnostic: bool = False, retina: bool = False, mask: bool = True, - offload: bool = shared.opts.face_restoration_unload, - ) -> list[YoLoResult]: + offload: bool = shared.opts.detailer_unload, + ) -> list[YoloResult]: args = { - 'conf': shared.opts.facehires_conf, - 'iou': shared.opts.facehires_iou, - 'max_det': shared.opts.facehires_max, + 'conf': shared.opts.detailer_conf, + 'iou': shared.opts.detailer_iou, + 'max_det': shared.opts.detailer_max, } - self.model.to(device) - predictions = self.model.predict( + model.to(device) + predictions = model.predict( source=[image], stream=False, verbose=False, @@ -67,7 +83,8 @@ class FaceRestorerYolo(FaceRestoration): **args ) if offload: - self.model.to('cpu') + model.to('cpu') + result = [] for prediction in predictions: boxes = prediction.boxes.xyxy.detach().int().cpu().numpy() if prediction.boxes is not None else [] @@ -77,41 +94,50 @@ class FaceRestorerYolo(FaceRestoration): mask_image = None w, h = box[2] - box[0], box[3] - box[1] size = w * h / (image.width * image.height) - if (min(w, h) > shared.opts.facehires_min_size if shared.opts.facehires_min_size > 0 else True) and (max(w, h) < shared.opts.facehires_max_size if shared.opts.facehires_max_size > 0 else True): + if (min(w, h) > shared.opts.detailer_min_size if shared.opts.detailer_min_size > 0 else True) and (max(w, h) < shared.opts.detailer_max_size if shared.opts.detailer_max_size > 0 else True): if mask: mask_image = image.copy() mask_image = Image.new('L', image.size, 0) draw = ImageDraw.Draw(mask_image) draw.rectangle(box, fill="white", outline=None, width=0) - face_image = image.crop(box) - result.append(YoLoResult(score=round(score, 2), box=box, mask=mask_image, face=face_image, size=size, width=w, height=h, args=args)) + cropped = image.crop(box) + result.append(YoloResult(score=round(score, 2), box=box, mask=mask_image, item=cropped, size=size, width=w, height=h, args=args)) return result - def load(self): + def load(self, model_name: str = None): from modules import modelloader self.dependencies() - if self.model is None: - model_file = modelloader.load_file_from_url(url=self.model_url, model_dir=self.model_dir, file_name=self.model_name) + if model_name is None: + model_name = list(self.list)[0] + if model_name in self.models: + return model_name, self.models[model_name] + else: + model_url = self.list.get(model_name) + file_name = os.path.basename(model_url) + model_file = modelloader.load_file_from_url(url=model_url, model_dir=shared.opts.yolo_dir, file_name=file_name) if model_file is not None: - shared.log.info(f'Load: type=FaceHires model={model_file}') + shared.log.info(f'Load: type=Detailer name="{model_name}" model="{model_file}"') from ultralytics import YOLO # pylint: disable=import-outside-toplevel - self.model = YOLO(model_file) + model = YOLO(model_file) + self.models[model_name] = model + return model_name, model + return None def restore(self, np_image, p: processing.StableDiffusionProcessing = None): if hasattr(p, 'recursion'): return - if not hasattr(p, 'facehires'): - p.facehires = 0 - if np_image is None or p.facehires >= p.batch_size * p.n_iter: + if not hasattr(p, 'detailer_active'): + p.detailer_active = 0 + if np_image is None or p.detailer_active >= p.batch_size * p.n_iter: return np_image - self.load() - if self.model is None: - shared.log.debug('Face HiRes: model not loaded') + name, model = self.load() + if model is None: + shared.log.warning(f'Detailer: model="{name}" not loaded') return np_image image = Image.fromarray(np_image) - faces = self.predict(image) - if len(faces) == 0: - shared.log.debug('Face HiRes: no faces detected') + items = self.predict(model, image) + if len(items) == 0: + shared.log.info(f'Detailer: model="{name}" no items detected') return np_image # create backups @@ -131,17 +157,17 @@ class FaceRestorerYolo(FaceRestoration): 'sampler_name': orig_p.get('hr_sampler_name', 'default'), 'steps': orig_p.get('hr_second_pass_steps', 0), 'negative_prompt': orig_p.get('refiner_negative', ''), - 'denoising_strength': shared.opts.facehires_strength if shared.opts.facehires_strength > 0 else orig_p.get('denoising_strength', 0.3), + 'denoising_strength': shared.opts.detailer_strength if shared.opts.detailer_strength > 0 else orig_p.get('denoising_strength', 0.3), 'styles': [], 'prompt': orig_p.get('refiner_prompt', ''), 'mask_blur': 10, - 'inpaint_full_res_padding': shared.opts.facehires_padding, - 'restore_faces': True, + 'inpaint_full_res_padding': shared.opts.detailer_padding, + 'detailer': True, 'width': resolution, 'height': resolution, } if args['denoising_strength'] == 0: - shared.log.debug('Face HiRes skip: strength=0') + shared.log.debug(f'Detailer: model="{name}" strength=0 skip') return np_image control_pipeline = None orig_class = shared.sd_model.__class__ @@ -151,7 +177,7 @@ class FaceRestorerYolo(FaceRestoration): run.restore_pipeline() p = processing_class.switch_class(p, processing.StableDiffusionProcessingImg2Img, args) - p.facehires += 1 # set flag to avoid recursion + p.detailer_active += 1 # set flag to avoid recursion if p.steps < 1: p.steps = orig_p.get('steps', 0) @@ -160,23 +186,23 @@ class FaceRestorerYolo(FaceRestoration): if len(p.negative_prompt) == 0: p.negative_prompt = orig_p.get('all_negative_prompts', [''])[0] - report = [{'score': f.score, 'size': f'{f.width}x{f.height}' } for f in faces] - shared.log.debug(f'Face HiRes: faces={report} args={faces[0].args} denoise={p.denoising_strength} blur={p.mask_blur} width={p.width} height={p.height} padding={p.inpaint_full_res_padding}') + report = [{'score': i.score, 'size': f'{i.width}x{i.height}' } for i in items] + shared.log.info(f'Detailer: model="{name}" items={report} args={items[0].args} denoise={p.denoising_strength} blur={p.mask_blur} width={p.width} height={p.height} padding={p.inpaint_full_res_padding}') mask_all = [] p.state = '' - for face in faces: - if face.mask is None: + for item in items: + if item.mask is None: continue p.init_images = [image] - p.image_mask = [face.mask] - # mask_all.append(face.mask) + p.image_mask = [item.mask] + # mask_all.append(item.mask) p.recursion = True pp = processing.process_images_inner(p) del p.recursion p.overlay_images = None # skip applying overlay twice if pp is not None and pp.images is not None and len(pp.images) > 0: - image = pp.images[0] # update image to be reused for next face + image = pp.images[0] # update image to be reused for next item if len(pp.images) > 1: mask_all.append(pp.images[1]) @@ -197,10 +223,11 @@ class FaceRestorerYolo(FaceRestoration): p.image_mask = blend([np.array(m) for m in mask_all]) # combined = blend([np_image, p.image_mask]) # combined = Image.fromarray(combined) - # combined.save('/tmp/face.png') + # combined.save('/tmp/item.png') p.image_mask = Image.fromarray(p.image_mask) return np_image -yolo = FaceRestorerYolo() -shared.face_restorers.append(yolo) +yolo = YoloRestorer() +shared.detailers.append(yolo) +shared.yolo = yolo diff --git a/scripts/prompts_from_file.py b/scripts/prompts_from_file.py index 465a44974..24dbc0b86 100644 --- a/scripts/prompts_from_file.py +++ b/scripts/prompts_from_file.py @@ -45,7 +45,7 @@ prompt_tags = { "cfg_scale": process_float_tag, "width": process_int_tag, "height": process_int_tag, - "restore_faces": process_boolean_tag, + "detailer": process_boolean_tag, "tiling": process_boolean_tag, "do_not_save_samples": process_boolean_tag, "do_not_save_grid": process_boolean_tag diff --git a/scripts/xyz_grid_classes.py b/scripts/xyz_grid_classes.py index f92a4fd43..db5684f97 100644 --- a/scripts/xyz_grid_classes.py +++ b/scripts/xyz_grid_classes.py @@ -1,4 +1,4 @@ -from scripts.xyz_grid_shared import apply_field, apply_task_args, apply_setting, apply_prompt, apply_order, apply_sampler, apply_hr_sampler_name, confirm_samplers, apply_checkpoint, apply_refiner, apply_unet, apply_dict, apply_clip_skip, apply_vae, list_lora, apply_lora, apply_te, apply_styles, apply_upscaler, apply_context, apply_face_restore, apply_override, apply_processing, apply_options, apply_seed, format_value_add_label, format_value, format_value_join_list, do_nothing, format_nothing, str_permutations # pylint: disable=no-name-in-module +from scripts.xyz_grid_shared import apply_field, apply_task_args, apply_setting, apply_prompt, apply_order, apply_sampler, apply_hr_sampler_name, confirm_samplers, apply_checkpoint, apply_refiner, apply_unet, apply_dict, apply_clip_skip, apply_vae, list_lora, apply_lora, apply_te, apply_styles, apply_upscaler, apply_context, apply_detailer, apply_override, apply_processing, apply_options, apply_seed, format_value_add_label, format_value, format_value_join_list, do_nothing, format_nothing, str_permutations # pylint: disable=no-name-in-module from modules import shared, shared_items, sd_samplers, ipadapter, sd_models, sd_vae, sd_unet @@ -127,7 +127,7 @@ axis_options = [ AxisOption("[Refine] Refiner steps", float, apply_field("refiner_steps")), AxisOption("[Postprocess] Upscaler", str, apply_upscaler, cost=0.4, choices=lambda: [x.name for x in shared.sd_upscalers][1:]), AxisOption("[Postprocess] Context", str, apply_context, choices=lambda: ["Add with forward", "Remove with forward", "Add with backward", "Remove with backward"]), - AxisOption("[Postprocess] Face restore", str, apply_face_restore, fmt=format_value), + AxisOption("[Postprocess] Detailer", str, apply_detailer, fmt=format_value), AxisOption("[HDR] Mode", int, apply_field("hdr_mode")), AxisOption("[HDR] Brightness", float, apply_field("hdr_brightness")), AxisOption("[HDR] Color", float, apply_field("hdr_color")), diff --git a/scripts/xyz_grid_shared.py b/scripts/xyz_grid_shared.py index 586070658..ee8977f52 100644 --- a/scripts/xyz_grid_shared.py +++ b/scripts/xyz_grid_shared.py @@ -237,17 +237,17 @@ def apply_context(p: processing.StableDiffusionProcessingTxt2Img, opt, x): shared.log.debug(f'XYZ grid apply resize-context: "{x}"') -def apply_face_restore(p, opt, x): +def apply_detailer(p, opt, x): opt = opt.lower() if opt == 'codeformer': is_active = True - p.face_restoration_model = 'CodeFormer' + p.detailer_model = 'CodeFormer' elif opt == 'gfpgan': is_active = True - p.face_restoration_model = 'GFPGAN' + p.detailer_model = 'GFPGAN' else: is_active = opt in ('true', 'yes', 'y', '1') - p.restore_faces = is_active + p.detailer = is_active shared.log.debug(f'XYZ grid apply face-restore: "{x}"')