Skip to content

Instantly share code, notes, and snippets.

@sveitser
Last active July 12, 2020 13:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sveitser/e263dd031b70c0f93a6e93f928fd7f69 to your computer and use it in GitHub Desktop.
Save sveitser/e263dd031b70c0f93a6e93f928fd7f69 to your computer and use it in GitHub Desktop.
Resize and center crop a directory of images.
#!/usr/bin/env python
"""Resize and crop images to square.
Install dependencies: pip install click Pillow
Usage: python resize.py [OPTIONS] --directory /my/image/dir --convert_directory /my/output/dir
Options:
--directory TEXT Directory with original images. [required]
--convert_directory TEXT Where to save converted images. [required]
--test Convert images one by one and examine them on
screen. [default: False]
-s, --size INTEGER Size of converted images. [default: 512]
-n, --n-proc INTEGER Number of processes (defaults to all) [default:
0]
-q, --quality INTEGER [default: 95]
--help Show this message and exit.
"""
import os
import multiprocessing
from multiprocessing.pool import Pool
from pathlib import Path
import click
from PIL import Image
N_PROC = None
ALLOWED_EXTENSIONS = set([".jpg", ".jpeg", ".png", ".tiff"])
QUALITY = 95
def square_bbox(img):
w, h = img.size
left = max((w - h) // 2, 0)
upper = 0
right = min(w - (w - h) // 2, w)
lower = h
return (left, upper, right, lower)
def convert_square(fname, size):
img = Image.open(fname)
bbox = square_bbox(img)
cropped = img.crop(bbox)
resized = cropped.resize([size, size])
return resized
def get_convert_fname(fname, directory, convert_directory):
return str(os.path.normpath(fname)).replace(
os.path.normpath(directory), os.path.normpath(convert_directory)
)
def process(args):
directory, convert_directory, fname, size = args
convert_fname = get_convert_fname(fname, directory, convert_directory)
print(f"{fname} -> {convert_fname}")
if os.path.exists(convert_fname):
print(f"File {convert_fname} exists already")
return
os.makedirs(os.path.dirname(convert_fname), exist_ok=True)
try:
img = convert_square(fname, size)
except OSError as exc:
print(f"{fname}: {exc}")
return
save(img, convert_fname)
def save(img, fname):
img.save(fname, quality=QUALITY)
@click.command()
@click.option(
"--directory", help="Directory with original images.", required=True,
)
@click.option(
"--convert-directory", help="Where to save converted images.", required=True,
)
@click.option(
"--test",
is_flag=True,
default=False,
show_default=True,
help="Convert images one by one and examine them on screen.",
)
@click.option(
"--size", "-s", default=512, show_default=True, help="Size of converted images."
)
@click.option(
"--n-proc",
"-n",
default=0,
show_default=True,
type=int,
help="Number of processes (defaults to all)",
)
@click.option("--quality", "-q", default=QUALITY, show_default=True)
def main(directory, convert_directory, test, size, n_proc, quality):
global N_PROC, QUALITY
N_PROC = n_proc or multiprocessing.cpu_count()
QUALITY = quality
os.makedirs(convert_directory, exist_ok=True)
filenames = list(Path(directory).rglob("*.*"))
print(f"Found {len(filenames)} files")
filenames = [
f for f in filenames if os.path.splitext(f)[1].lower() in ALLOWED_EXTENSIONS
]
n = len(filenames)
print(
f"Resizing {n} images in {directory} to {convert_directory} with size {size}."
)
pool = Pool(N_PROC)
args = []
for f in filenames:
args.append((directory, convert_directory, f, size))
pool.map(process, args, chunksize=1000)
pool.close()
pool.join()
print("done")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment