| | 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) |
| |
|