From 9d3f178573a3b8ad5cffea6e205a3bf2fedef280 Mon Sep 17 00:00:00 2001 From: Dowon Date: Thu, 21 Mar 2024 00:24:41 +0900 Subject: [PATCH 1/7] fix: is_all_black - check image mode --- adetailer/mask.py | 2 +- scripts/!adetailer.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/adetailer/mask.py b/adetailer/mask.py index 5514828..3cd2edd 100644 --- a/adetailer/mask.py +++ b/adetailer/mask.py @@ -86,7 +86,7 @@ def offset(img: Image.Image, x: int = 0, y: int = 0) -> Image.Image: def is_all_black(img: Image.Image | np.ndarray) -> bool: if isinstance(img, Image.Image): - img = np.array(img) + img = np.array(ensure_pil_image(img, "L")) return cv2.countNonZero(img) == 0 diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index e89d694..08511a9 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -577,9 +577,10 @@ def pred_preprocessing(self, p, pred: PredictOutput, args: ADetailerArgs): y_offset=args.ad_y_offset, merge_invert=args.ad_mask_merge_invert, ) + if self.is_img2img_inpaint(p) and not self.is_inpaint_only_masked(p): invert = p.inpainting_mask_invert - image_mask = ensure_pil_image(p.image_mask, mode="L") + image_mask = ensure_pil_image(p.image_mask, "L") masks = self.inpaint_mask_filter(image_mask, masks, invert) return masks From eb63f828de2bf0cf28d5ccbfc4928792bab107ce Mon Sep 17 00:00:00 2001 From: Dowon Date: Thu, 21 Mar 2024 00:34:19 +0900 Subject: [PATCH 2/7] test: is_all_black rgb test, yolo9c test --- tests/test_mask.py | 20 ++++++++++++++++++++ tests/test_ultralytics.py | 2 ++ 2 files changed, 22 insertions(+) diff --git a/tests/test_mask.py b/tests/test_mask.py index e5ae03e..19698d5 100644 --- a/tests/test_mask.py +++ b/tests/test_mask.py @@ -1,4 +1,6 @@ +import cv2 import numpy as np +import pytest from PIL import Image, ImageDraw from adetailer.mask import dilate_erode, has_intersection, is_all_black, offset @@ -78,6 +80,24 @@ def test_is_all_black_2(): assert not is_all_black(img) +def test_is_all_black_rgb_image_pil(): + img = Image.new("RGB", (10, 10), color="red") + assert not is_all_black(img) + + img = Image.new("RGBA", (10, 10), color="red") + assert not is_all_black(img) + + +def test_is_all_black_rgb_image_numpy(): + img = np.full((10, 10, 4), 127, dtype=np.uint8) + with pytest.raises(cv2.error): + is_all_black(img) + + img = np.full((4, 10, 10), 0.5, dtype=np.float32) + with pytest.raises(cv2.error): + is_all_black(img) + + def test_has_intersection_1(): arr1 = np.array( [ diff --git a/tests/test_ultralytics.py b/tests/test_ultralytics.py index 1a75d7a..c772607 100644 --- a/tests/test_ultralytics.py +++ b/tests/test_ultralytics.py @@ -11,8 +11,10 @@ "face_yolov8n.pt", "face_yolov8n_v2.pt", "face_yolov8s.pt", + "face_yolov9c.pt", "hand_yolov8n.pt", "hand_yolov8s.pt", + "hand_yolov9c.pt", "person_yolov8n-seg.pt", "person_yolov8s-seg.pt", "person_yolov8m-seg.pt", From 9269676e8af5ec7b02976f71b2d1f44a81692c5b Mon Sep 17 00:00:00 2001 From: Dowon Date: Thu, 21 Mar 2024 00:43:56 +0900 Subject: [PATCH 3/7] chore: fix pyproject.toml, add pypi classifiers --- pyproject.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9b0822f..5b24e26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ readme = "README.md" license = { text = "AGPL-3.0" } dependencies = [ "ultralytics>=8.1", - "mediapipe>=10", + "mediapipe>=0.10", "pydantic<3", "rich>=13", "huggingface_hub", @@ -18,6 +18,10 @@ keywords = [ "adetailer", "ultralytics", ] +classifiers = [ + "License :: OSI Approved :: GNU Affero General Public License v3", + "Topic :: Scientific/Engineering :: Image Recognition", +] dynamic = ["version"] [project.urls] From 55571ab646e0cb1a1c9c23c1fb044a3d9c9c9bf0 Mon Sep 17 00:00:00 2001 From: Dowon Date: Thu, 21 Mar 2024 18:44:46 +0900 Subject: [PATCH 4/7] fix: opencv image size mismatch error --- scripts/!adetailer.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index 08511a9..8706e15 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -50,6 +50,7 @@ from modules.processing import ( Processed, StableDiffusionProcessingImg2Img, + create_binary_mask, create_infotext, process_images, ) @@ -579,9 +580,8 @@ def pred_preprocessing(self, p, pred: PredictOutput, args: ADetailerArgs): ) if self.is_img2img_inpaint(p) and not self.is_inpaint_only_masked(p): - invert = p.inpainting_mask_invert - image_mask = ensure_pil_image(p.image_mask, "L") - masks = self.inpaint_mask_filter(image_mask, masks, invert) + image_mask = self.get_image_mask(p) + masks = self.inpaint_mask_filter(image_mask, masks) return masks @staticmethod @@ -642,18 +642,24 @@ def is_inpaint_only_masked(p) -> bool: @staticmethod def inpaint_mask_filter( - img2img_mask: Image.Image, ad_mask: list[Image.Image], invert: int = 0 + img2img_mask: Image.Image, ad_mask: list[Image.Image] ) -> list[Image.Image]: - if invert: - img2img_mask = ImageChops.invert(img2img_mask) return [mask for mask in ad_mask if has_intersection(img2img_mask, mask)] + @staticmethod + def get_image_mask(p) -> Image.Image: + mask = p.image_mask + if p.inpainting_mask_invert: + mask = ImageChops.invert(mask) + mask = create_binary_mask(mask) + return images.resize_image(p.resize_mode, mask, p.width, p.height) + @rich_traceback def process(self, p, *args_): if getattr(p, "_ad_disabled", False): return - if self.is_img2img_inpaint(p) and is_all_black(p.image_mask): + if self.is_img2img_inpaint(p) and is_all_black(self.get_image_mask(p)): p._ad_disabled = True msg = ( "[-] ADetailer: img2img inpainting with no mask -- adetailer disabled." From 9c6d98263ab5b7e3031a2cc9cdd253653940c953 Mon Sep 17 00:00:00 2001 From: Dowon Date: Thu, 21 Mar 2024 18:59:57 +0900 Subject: [PATCH 5/7] fix: mask size when using skip img2img --- adetailer/__version__.py | 2 +- scripts/!adetailer.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/adetailer/__version__.py b/adetailer/__version__.py index c83195c..1362b0d 100644 --- a/adetailer/__version__.py +++ b/adetailer/__version__.py @@ -1 +1 @@ -__version__ = "24.3.1" +__version__ = "24.3.2-dev.0" diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index 8706e15..75a1e19 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -652,7 +652,12 @@ def get_image_mask(p) -> Image.Image: if p.inpainting_mask_invert: mask = ImageChops.invert(mask) mask = create_binary_mask(mask) - return images.resize_image(p.resize_mode, mask, p.width, p.height) + + if getattr(p, "_ad_skip_img2img", False): + width, height = p.init_images[0].size + else: + width, height = p.width, p.height + return images.resize_image(p.resize_mode, mask, width, height) @rich_traceback def process(self, p, *args_): From f185f602d2e68219612a3d0eba2d52bc528326fb Mon Sep 17 00:00:00 2001 From: Dowon Date: Thu, 21 Mar 2024 19:26:52 +0900 Subject: [PATCH 6/7] fix: disable when inpaint with skip img2img bug! --- scripts/!adetailer.py | 64 ++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/scripts/!adetailer.py b/scripts/!adetailer.py index 75a1e19..7c0bd31 100644 --- a/scripts/!adetailer.py +++ b/scripts/!adetailer.py @@ -201,7 +201,7 @@ def is_ad_enabled(self, *args_) -> bool: not_none = any(arg.get("ad_model", "None") != "None" for arg in arg_list) return ad_enabled and not_none - def check_skip_img2img(self, p, *args_) -> None: + def set_skip_img2img(self, p, *args_) -> None: if ( hasattr(p, "_ad_skip_img2img") or not hasattr(p, "init_images") @@ -211,20 +211,29 @@ def check_skip_img2img(self, p, *args_) -> None: if len(args_) >= 2 and isinstance(args_[1], bool): p._ad_skip_img2img = args_[1] - if args_[1]: - p._ad_orig = SkipImg2ImgOrig( - steps=p.steps, - sampler_name=p.sampler_name, - width=p.width, - height=p.height, - ) - p.steps = 1 - p.sampler_name = "Euler" - p.width = 128 - p.height = 128 else: p._ad_skip_img2img = False + if not p._ad_skip_img2img: + return + + if self.is_img2img_inpaint(p): + p._ad_disabled = True + msg = "[-] ADetailer: img2img inpainting with skip img2img is not supported. (because it's buggy)" + print(msg) + return + + p._ad_orig = SkipImg2ImgOrig( + steps=p.steps, + sampler_name=p.sampler_name, + width=p.width, + height=p.height, + ) + p.steps = 1 + p.sampler_name = "Euler" + p.width = 128 + p.height = 128 + @staticmethod def get_i(p) -> int: it = p.iteration @@ -672,21 +681,26 @@ def process(self, p, *args_): print(msg) return - if self.is_ad_enabled(*args_): - arg_list = self.get_args(p, *args_) - self.check_skip_img2img(p, *args_) + if not self.is_ad_enabled(*args_): + p._ad_disabled = True + return + + self.set_skip_img2img(p, *args_) + if getattr(p, "_ad_disabled", False): + # case when img2img inpainting with skip img2img + return - if hasattr(p, "_ad_xyz_prompt_sr"): - replaced_positive_prompt, replaced_negative_prompt = self.get_prompt( - p, arg_list[0] - ) - arg_list[0].ad_prompt = replaced_positive_prompt[0] - arg_list[0].ad_negative_prompt = replaced_negative_prompt[0] + arg_list = self.get_args(p, *args_) - extra_params = self.extra_params(arg_list) - p.extra_generation_params.update(extra_params) - else: - p._ad_disabled = True + if hasattr(p, "_ad_xyz_prompt_sr"): + replaced_positive_prompt, replaced_negative_prompt = self.get_prompt( + p, arg_list[0] + ) + arg_list[0].ad_prompt = replaced_positive_prompt[0] + arg_list[0].ad_negative_prompt = replaced_negative_prompt[0] + + extra_params = self.extra_params(arg_list) + p.extra_generation_params.update(extra_params) def _postprocess_image_inner( self, p, pp, args: ADetailerArgs, *, n: int = 0 From 3d6a5a541a7c8c0b2ef600553ab92ea7963e5814 Mon Sep 17 00:00:00 2001 From: Dowon Date: Thu, 21 Mar 2024 19:51:15 +0900 Subject: [PATCH 7/7] chore: v24.3.2 --- CHANGELOG.md | 8 ++++++++ adetailer/__version__.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da709bc..e2054d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,15 @@ # Changelog +## 2024-03-21 + +- v24.3.2 +- UI를 거치지 않은 입력에 대해, image_mask를 입력했을 때 opencv 에러가 발생하는 것 수정 +- img2img inpaint에서 skip img2img 옵션을 활성화할 경우, adetailer를 비활성화함 + - 마스크 크기에 대해 해결하기 힘든 문제가 있음 + ## 2024-03-16 +- v24.3.1 - YOLO World v2, YOLO9 지원가능한 버전으로 ultralytics 업데이트 - inpaint full res인 경우 인페인트 모드에서 동작하게 변경 - inpaint full res가 아닌 경우, 사용자가 입력한 마스크와 교차점이 있는 마스크만 선택하여 사용함 diff --git a/adetailer/__version__.py b/adetailer/__version__.py index 1362b0d..c5ff941 100644 --- a/adetailer/__version__.py +++ b/adetailer/__version__.py @@ -1 +1 @@ -__version__ = "24.3.2-dev.0" +__version__ = "24.3.2"