Spaces:
Running on Zero
Running on Zero
fix: pad-to-bucket instead of stretch, preserve exact aspect ratio, edge-fill padding
Browse files
app.py
CHANGED
|
@@ -380,23 +380,61 @@ def prepare_images_before_pipe(
|
|
| 380 |
pil_images: List[Image.Image],
|
| 381 |
allow_upscale: bool = UPSCALE_SMALL_IMAGES,
|
| 382 |
divisible_by: int = 16,
|
| 383 |
-
) -> Tuple[List[Image.Image], int, int]:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
if not pil_images:
|
| 385 |
raise ValueError("No input images.")
|
| 386 |
|
| 387 |
base_w, base_h = pil_images[0].size
|
| 388 |
|
| 389 |
-
#
|
| 390 |
-
|
| 391 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 392 |
|
| 393 |
processed = []
|
| 394 |
for img in pil_images:
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 400 |
|
| 401 |
|
| 402 |
def extract_pil_from_source(source) -> Image.Image:
|
|
@@ -497,7 +535,7 @@ def infer(
|
|
| 497 |
|
| 498 |
generator = torch.Generator(device=device).manual_seed(int(seed))
|
| 499 |
|
| 500 |
-
processed_images, width, height = prepare_images_before_pipe(
|
| 501 |
pil_images, allow_upscale=UPSCALE_SMALL_IMAGES
|
| 502 |
)
|
| 503 |
|
|
@@ -519,7 +557,12 @@ def infer(
|
|
| 519 |
true_cfg_scale=guidance_scale,
|
| 520 |
).images[0]
|
| 521 |
|
| 522 |
-
# ── 还原到原始
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 523 |
if result.size != orig_size:
|
| 524 |
result = result.resize(orig_size, Image.LANCZOS)
|
| 525 |
|
|
|
|
| 380 |
pil_images: List[Image.Image],
|
| 381 |
allow_upscale: bool = UPSCALE_SMALL_IMAGES,
|
| 382 |
divisible_by: int = 16,
|
| 383 |
+
) -> Tuple[List[Image.Image], int, int, tuple]:
|
| 384 |
+
"""准备图片:等比缩放 + 补边到最佳 bucket,保留原始比例。
|
| 385 |
+
返回 (processed_images, width, height, pad_info)
|
| 386 |
+
pad_info = (pad_left, pad_top, content_w, content_h) 用于推理后裁剪补边。
|
| 387 |
+
"""
|
| 388 |
if not pil_images:
|
| 389 |
raise ValueError("No input images.")
|
| 390 |
|
| 391 |
base_w, base_h = pil_images[0].size
|
| 392 |
|
| 393 |
+
# 选最佳 bucket(~1MP,比例最接近)
|
| 394 |
+
bucket_w, bucket_h = pick_best_bucket(base_w, base_h, SAFE_BUCKETS, allow_upscale)
|
| 395 |
+
|
| 396 |
+
# 等比缩放 fit 到 bucket 内(不拉伸)
|
| 397 |
+
scale = min(bucket_w / base_w, bucket_h / base_h)
|
| 398 |
+
content_w = max(divisible_by, round(base_w * scale))
|
| 399 |
+
content_h = max(divisible_by, round(base_h * scale))
|
| 400 |
+
|
| 401 |
+
# 居中补边到 bucket 尺寸
|
| 402 |
+
pad_left = (bucket_w - content_w) // 2
|
| 403 |
+
pad_top = (bucket_h - content_h) // 2
|
| 404 |
+
pad_info = (pad_left, pad_top, content_w, content_h)
|
| 405 |
|
| 406 |
processed = []
|
| 407 |
for img in pil_images:
|
| 408 |
+
# 等比缩放
|
| 409 |
+
resized = img.resize((content_w, content_h), Image.LANCZOS)
|
| 410 |
+
# 创建 bucket 大小的画布,边缘用镜像填充减少接缝
|
| 411 |
+
canvas = Image.new("RGB", (bucket_w, bucket_h), (0, 0, 0))
|
| 412 |
+
canvas.paste(resized, (pad_left, pad_top))
|
| 413 |
+
|
| 414 |
+
# 用边缘像素填充补边区域(比纯黑效果好)
|
| 415 |
+
import numpy as _np
|
| 416 |
+
arr = np.array(canvas)
|
| 417 |
+
res_arr = np.array(resized)
|
| 418 |
+
# 填充左右
|
| 419 |
+
if pad_left > 0:
|
| 420 |
+
left_col = res_arr[:, 0:1, :]
|
| 421 |
+
arr[pad_top:pad_top+content_h, :pad_left, :] = np.broadcast_to(left_col, (content_h, pad_left, 3))
|
| 422 |
+
right_start = pad_left + content_w
|
| 423 |
+
if right_start < bucket_w:
|
| 424 |
+
right_col = res_arr[:, -1:, :]
|
| 425 |
+
arr[pad_top:pad_top+content_h, right_start:, :] = np.broadcast_to(right_col, (content_h, bucket_w - right_start, 3))
|
| 426 |
+
# 填充上下
|
| 427 |
+
if pad_top > 0:
|
| 428 |
+
top_row = arr[pad_top:pad_top+1, :, :]
|
| 429 |
+
arr[:pad_top, :, :] = np.broadcast_to(top_row, (pad_top, bucket_w, 3))
|
| 430 |
+
bottom_start = pad_top + content_h
|
| 431 |
+
if bottom_start < bucket_h:
|
| 432 |
+
bottom_row = arr[bottom_start-1:bottom_start, :, :]
|
| 433 |
+
arr[bottom_start:, :, :] = np.broadcast_to(bottom_row, (bucket_h - bottom_start, bucket_w, 3))
|
| 434 |
+
|
| 435 |
+
processed.append(Image.fromarray(arr))
|
| 436 |
+
|
| 437 |
+
return processed, bucket_w, bucket_h, pad_info
|
| 438 |
|
| 439 |
|
| 440 |
def extract_pil_from_source(source) -> Image.Image:
|
|
|
|
| 535 |
|
| 536 |
generator = torch.Generator(device=device).manual_seed(int(seed))
|
| 537 |
|
| 538 |
+
processed_images, width, height, pad_info = prepare_images_before_pipe(
|
| 539 |
pil_images, allow_upscale=UPSCALE_SMALL_IMAGES
|
| 540 |
)
|
| 541 |
|
|
|
|
| 557 |
true_cfg_scale=guidance_scale,
|
| 558 |
).images[0]
|
| 559 |
|
| 560 |
+
# ── 裁掉补边,还原到原始比例内容区域 ──
|
| 561 |
+
pad_left, pad_top, content_w, content_h = pad_info
|
| 562 |
+
if pad_left > 0 or pad_top > 0 or content_w < width or content_h < height:
|
| 563 |
+
result = result.crop((pad_left, pad_top, pad_left + content_w, pad_top + content_h))
|
| 564 |
+
|
| 565 |
+
# ── 还原到原始尺寸 ──
|
| 566 |
if result.size != orig_size:
|
| 567 |
result = result.resize(orig_size, Image.LANCZOS)
|
| 568 |
|