Update app.py
Browse files
app.py
CHANGED
|
@@ -552,12 +552,12 @@ def step1_cpu(img, keep_rembg, do_weaponless, weapon_terms):
|
|
| 552 |
return [preview], out_path, "\n".join(logs)
|
| 553 |
|
| 554 |
def _resize_to_multiple(img: Image.Image, multiple: int = 8, max_side: int = 768) -> Image.Image:
|
| 555 |
-
"""Aspect 유지 + 8의 배수
|
| 556 |
w, h = img.size
|
| 557 |
-
#
|
| 558 |
scale = min(1.0, float(max_side) / float(max(w, h)))
|
| 559 |
w = int(w * scale); h = int(h * scale)
|
| 560 |
-
#
|
| 561 |
w = max(multiple, (w // multiple) * multiple)
|
| 562 |
h = max(multiple, (h // multiple) * multiple)
|
| 563 |
if (w, h) != img.size:
|
|
@@ -565,18 +565,18 @@ def _resize_to_multiple(img: Image.Image, multiple: int = 8, max_side: int = 768
|
|
| 565 |
return img
|
| 566 |
|
| 567 |
def _make_tpose_canvas_like(img: Image.Image) -> Image.Image:
|
| 568 |
-
"""
|
|
|
|
| 569 |
w, h = img.size
|
| 570 |
size = min(w, h)
|
| 571 |
base = Image.new("RGB", (w, h), "black")
|
| 572 |
-
# T-포즈 가이드는 정사각 영역에 그린 후 중앙 정렬
|
| 573 |
square = Image.new("RGB", (size, size), "black")
|
| 574 |
d = ImageDraw.Draw(square)
|
| 575 |
cx, cy = size//2, int(size*0.58)
|
| 576 |
arm = int(size*0.36); leg = int(size*0.36); head = int(size*0.06)
|
| 577 |
# spine
|
| 578 |
d.line([(cx, cy-int(size*0.28)), (cx, cy+int(size*0.04))], fill="white", width=10)
|
| 579 |
-
# arms
|
| 580 |
yA = cy-int(size*0.22)
|
| 581 |
d.line([(cx-arm, yA), (cx+arm, yA)], fill="white", width=10)
|
| 582 |
# legs
|
|
@@ -586,101 +586,111 @@ def _make_tpose_canvas_like(img: Image.Image) -> Image.Image:
|
|
| 586 |
d.ellipse([(cx-head, yA-int(size*0.18)-head), (cx+head, yA-int(size*0.18)+head)], outline="white", width=10)
|
| 587 |
# joints
|
| 588 |
for pt in [(cx,yA), (cx-arm,yA), (cx+arm,yA), (cx,cy), (cx,cy+int(size*0.04))]:
|
| 589 |
-
d.ellipse([(pt[0]-8,pt[1]-8),(pt[0]+8,pt[1]+8)], fill="white")
|
| 590 |
-
# 중앙 배치
|
| 591 |
offx = (w - size)//2; offy = (h - size)//2
|
| 592 |
base.paste(square, (offx, offy))
|
| 593 |
return base
|
| 594 |
|
| 595 |
|
| 596 |
-
@spaces.GPU(duration=600)
|
| 597 |
def step1_gpu_refine(
|
| 598 |
-
s1_path,
|
| 599 |
-
enforce_tpose, tpose_strength, tpose_steps, tpose_guidance,
|
| 600 |
-
do_redraw_flag, redraw_strength, redraw_steps, redraw_guidance
|
| 601 |
):
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
redraw_steps = int(max(12, min(28, int(redraw_steps))))
|
| 608 |
-
redraw_guidance = max(5.0, min(9.0, float(redraw_guidance)))
|
| 609 |
-
# 입력 이미지와 포즈 캔버스를 같은 해상도(8의 배수)로 맞추기
|
| 610 |
-
img_rgb = img.convert("RGB")
|
| 611 |
-
img_rgb = _resize_to_multiple(img_rgb, multiple=8, max_side=768)
|
| 612 |
-
pose_canvas = _make_tpose_canvas_like(img_rgb) # 입력과 동일 해상도
|
| 613 |
-
|
| 614 |
-
# (선택) 시드 고정 원하면:
|
| 615 |
-
# generator = None
|
| 616 |
-
# try:
|
| 617 |
-
# import torch
|
| 618 |
-
# generator = torch.Generator(device="cuda" if torch.cuda.is_available() else "cpu").manual_seed(0)
|
| 619 |
-
# except Exception:
|
| 620 |
-
# pass
|
| 621 |
-
|
| 622 |
-
"""GPU 단계: ControlNet(OpenPose)로 T-포즈 강제 + img2img 리드로우"""
|
| 623 |
logs = []
|
|
|
|
| 624 |
if not s1_path or not Path(s1_path).exists():
|
| 625 |
raise gr.Error("STEP1 이미지가 없습니다. 먼저 STEP1(CPU)을 실행하세요.")
|
| 626 |
-
img
|
|
|
|
| 627 |
|
| 628 |
-
#
|
| 629 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 630 |
if enforce_tpose:
|
| 631 |
try:
|
| 632 |
from diffusers import ControlNetModel, StableDiffusionControlNetImg2ImgPipeline
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
dev = "cuda" if torch.cuda.is_available() else "cpu"
|
| 636 |
controlnet = ControlNetModel.from_pretrained(
|
| 637 |
"lllyasviel/control_v11p_sd15_openpose",
|
| 638 |
-
torch_dtype=torch.float16 if dev == "cuda" else torch.float32
|
| 639 |
)
|
| 640 |
-
|
| 641 |
-
pipe = StableDiffusionControlNetImg2ImgPipeline.from_pretrained(
|
| 642 |
"runwayml/stable-diffusion-v1-5",
|
| 643 |
controlnet=controlnet,
|
| 644 |
-
torch_dtype=torch.float16 if dev == "cuda" else torch.float32
|
| 645 |
-
)
|
| 646 |
-
|
| 647 |
-
|
|
|
|
|
|
|
| 648 |
img_rgb = _resize_to_multiple(img.convert("RGB"), multiple=8, max_side=768)
|
| 649 |
pose_canvas = _make_tpose_canvas_like(img_rgb)
|
| 650 |
-
|
| 651 |
-
|
| 652 |
prompt="T-pose, full body, clean anime lines",
|
| 653 |
image=img_rgb,
|
| 654 |
control_image=pose_canvas,
|
| 655 |
strength=float(tpose_strength),
|
| 656 |
guidance_scale=float(tpose_guidance),
|
| 657 |
num_inference_steps=int(tpose_steps),
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
except Exception as e:
|
| 662 |
-
logs.append(f"T-포즈 실패: {e}")
|
| 663 |
-
|
| 664 |
|
| 665 |
-
#
|
| 666 |
-
# ---- 리드로우
|
| 667 |
if do_redraw_flag:
|
| 668 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 669 |
img_for_redraw = _resize_to_multiple(img.convert("RGB"), multiple=8, max_side=768)
|
| 670 |
-
|
| 671 |
prompt="clean anime illustration, sharp lines, simple solid background",
|
| 672 |
image=img_for_redraw,
|
| 673 |
strength=float(redraw_strength),
|
| 674 |
guidance_scale=float(redraw_guidance),
|
| 675 |
num_inference_steps=int(redraw_steps),
|
| 676 |
-
|
| 677 |
-
|
|
|
|
| 678 |
except Exception as e:
|
| 679 |
-
logs.append(f"리드로우 실패: {e}")
|
| 680 |
-
|
| 681 |
|
|
|
|
| 682 |
out_path = _save_png(img, OUT / "step1" / "input_preprocessed.png")
|
| 683 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 684 |
|
| 685 |
# ---------------------------------
|
| 686 |
# STEP2: Spaces call (model + texture)
|
|
|
|
| 552 |
return [preview], out_path, "\n".join(logs)
|
| 553 |
|
| 554 |
def _resize_to_multiple(img: Image.Image, multiple: int = 8, max_side: int = 768) -> Image.Image:
|
| 555 |
+
"""Aspect 유지 + 8의 배수 리사이즈 (최대 변은 max_side)"""
|
| 556 |
w, h = img.size
|
| 557 |
+
# 최대 변 제한
|
| 558 |
scale = min(1.0, float(max_side) / float(max(w, h)))
|
| 559 |
w = int(w * scale); h = int(h * scale)
|
| 560 |
+
# 8 배수로 내림
|
| 561 |
w = max(multiple, (w // multiple) * multiple)
|
| 562 |
h = max(multiple, (h // multiple) * multiple)
|
| 563 |
if (w, h) != img.size:
|
|
|
|
| 565 |
return img
|
| 566 |
|
| 567 |
def _make_tpose_canvas_like(img: Image.Image) -> Image.Image:
|
| 568 |
+
"""입력과 동일 해상도의 T-포즈 가이드 캔버스 생성"""
|
| 569 |
+
from PIL import ImageDraw
|
| 570 |
w, h = img.size
|
| 571 |
size = min(w, h)
|
| 572 |
base = Image.new("RGB", (w, h), "black")
|
|
|
|
| 573 |
square = Image.new("RGB", (size, size), "black")
|
| 574 |
d = ImageDraw.Draw(square)
|
| 575 |
cx, cy = size//2, int(size*0.58)
|
| 576 |
arm = int(size*0.36); leg = int(size*0.36); head = int(size*0.06)
|
| 577 |
# spine
|
| 578 |
d.line([(cx, cy-int(size*0.28)), (cx, cy+int(size*0.04))], fill="white", width=10)
|
| 579 |
+
# arms (T)
|
| 580 |
yA = cy-int(size*0.22)
|
| 581 |
d.line([(cx-arm, yA), (cx+arm, yA)], fill="white", width=10)
|
| 582 |
# legs
|
|
|
|
| 586 |
d.ellipse([(cx-head, yA-int(size*0.18)-head), (cx+head, yA-int(size*0.18)+head)], outline="white", width=10)
|
| 587 |
# joints
|
| 588 |
for pt in [(cx,yA), (cx-arm,yA), (cx+arm,yA), (cx,cy), (cx,cy+int(size*0.04))]:
|
| 589 |
+
d.ellipse([(pt[0]-8,pt[1]-8), (pt[0]+8,pt[1]+8)], fill="white")
|
|
|
|
| 590 |
offx = (w - size)//2; offy = (h - size)//2
|
| 591 |
base.paste(square, (offx, offy))
|
| 592 |
return base
|
| 593 |
|
| 594 |
|
| 595 |
+
@spaces.GPU(duration=600)
|
| 596 |
def step1_gpu_refine(
|
| 597 |
+
s1_path: str,
|
| 598 |
+
enforce_tpose: bool, tpose_strength: float, tpose_steps: int, tpose_guidance: float,
|
| 599 |
+
do_redraw_flag: bool, redraw_strength: float, redraw_steps: int, redraw_guidance: float
|
| 600 |
):
|
| 601 |
+
"""
|
| 602 |
+
GPU 단계: ControlNet(OpenPose)로 T-포즈 강제 → (선택) img2img 리드로우.
|
| 603 |
+
- ZeroGPU 규칙: torch/diffusers 로드는 이 함수 내부에서만!
|
| 604 |
+
- image/control_image 해상도 동일 + 8의 배수로 강제.
|
| 605 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 606 |
logs = []
|
| 607 |
+
# ====== 입력 확인 & 기본 이미지 로드 ======
|
| 608 |
if not s1_path or not Path(s1_path).exists():
|
| 609 |
raise gr.Error("STEP1 이미지가 없습니다. 먼저 STEP1(CPU)을 실행하세요.")
|
| 610 |
+
# 항상 먼저 img 초기화 (UnboundLocal 방지)
|
| 611 |
+
img: Image.Image = Image.open(s1_path).convert("RGBA")
|
| 612 |
|
| 613 |
+
# ====== 안전 파라미터 클램프 (형태 붕괴 방지) ======
|
| 614 |
+
tpose_strength = max(0.35, min(0.65, float(tpose_strength)))
|
| 615 |
+
tpose_steps = int(max(12, min(28, int(tpose_steps)))))
|
| 616 |
+
tpose_guidance = max(5.5, min(9.0, float(tpose_guidance)))
|
| 617 |
+
redraw_strength = max(0.25, min(0.5, float(redraw_strength)))
|
| 618 |
+
redraw_steps = int(max(12, min(28, int(redraw_steps))))
|
| 619 |
+
redraw_guidance = max(5.0, min(9.0, float(redraw_guidance)))
|
| 620 |
+
|
| 621 |
+
# ====== 디바이스 결정 (ZeroGPU: 이 함�� 내부에서만) ======
|
| 622 |
+
try:
|
| 623 |
+
import torch
|
| 624 |
+
dev = "cuda" if torch.cuda.is_available() else "cpu"
|
| 625 |
+
except Exception:
|
| 626 |
+
dev = "cpu"
|
| 627 |
+
|
| 628 |
+
# ====== T-포즈 강제 (ControlNet/OpenPose) ======
|
| 629 |
if enforce_tpose:
|
| 630 |
try:
|
| 631 |
from diffusers import ControlNetModel, StableDiffusionControlNetImg2ImgPipeline
|
| 632 |
+
# 모델 로드 (함수 내부)
|
|
|
|
|
|
|
| 633 |
controlnet = ControlNetModel.from_pretrained(
|
| 634 |
"lllyasviel/control_v11p_sd15_openpose",
|
| 635 |
+
torch_dtype=(torch.float16 if dev == "cuda" else torch.float32)
|
| 636 |
)
|
| 637 |
+
pipe_pose = StableDiffusionControlNetImg2ImgPipeline.from_pretrained(
|
|
|
|
| 638 |
"runwayml/stable-diffusion-v1-5",
|
| 639 |
controlnet=controlnet,
|
| 640 |
+
torch_dtype=(torch.float16 if dev == "cuda" else torch.float32)
|
| 641 |
+
)
|
| 642 |
+
if dev == "cuda":
|
| 643 |
+
pipe_pose.to("cuda")
|
| 644 |
+
|
| 645 |
+
# 입력/컨트롤 이미지 해상도 정규화 (동일 + 8배수)
|
| 646 |
img_rgb = _resize_to_multiple(img.convert("RGB"), multiple=8, max_side=768)
|
| 647 |
pose_canvas = _make_tpose_canvas_like(img_rgb)
|
| 648 |
+
|
| 649 |
+
out = pipe_pose(
|
| 650 |
prompt="T-pose, full body, clean anime lines",
|
| 651 |
image=img_rgb,
|
| 652 |
control_image=pose_canvas,
|
| 653 |
strength=float(tpose_strength),
|
| 654 |
guidance_scale=float(tpose_guidance),
|
| 655 |
num_inference_steps=int(tpose_steps),
|
| 656 |
+
).images[0]
|
| 657 |
+
img = out.convert("RGBA")
|
| 658 |
+
logs.append("ControlNet(OpenPose) T-포즈 적용")
|
| 659 |
except Exception as e:
|
| 660 |
+
logs.append(f"T-포즈 ControlNet 실패: {e}")
|
|
|
|
| 661 |
|
| 662 |
+
# ====== (옵션) img2img 리드로우 ======
|
|
|
|
| 663 |
if do_redraw_flag:
|
| 664 |
try:
|
| 665 |
+
from diffusers import StableDiffusionImg2ImgPipeline
|
| 666 |
+
pipe_redraw = StableDiffusionImg2ImgPipeline.from_pretrained(
|
| 667 |
+
"runwayml/stable-diffusion-v1-5",
|
| 668 |
+
torch_dtype=(torch.float16 if dev == "cuda" else torch.float32)
|
| 669 |
+
)
|
| 670 |
+
if dev == "cuda":
|
| 671 |
+
pipe_redraw.to("cuda")
|
| 672 |
+
|
| 673 |
img_for_redraw = _resize_to_multiple(img.convert("RGB"), multiple=8, max_side=768)
|
| 674 |
+
out = pipe_redraw(
|
| 675 |
prompt="clean anime illustration, sharp lines, simple solid background",
|
| 676 |
image=img_for_redraw,
|
| 677 |
strength=float(redraw_strength),
|
| 678 |
guidance_scale=float(redraw_guidance),
|
| 679 |
num_inference_steps=int(redraw_steps),
|
| 680 |
+
).images[0]
|
| 681 |
+
img = out.convert("RGBA")
|
| 682 |
+
logs.append("img2img 리드로우 적용")
|
| 683 |
except Exception as e:
|
| 684 |
+
logs.append(f"img2img 리드로우 실패: {e}")
|
|
|
|
| 685 |
|
| 686 |
+
# ====== 저장 & 갤러리 미리보기 ======
|
| 687 |
out_path = _save_png(img, OUT / "step1" / "input_preprocessed.png")
|
| 688 |
+
try:
|
| 689 |
+
preview = _to_preview(img) # 있으면 사용
|
| 690 |
+
except Exception:
|
| 691 |
+
preview = img.convert("RGB")
|
| 692 |
+
|
| 693 |
+
return [preview], str(out_path), "\n".join(logs)
|
| 694 |
|
| 695 |
# ---------------------------------
|
| 696 |
# STEP2: Spaces call (model + texture)
|