aptol commited on
Commit
f72c130
·
verified ·
1 Parent(s): bb6302e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +71 -61
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의 배수 크기로 리사이즈 (최대 변은 max_side로 제한)"""
556
  w, h = img.size
557
- # 1) 최대 변 제한
558
  scale = min(1.0, float(max_side) / float(max(w, h)))
559
  w = int(w * scale); h = int(h * scale)
560
- # 2) 8 배수로 내림
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
- """입력 이미지와 같은 해상도의 T-포즈 캔버스 생성"""
 
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) # ← ZeroGPU 환경: 여기서만 CUDA/모델 로딩 허용
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
- tpose_strength = max(0.35, min(0.65, float(tpose_strength)))
604
- tpose_steps = int(max(12, min(28, int(tpose_steps))))
605
- tpose_guidance = max(5.5, min(9.0, float(tpose_guidance)))
606
- redraw_strength = max(0.25, min(0.5, float(redraw_strength)))
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 = Image.open(s1_path).convert("RGBA")
 
627
 
628
- # ---- T-포즈 (ControlNet/OpenPose)
629
- # ---- T-포즈 (ControlNet/OpenPose)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
630
  if enforce_tpose:
631
  try:
632
  from diffusers import ControlNetModel, StableDiffusionControlNetImg2ImgPipeline
633
- import torch
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
- ).to(dev)
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
- img = pipe(
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
- # generator=generator,
659
- ).images[0].convert("RGBA")
660
-
661
  except Exception as e:
662
- logs.append(f"T-포즈 실패: {e}")
663
-
664
 
665
- # ---- img2img 리드로우 (옵션)
666
- # ---- 리드로우
667
  if do_redraw_flag:
668
  try:
 
 
 
 
 
 
 
 
669
  img_for_redraw = _resize_to_multiple(img.convert("RGB"), multiple=8, max_side=768)
670
- img = pipe(
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
- # generator=generator,
677
- ).images[0].convert("RGBA")
 
678
  except Exception as e:
679
- logs.append(f"리드로우 실패: {e}")
680
-
681
 
 
682
  out_path = _save_png(img, OUT / "step1" / "input_preprocessed.png")
683
- return [(out_path, "refined")], out_path, "\n".join(logs)
 
 
 
 
 
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)