| import numpy as np |
| import PIL.Image |
|
|
| ANY_ASPECT_RATIO = (0, 0) |
|
|
| HW_ASPECT_RATIOS = [ |
| (8, 32), |
| (9, 28), |
| (10, 25), |
| (11, 23), |
| (12, 21), |
| (13, 19), |
| (14, 18), |
| (15, 17), |
| (16, 16), |
| (17, 15), |
| (18, 14), |
| (19, 13), |
| (21, 12), |
| (23, 11), |
| (25, 10), |
| (28, 9), |
| (32, 8), |
| ] |
|
|
|
|
| def get_ar_base(ars: list[tuple[int, int]] = HW_ASPECT_RATIOS): |
| sqrt_products = [round(np.sqrt(h * w)) for h, w in ars] |
| return round(np.mean(sqrt_products)) |
|
|
|
|
| def ar2str(h: int, w: int) -> str: |
| return f"{h}*{w}" |
|
|
|
|
| def str2ar(s: str) -> tuple[int, int]: |
| return tuple(map(int, s.split("*"))) |
|
|
| def center_crop_arr_with_buckets(pil_image, ars: list[tuple[int, int]] = HW_ASPECT_RATIOS, crop=True, buckets: list[int] = [256, 512, 768, 1024]): |
| """ |
| Center crop the image to match the closest aspect ratio from the provided list. |
| |
| Args: |
| pil_image: PIL Image to be cropped |
| image_size: Target size for the smaller dimension |
| ars: List of aspect ratios as (height, width) tuples |
| |
| Returns: |
| PIL Image cropped to the closest aspect ratio |
| """ |
| |
| |
| width, height = pil_image.size |
| |
| buckets = sorted(buckets, reverse=True) |
| image_size = buckets[-1] |
|
|
| for bucket in buckets: |
| if width * height >= bucket * bucket: |
| image_size = bucket |
| break |
|
|
| return center_crop_arr_with_ar(pil_image, image_size, ars, crop) |
|
|
| def center_crop_arr_with_ar(pil_image, image_size: int, ars: list[tuple[int, int]] = HW_ASPECT_RATIOS, crop=True): |
| """ |
| Center crop the image to match the closest aspect ratio from the provided list. |
| |
| Args: |
| pil_image: PIL Image to be cropped |
| image_sizes: Target size for the smaller dimension |
| ars: List of aspect ratios as (height, width) tuples |
| |
| Returns: |
| PIL Image cropped to the closest aspect ratio |
| """ |
|
|
| ar_base = get_ar_base(ars) |
| assert image_size % ar_base == 0, f"image_size must be divisible by {ar_base}" |
|
|
| |
| width, height = pil_image.size |
| |
| current_ar = height / width |
|
|
| |
| closest_ar_idx = np.argmin([abs(current_ar - (h / w)) for h, w in ars]) |
| target_h, target_w = ars[closest_ar_idx] |
|
|
| if crop: |
| target_h, target_w = round(image_size / ar_base * target_h), round(image_size / ar_base * target_w) |
|
|
| |
| scale = max(target_h / height, target_w / width) |
| new_height = round(height * scale) |
| new_width = round(width * scale) |
| pil_image = pil_image.resize((new_width, new_height), resample=PIL.Image.LANCZOS) |
|
|
| arr = np.array(pil_image) |
| |
| crop_y = (new_height - target_h) // 2 |
| crop_x = (new_width - target_w) // 2 |
|
|
| return PIL.Image.fromarray(arr[crop_y : crop_y + target_h, crop_x : crop_x + target_w]) |
| else: |
| scale = image_size // ar_base |
| return pil_image.resize((round(target_w * scale), round(target_h * scale)), resample=PIL.Image.LANCZOS) |
|
|