考え方
元の画像サイズ (w, h)
サムネイルサイズ (box_w, box_h)
とした場合、サムネイルの大きさ一杯に収まるようにアスペクト比を変えないでリサイズするという処理は
w * scale <= box_w
h * scale <= box_h
という制約条件を満たす scale の最大化問題になります。
変形すると、
scale <= box_w / w
scale <= box_h / h
となるので、これを満たす最大の scale は min(box_w / w, box_h / h) になります。
python
1from pathlib import Path
2
3from PIL import Image
4
5
6def get_img_paths(img_dir):
7 """画像のパスを取得する。
8 """
9 IMG_EXTENSIONS = (".jpg", ".jpeg", ".png", ".bmp")
10 img_paths = [p for p in img_dir.iterdir() if p.suffix in IMG_EXTENSIONS]
11
12 return img_paths
13
14
15def resize(img, box_size, border=True):
16 """
17 """
18 box_width, box_height = box_size
19 scale_x = box_width / img.width
20 scale_y = box_height / img.height
21 scale = min(scale_x, scale_y)
22
23 # リサイズ後の大きさを計算する。
24 new_width = int(img.width * scale)
25 new_height = int(img.height * scale)
26
27 # リサイズする。
28 resized = img.resize((new_width, new_height))
29
30 if border:
31 # 画枠をつける場合
32 new_img = Image.new("RGB", box_size)
33
34 paste_x = (box_width - new_width) // 2
35 paste_y = (box_height - new_height) // 2
36 new_img.paste(resized, (paste_x, paste_y))
37
38 return new_img
39 else:
40 return resized
41
42
43box_size = (400, 400) # サムネイルの大きさ
44input_dir = Path("samples")
45output_dir = Path("resized")
46output_dir.mkdir(exist_ok=True)
47
48for path in get_img_paths(input_dir):
49 # 画像を読み込む。
50 img = Image.open(path)
51
52 # リサイズする。
53 resized = resize(img, box_size)
54 print(f"{path.name}: {img.size} --> {resized.size}")
55
56 # 保存する。
57 resized.save(output_dir / path.name)

↓

追記
- アスペクト比を固定して、短辺をサムネイル枠に合わせてリサイズする。
- リサイズした画像を、画像の中心を基準にして、サムネイル枠に合わせてクロップする。

python
1from pathlib import Path
2
3from PIL import Image
4
5
6def get_img_paths(img_dir):
7 """画像のパスを取得する。
8 """
9 IMG_EXTENSIONS = (".jpg", ".jpeg", ".png", ".bmp")
10 img_paths = [p for p in img_dir.iterdir() if p.suffix in IMG_EXTENSIONS]
11
12 return img_paths
13
14
15def resize(img, box_size):
16 box_width, box_height = box_size
17 scale_x = box_width / img.width
18 scale_y = box_height / img.height
19 scale = max(scale_x, scale_y)
20
21 # リサイズ後の大きさを計算する。
22 if scale_x > scale_y:
23 new_width = box_width
24 new_height = int(img.height * scale)
25 else:
26 new_width = int(img.width * scale)
27 new_height = box_height
28
29 # リサイズする。
30 resized = img.resize((new_width, new_height))
31
32 # 中心で切り抜く
33 left = (new_width - box_width) // 2
34 top = (new_height - box_height) // 2
35 right = (new_width + box_width) // 2
36 bottom = (new_height + box_height) // 2
37 cropped = resized.crop((left, top, right, bottom))
38 return cropped
39
40
41box_size = (800, 400) # サムネイルの大きさ
42input_dir = Path("samples")
43output_dir = Path("resized")
44output_dir.mkdir(exist_ok=True)
45
46for path in get_img_paths(input_dir):
47 # 画像を読み込む。
48 img = Image.open(path)
49
50 # リサイズする。
51 resized = resize(img, box_size)
52 print(f"{path.name}: {img.size} --> {resized.size}")
53
54 # 保存する。
55 resized.save(output_dir / path.name)

↓
