cut-mini-size.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. """
  2. 根据抠图结果裁剪多余的透明区域,同时保证整个文件夹内的图片尺寸一致
  3. """
  4. import sys
  5. import os
  6. from glob import glob
  7. import numpy as np
  8. from PIL import Image
  9. SUPPORTED_FORMATS = [".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tiff", ".tif"]
  10. def collect_image_files(folder):
  11. """收集文件夹中所有支持格式的图片路径"""
  12. image_paths = []
  13. for ext in SUPPORTED_FORMATS:
  14. image_paths.extend(glob(os.path.join(folder, f"*{ext}")))
  15. image_paths.extend(glob(os.path.join(folder, f"*{ext.upper()}")))
  16. # 去重并排序(Windows文件系统不区分大小写)
  17. return sorted(set(image_paths))
  18. def compute_global_bbox(image_paths, alpha_threshold=0):
  19. """
  20. 计算所有图片中非透明像素的全局 bounding box
  21. Returns: (left, top, right, bottom)
  22. """
  23. min_x = None
  24. min_y = None
  25. max_x = None
  26. max_y = None
  27. for path in image_paths:
  28. try:
  29. image = Image.open(path).convert("RGBA")
  30. alpha = np.array(image)[:, :, 3]
  31. mask = alpha > alpha_threshold
  32. if not np.any(mask):
  33. continue
  34. rows, cols = np.where(mask)
  35. left, right = int(cols.min()), int(cols.max())
  36. top, bottom = int(rows.min()), int(rows.max())
  37. min_x = left if min_x is None else min(min_x, left)
  38. min_y = top if min_y is None else min(min_y, top)
  39. max_x = right if max_x is None else max(max_x, right)
  40. max_y = bottom if max_y is None else max(max_y, bottom)
  41. except Exception as error:
  42. print(f"读取 {path} 时出错: {error}")
  43. if min_x is None or min_y is None or max_x is None or max_y is None:
  44. return None
  45. # PIL 的 crop 右下角是开区间,因此 +1
  46. return (min_x, min_y, max_x + 1, max_y + 1)
  47. def crop_images(input_folder, output_folder):
  48. """裁剪输入文件夹中的图片并输出到指定文件夹"""
  49. print('\n' + '=' * 60)
  50. print('[Step 2/2] Smart Cropping')
  51. print('=' * 60)
  52. if not os.path.exists(input_folder):
  53. print(f"[X] Error: Input folder not found: {input_folder}")
  54. return 0
  55. print(f"-> Input folder: {input_folder}")
  56. image_paths = collect_image_files(input_folder)
  57. if not image_paths:
  58. print(f"[X] Error: No supported image files found")
  59. return 0
  60. print(f"[OK] Found {len(image_paths)} images")
  61. print(f"-> Computing global crop area...")
  62. bbox = compute_global_bbox(image_paths)
  63. if bbox is None:
  64. print("[X] Warning: No valid non-transparent pixels detected")
  65. return 0
  66. left, top, right, bottom = bbox
  67. target_width = right - left
  68. target_height = bottom - top
  69. print(f"[OK] Crop area: left={left}, top={top}, width={target_width}, height={target_height}")
  70. os.makedirs(output_folder, exist_ok=True)
  71. print(f"-> Output folder: {output_folder}")
  72. print('-' * 60)
  73. success = 0
  74. for idx, path in enumerate(image_paths, 1):
  75. try:
  76. filename = os.path.basename(path)
  77. print(f"[{idx}/{len(image_paths)}] Cropping: {filename}")
  78. print(f'PROGRESS: {idx}/{len(image_paths)}')
  79. image = Image.open(path).convert("RGBA")
  80. cropped = image.crop(bbox)
  81. # 确保裁剪后的尺寸一致
  82. if cropped.size != (target_width, target_height):
  83. fixed = Image.new("RGBA", (target_width, target_height), (0, 0, 0, 0))
  84. fixed.paste(cropped, (0, 0), cropped)
  85. cropped = fixed
  86. output_path = os.path.join(output_folder, filename)
  87. cropped.save(output_path, "PNG")
  88. print(f" [OK] Done")
  89. success += 1
  90. except Exception as error:
  91. print(f" [X] Failed: {error}")
  92. print('\n' + '=' * 60)
  93. print(f"[Step 2 Complete] Success: {success}/{len(image_paths)}")
  94. print('=' * 60)
  95. return success
  96. if __name__ == "__main__":
  97. if len(sys.argv) != 3:
  98. print("用法: python cut-mini-size.py <输入文件夹> <输出文件夹>")
  99. sys.exit(1)
  100. input_folder = sys.argv[1]
  101. output_folder = sys.argv[2]
  102. processed = crop_images(input_folder, output_folder)
  103. sys.exit(0 if processed > 0 else 1)