| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- """
- 根据抠图结果裁剪多余的透明区域,同时保证整个文件夹内的图片尺寸一致
- """
- import sys
- import os
- from glob import glob
- import numpy as np
- from PIL import Image
- SUPPORTED_FORMATS = [".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tiff", ".tif"]
- def collect_image_files(folder):
- """收集文件夹中所有支持格式的图片路径"""
- image_paths = []
- for ext in SUPPORTED_FORMATS:
- image_paths.extend(glob(os.path.join(folder, f"*{ext}")))
- image_paths.extend(glob(os.path.join(folder, f"*{ext.upper()}")))
- # 去重并排序(Windows文件系统不区分大小写)
- return sorted(set(image_paths))
- def compute_global_bbox(image_paths, alpha_threshold=0):
- """
- 计算所有图片中非透明像素的全局 bounding box
- Returns: (left, top, right, bottom)
- """
- min_x = None
- min_y = None
- max_x = None
- max_y = None
- for path in image_paths:
- try:
- image = Image.open(path).convert("RGBA")
- alpha = np.array(image)[:, :, 3]
- mask = alpha > alpha_threshold
- if not np.any(mask):
- continue
- rows, cols = np.where(mask)
- left, right = int(cols.min()), int(cols.max())
- top, bottom = int(rows.min()), int(rows.max())
- min_x = left if min_x is None else min(min_x, left)
- min_y = top if min_y is None else min(min_y, top)
- max_x = right if max_x is None else max(max_x, right)
- max_y = bottom if max_y is None else max(max_y, bottom)
- except Exception as error:
- print(f"读取 {path} 时出错: {error}")
- if min_x is None or min_y is None or max_x is None or max_y is None:
- return None
- # PIL 的 crop 右下角是开区间,因此 +1
- return (min_x, min_y, max_x + 1, max_y + 1)
- def crop_images(input_folder, output_folder):
- """裁剪输入文件夹中的图片并输出到指定文件夹"""
- print('\n' + '=' * 60)
- print('[Step 2/2] Smart Cropping')
- print('=' * 60)
-
- if not os.path.exists(input_folder):
- print(f"[X] Error: Input folder not found: {input_folder}")
- return 0
- print(f"-> Input folder: {input_folder}")
-
- image_paths = collect_image_files(input_folder)
- if not image_paths:
- print(f"[X] Error: No supported image files found")
- return 0
- print(f"[OK] Found {len(image_paths)} images")
- print(f"-> Computing global crop area...")
- bbox = compute_global_bbox(image_paths)
- if bbox is None:
- print("[X] Warning: No valid non-transparent pixels detected")
- return 0
- left, top, right, bottom = bbox
- target_width = right - left
- target_height = bottom - top
- print(f"[OK] Crop area: left={left}, top={top}, width={target_width}, height={target_height}")
- os.makedirs(output_folder, exist_ok=True)
- print(f"-> Output folder: {output_folder}")
- print('-' * 60)
- success = 0
- for idx, path in enumerate(image_paths, 1):
- try:
- filename = os.path.basename(path)
- print(f"[{idx}/{len(image_paths)}] Cropping: {filename}")
- print(f'PROGRESS: {idx}/{len(image_paths)}')
-
- image = Image.open(path).convert("RGBA")
- cropped = image.crop(bbox)
- # 确保裁剪后的尺寸一致
- if cropped.size != (target_width, target_height):
- fixed = Image.new("RGBA", (target_width, target_height), (0, 0, 0, 0))
- fixed.paste(cropped, (0, 0), cropped)
- cropped = fixed
- output_path = os.path.join(output_folder, filename)
- cropped.save(output_path, "PNG")
- print(f" [OK] Done")
- success += 1
- except Exception as error:
- print(f" [X] Failed: {error}")
- print('\n' + '=' * 60)
- print(f"[Step 2 Complete] Success: {success}/{len(image_paths)}")
- print('=' * 60)
- return success
- if __name__ == "__main__":
- if len(sys.argv) != 3:
- print("用法: python cut-mini-size.py <输入文件夹> <输出文件夹>")
- sys.exit(1)
-
- input_folder = sys.argv[1]
- output_folder = sys.argv[2]
-
- processed = crop_images(input_folder, output_folder)
- sys.exit(0 if processed > 0 else 1)
|