mirror of
https://github.com/Bing-su/adetailer.git
synced 2026-04-29 02:31:29 +00:00
Merge branch 'dev'
This commit is contained in:
@@ -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가 아닌 경우, 사용자가 입력한 마스크와 교차점이 있는 마스크만 선택하여 사용함
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "24.3.1"
|
||||
__version__ = "24.3.2"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -50,6 +50,7 @@ from modules.devices import NansException
|
||||
from modules.processing import (
|
||||
Processed,
|
||||
StableDiffusionProcessingImg2Img,
|
||||
create_binary_mask,
|
||||
create_infotext,
|
||||
process_images,
|
||||
)
|
||||
@@ -200,7 +201,7 @@ class AfterDetailerScript(scripts.Script):
|
||||
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")
|
||||
@@ -210,20 +211,29 @@ class AfterDetailerScript(scripts.Script):
|
||||
|
||||
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
|
||||
@@ -577,10 +587,10 @@ class AfterDetailerScript(scripts.Script):
|
||||
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")
|
||||
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
|
||||
@@ -641,18 +651,29 @@ class AfterDetailerScript(scripts.Script):
|
||||
|
||||
@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)
|
||||
|
||||
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_):
|
||||
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."
|
||||
@@ -660,21 +681,26 @@ class AfterDetailerScript(scripts.Script):
|
||||
print(msg)
|
||||
return
|
||||
|
||||
if self.is_ad_enabled(*args_):
|
||||
arg_list = self.get_args(p, *args_)
|
||||
self.check_skip_img2img(p, *args_)
|
||||
|
||||
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)
|
||||
else:
|
||||
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
|
||||
|
||||
arg_list = self.get_args(p, *args_)
|
||||
|
||||
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
|
||||
|
||||
@@ -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(
|
||||
[
|
||||
|
||||
@@ -11,8 +11,10 @@ from adetailer.ultralytics import ultralytics_predict
|
||||
"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",
|
||||
|
||||
Reference in New Issue
Block a user