facehires support batch size&count, add override strength

This commit is contained in:
Vladimir Mandic
2024-03-14 11:40:58 -04:00
parent 5b4aca87d1
commit 940b6d02ba
8 changed files with 39 additions and 33 deletions
+3 -2
View File
@@ -5,13 +5,13 @@
- items that require `diffusers==0.27.0.dev`:
- EDM samplers for Playground 2.5
- Stable Cascade
- LEdits++ pipeline
- LEdits++ pipeline: <https://github.com/huggingface/diffusers/pull/6074>
- fix reference models:
- Warp Wuerstchen: pipeline does not have all components
- Kandinsky 2.1: pipeline does not have all components
- Kandinsky 2.2: pipeline does not have all components
## Update for 2024-03-13
## Update for 2024-03-14
- [Playground v2.5](https://huggingface.co/playgroundai/playground-v2.5-1024px-aesthetic)
- new model version from Playground: based on SDXL, but with some cool new concepts
@@ -90,6 +90,7 @@
- set as default face restorer in settings -> postprocessing
- disabled by default, to enable simply check *face restore* in your generate advanced settings
- strength, steps and sampler are set using by hires section in refine menu
- strength can be overriden in settings -> postprocessing
- will use secondary prompt and secondary negative prompt if present in refine
- **Watermarking**
- SD.Next disables all known watermarks in models, but does allow user to set custom watermark
+2 -2
View File
@@ -216,7 +216,7 @@ def draw_prompt_matrix(im, width, height, all_prompts, margin=0):
def resize_image(resize_mode, im, width, height, upscaler_name=None, output_type='image'):
shared.log.debug(f'Image resize: input={im} mode={resize_mode} target={width}x{height} upscaler={upscaler_name} function={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access
shared.log.debug(f'Image resize: input={im} mode={resize_mode} target={width}x{height} upscaler={upscaler_name} fn={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access
"""
Resizes an image with the specified resize_mode, width, and height.
Args:
@@ -597,7 +597,7 @@ save_thread.start()
def save_image(image, path, basename='', seed=None, prompt=None, extension=shared.opts.samples_format, info=None, short_filename=False, no_prompt=False, grid=False, pnginfo_section_name='parameters', p=None, existing_info=None, forced_filename=None, suffix='', save_to_dirs=None): # pylint: disable=unused-argument
debug(f'Save from function={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access
debug(f'Save: fn={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access
if image is None:
shared.log.warning('Image is none')
return None, None
+1 -1
View File
@@ -372,7 +372,7 @@ def outpaint(input_image: Image.Image, outpaint_type: str = 'Edge'):
def run_mask(input_image: Image.Image, input_mask: Image.Image = None, return_type: str = None, mask_blur: int = None, mask_padding: int = None, segment_enable=True, invert=None):
debug(f'Run mask: function={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access
debug(f'Run mask: fn={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access
if input_image is None:
return input_mask
+13 -13
View File
@@ -268,18 +268,19 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
extra_network_data = None
debug(f'Processing inner: args={vars(p)}')
for n in range(p.n_iter):
debug(f'Processing inner: iteration={n+1}/{p.n_iter}')
p.iteration = n
if shared.state.skipped:
shared.log.debug(f'Process skipped: {n}/{p.n_iter}')
shared.log.debug(f'Process skipped: {n+1}/{p.n_iter}')
shared.state.skipped = False
continue
if shared.state.interrupted:
shared.log.debug(f'Process interrupted: {n}/{p.n_iter}')
shared.log.debug(f'Process interrupted: {n+1}/{p.n_iter}')
break
p.prompts = p.all_prompts[n * p.batch_size:(n + 1) * p.batch_size]
p.negative_prompts = p.all_negative_prompts[n * p.batch_size:(n + 1) * p.batch_size]
p.seeds = p.all_seeds[n * p.batch_size:(n + 1) * p.batch_size]
p.subseeds = p.all_subseeds[n * p.batch_size:(n + 1) * p.batch_size]
p.prompts = p.all_prompts[n * p.batch_size:(n+1) * p.batch_size]
p.negative_prompts = p.all_negative_prompts[n * p.batch_size:(n+1) * p.batch_size]
p.seeds = p.all_seeds[n * p.batch_size:(n+1) * p.batch_size]
p.subseeds = p.all_subseeds[n * p.batch_size:(n+1) * p.batch_size]
if p.scripts is not None and isinstance(p.scripts, scripts.ScriptRunner):
p.scripts.before_process_batch(p, batch_number=n, prompts=p.prompts, seeds=p.seeds, subseeds=p.subseeds)
if len(p.prompts) == 0:
@@ -313,8 +314,8 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
if p.scripts is not None and isinstance(p.scripts, scripts.ScriptRunner):
p.scripts.postprocess_batch(p, x_samples_ddim, batch_number=n)
if p.scripts is not None and isinstance(p.scripts, scripts.ScriptRunner):
p.prompts = p.all_prompts[n * p.batch_size:(n + 1) * p.batch_size]
p.negative_prompts = p.all_negative_prompts[n * p.batch_size:(n + 1) * p.batch_size]
p.prompts = p.all_prompts[n * p.batch_size:(n+1) * p.batch_size]
p.negative_prompts = p.all_negative_prompts[n * p.batch_size:(n+1) * p.batch_size]
batch_params = scripts.PostprocessBatchListArgs(list(x_samples_ddim))
p.scripts.postprocess_batch_list(p, batch_params, batch_number=n)
x_samples_ddim = batch_params.images
@@ -326,6 +327,9 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
shared.sd_model.restore_pipeline()
for i, x_sample in enumerate(x_samples_ddim):
if hasattr(p, 'recursion'):
continue
debug(f'Processing result: index={i+1}/{len(x_samples_ddim)} iteration={n+1}/{p.n_iter}')
p.batch_index = i
if type(x_sample) == Image.Image:
image = x_sample
@@ -335,11 +339,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
image = Image.fromarray(x_sample)
if p.restore_faces:
if not p.do_not_save_samples and shared.opts.save_images_before_face_restoration:
orig = p.restore_faces
p.restore_faces = False
info = infotext(i)
p.restore_faces = orig
images.save_image(Image.fromarray(x_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")
images.save_image(Image.fromarray(x_sample), path=p.outpath_samples, basename="", seed=p.seeds[i], prompt=p.prompts[i], extension=shared.opts.samples_format, info=infotext(i), p=p, suffix="-before-face-restore")
p.ops.append('face')
x_sample = face_restoration.restore_faces(x_sample, p)
image = Image.fromarray(x_sample)
+7 -3
View File
@@ -1,4 +1,6 @@
import os
import sys
import inspect
import hashlib
from typing import Any, Dict, List
from dataclasses import dataclass, field
@@ -10,6 +12,9 @@ from modules import shared, devices, images, scripts, masking, sd_samplers, sd_m
from modules.sd_hijack_hypertile import hypertile_set
debug = shared.log.trace if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None
@dataclass(repr=False)
class StableDiffusionProcessing:
"""
@@ -507,18 +512,17 @@ class StableDiffusionProcessingControl(StableDiffusionProcessingImg2Img):
def switch_class(p: StableDiffusionProcessing, new_class: type, dct: dict = None):
import inspect
signature = inspect.signature(type(new_class).__init__, follow_wrapped=True)
possible = list(signature.parameters)
kwargs = {}
for k, v in p.__dict__.items():
for k, v in p.__dict__.copy().items():
if k in possible:
kwargs[k] = v
if dct is not None:
for k, v in dct.items():
if k in possible:
kwargs[k] = v
shared.log.debug(f"Switching class: {p.__class__} -> {new_class}")
debug(f"Switching class: {p.__class__.__name__} -> {new_class.__name__} fn={sys._getframe(1).f_code.co_name}") # pylint: disable=protected-access
p.__class__ = new_class
p.__init__(**kwargs)
for k, v in p.__dict__.items():
+2 -2
View File
@@ -754,9 +754,9 @@ def move_model(model, device=None, force=False):
if device == devices.device: # force vae back to gpu if not in txt2img mode
model.vae.to(device)
if hasattr(model.vae, '_hf_hook'):
debug_move(f'Model move: to={device} class={model.vae.__class__} function={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access
debug_move(f'Model move: to={device} class={model.vae.__class__} fn={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access
model.vae._hf_hook.execution_device = device # pylint: disable=protected-access
debug_move(f'Model move: device={device} class={model.__class__} accelerate={getattr(model, "has_accelerate", False)} function={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access
debug_move(f'Model move: device={device} class={model.__class__} accelerate={getattr(model, "has_accelerate", False)} fn={sys._getframe(1).f_code.co_name}') # pylint: disable=protected-access
if hasattr(model, "components"): # accelerate patch
for name, m in model.components.items():
if not hasattr(m, "_hf_hook"): # not accelerate hook
+1
View File
@@ -669,6 +669,7 @@ options_templates.update(options_section(('postprocessing', "Postprocessing"), {
"postprocessing_sep_face_restoration": OptionInfo("<h2>Face Restoration</h2>", "", gr.HTML),
"face_restoration_model": OptionInfo("Face HiRes", "Face restoration model", gr.Radio, lambda: {"choices": [x.name() for x in face_restorers]}),
"facehires_strength": OptionInfo(0.0, "Face HiRes strength", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}),
"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"),
+10 -10
View File
@@ -92,7 +92,7 @@ class FaceRestorerYolo(FaceRestoration):
from modules import devices, processing_class
if not hasattr(p, 'facehires'):
p.facehires = 0
if np_image is None or p.facehires >= p.batch_size:
if np_image is None or p.facehires >= p.batch_size * p.n_iter:
return np_image
self.load()
if self.model is None:
@@ -119,7 +119,7 @@ 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': orig_p.get('denoising_strength', 0.3),
'denoising_strength': shared.opts.facehires_strength if shared.opts.facehires_strength > 0 else orig_p.get('denoising_strength', 0.3),
'styles': [],
'prompt': orig_p.get('refiner_prompt', ''),
# TODO facehires expose as tunable
@@ -137,6 +137,7 @@ class FaceRestorerYolo(FaceRestoration):
if len(p.negative_prompt) == 0:
p.negative_prompt = orig_p.get('all_negative_prompts', [''])[0]
shared.log.debug(f'Face HiRes: faces={[f.__dict__ for f in faces]} strength={p.denoising_strength} blur={p.mask_blur} padding={p.inpaint_full_res_padding} steps={p.steps}')
for face in faces:
if face.mask is None:
continue
@@ -145,21 +146,20 @@ class FaceRestorerYolo(FaceRestoration):
continue
p.init_images = [image]
p.image_mask = [face.mask]
shared.log.debug(f'Face HiRes: face={p.facehires} {face.__dict__} strength={p.denoising_strength} blur={p.mask_blur} padding={p.inpaint_full_res_padding} steps={p.steps}')
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]
if np_image is None or getattr(p, 'facehires', 0) >= p.batch_size:
p.facehires = 0
image = pp.images[0] # update image to be reused for next face
# restore pipeline
p = processing_class.switch_class(p, orig_cls, orig_p)
p.init_images = getattr(orig_p, 'init_images', None)
p.image_mask = getattr(orig_p, 'image_mask', None)
shared.opts.data['mask_apply_overlay'] = orig_apply_overlay
if pp is not None and pp.images is not None and len(pp.images) > 0:
image = pp.images[0]
np_image = np.array(image)
np_image = np.array(image)
# shared.log.debug(f'Face HiRes complete: faces={len(faces)} time={t1-t0:.3f}')
return np_image