Spaces:
Runtime error
Runtime error
| import os | |
| import numpy as np | |
| from PIL import Image, ImageDraw | |
| from modules import shared, processing | |
| from modules.face_restoration import FaceRestoration | |
| class YoLoResult: | |
| def __init__(self, score: float, box: list[int], mask: Image.Image = None, size: float = 0): | |
| self.score = score | |
| self.box = box | |
| self.mask = mask | |
| self.size = size | |
| class FaceRestorerYolo(FaceRestoration): | |
| def name(self): | |
| return "Face HiRes" | |
| 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' | |
| def dependencies(self): | |
| import installer | |
| installer.install('ultralytics', ignore=False) | |
| def predict( | |
| self, | |
| image: Image.Image, | |
| offload: bool = False, | |
| conf: float = 0.5, | |
| iou: float = 0.5, | |
| imgsz: int = 640, | |
| half: bool = True, | |
| device = 'cuda', | |
| n: int = 5, | |
| augment: bool = True, | |
| agnostic: bool = False, | |
| retina: bool = False, | |
| mask: bool = True, | |
| ) -> list[YoLoResult]: | |
| self.model.to(device) | |
| predictions = self.model.predict( | |
| source=[image], | |
| stream=False, | |
| verbose=False, | |
| conf=conf, | |
| iou=iou, | |
| imgsz=imgsz, | |
| half=half, | |
| device=device, | |
| max_det=n, | |
| augment=augment, | |
| agnostic_nms=agnostic, | |
| retina_masks=retina, | |
| ) | |
| if offload: | |
| self.model.to('cpu') | |
| result = [] | |
| for prediction in predictions: | |
| boxes = prediction.boxes.xyxy.detach().int().cpu().numpy() if prediction.boxes is not None else [] | |
| scores = prediction.boxes.conf.detach().float().cpu().numpy() if prediction.boxes is not None else [] | |
| for score, box in zip(scores, boxes): | |
| box = box.tolist() | |
| mask_image = None | |
| size = (box[2] - box[0]) * (box[3] - box[1]) / (image.width * image.height) | |
| 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) | |
| result.append(YoLoResult(score=score, box=box, mask=mask_image, size=size)) | |
| return result | |
| def load(self): | |
| 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_file is not None: | |
| shared.log.info(f'Loading: type=FaceHires model={model_file}') | |
| from ultralytics import YOLO # pylint: disable=import-outside-toplevel | |
| self.model = YOLO(model_file) | |
| def restore(self, np_image, p: processing.StableDiffusionProcessing = None): | |
| 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 * p.n_iter: | |
| return np_image | |
| self.load() | |
| if self.model is None: | |
| shared.log.error(f"Model load: type=FaceHires model='{self.model_name}' dir={self.model_dir} url={self.model_url}") | |
| return np_image | |
| image = Image.fromarray(np_image) | |
| faces = self.predict(image, mask=True, device=devices.device, offload=shared.opts.face_restoration_unload) | |
| if len(faces) == 0: | |
| return np_image | |
| # create backups | |
| orig_apply_overlay = shared.opts.mask_apply_overlay | |
| orig_p = p.__dict__.copy() | |
| orig_cls = p.__class__ | |
| pp = None | |
| shared.opts.data['mask_apply_overlay'] = True | |
| args = { | |
| 'batch_size': 1, | |
| 'n_iter': 1, | |
| 'inpaint_full_res': True, | |
| 'inpainting_mask_invert': 0, | |
| 'inpainting_fill': 1, # no fill | |
| '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), | |
| 'styles': [], | |
| 'prompt': orig_p.get('refiner_prompt', ''), | |
| # TODO facehires expose as tunable | |
| 'mask_blur': 10, | |
| 'inpaint_full_res_padding': 15, | |
| 'restore_faces': True, | |
| } | |
| p = processing_class.switch_class(p, processing.StableDiffusionProcessingImg2Img, args) | |
| p.facehires += 1 # set flag to avoid recursion | |
| if p.steps < 1: | |
| p.steps = orig_p.get('steps', 0) | |
| if len(p.prompt) == 0: | |
| p.prompt = orig_p.get('all_prompts', [''])[0] | |
| 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 | |
| if face.size < 0.0002 or face.size > 0.8: | |
| shared.log.debug(f'Face HiRes skip: {face.__dict__}') | |
| continue | |
| p.init_images = [image] | |
| p.image_mask = [face.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 | |
| # 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 | |
| np_image = np.array(image) | |
| # shared.log.debug(f'Face HiRes complete: faces={len(faces)} time={t1-t0:.3f}') | |
| return np_image | |
| yolo = FaceRestorerYolo() | |
| shared.face_restorers.append(yolo) | |