Some utils function to quickly download a bunch of images, check them and pre-resize them
Downloading images
download_images uses random UUID filenames by default. With preserve_filename=True, it uses the URL’s stem. It adds a numeric suffix if a file with the same filename already exists in dest.
verify_image uses PIL’s draft mode to decode JPEGs at reduced resolution. This keeps verification fast even for large images. It returns False if opening or decoding the file raises an exception.
with tempfile.TemporaryDirectory() as d: d = Path(d) shutil.copy('images/puppy.jpg', d) (d/'bad.jpg').write_text('not an image') failed = verify_images(get_image_files(d)) test_eq(failed, [d/'bad.jpg']) failed.map(Path.unlink) test_eq(get_image_files(d), [d/'puppy.jpg'])
Size to resize to, to hit targ_sz at same aspect ratio, in PIL coords (i.e wh)*
resize_to calculates dimensions at the original aspect ratio. By default, the larger side equals targ_sz and the result fits inside a targ_sz square. With use_min=True, the smaller side equals targ_sz and the result covers that square. The input needs a PIL-style size attribute:
def resize_image(file, # Image path, relative to `src` dest, # Output directory src:str='.', # Directory that `file` is relative to max_size:NoneType=None, # Resize so the larger side is at most this many pixels n_channels:int=3, # Convert to RGB when 3 ext:NoneType=None, # Output suffix, e.g. '.png'; default keeps the original img_format:NoneType=None, # PIL format for `save`; default inferred from the suffix resample:PIL.Image.Resampling=<Resampling.BILINEAR: 2>, # PIL resampling filter resume:bool=False, # Skip files already present in `dest`?**kwargs):
Resize file to dest to max_size
resize_image re-encodes files that exceed max_size or have a different number of channels from n_channels. It copies other files unchanged to dest, unless source and destination are the same. Use resume=True to skip existing destination files when resuming an interrupted run.
def resize_images( path, # Directory of images max_workers:int=4, # Parallel workers; 0 runs in the current process max_size:NoneType=None, # Resize so the larger side is at most this many pixels recurse:bool=False, # Include subdirectories? dest:pathlib.PosixPath=Path('.'), # Output directory n_channels:int=3, # Convert to RGB when 3 ext:NoneType=None, # Output suffix, e.g. '.png'; default keeps the original img_format:NoneType=None, # PIL format for `save`; default inferred from the suffix resample:PIL.Image.Resampling=<Resampling.BILINEAR: 2>, # PIL resampling filter resume:NoneType=None, # Skip files already present in `dest`?**kwargs):
Resize the images in path to dest, so the larger side is at most max_size
resize_images processes images in parallel and preserves the directory structure under dest:
with tempfile.TemporaryDirectory() as d: dest = Path(d)/'resized_images' resize_images('images', max_size=100, dest=dest, max_workers=0, recurse=True)
Image search
Microsoft has retired the Bing Image Search API that earlier versions of these functions, and the fastai book, relied on. These functions use SerpApi instead, which serves Bing and other engines’ results through one API. Its free plan allows 250 searches a month, and each page of 35 Bing results counts as one search, so a 150-image query costs 5.
def search_images_serpapi( term, # Search query engine:str='bing_images', # Any SerpApi image engine, e.g. 'google_images' key:NoneType=None, # SerpApi key; default `SERPAPI_KEY` environment variable max_images:int=150, # Stop after this many URLs):
Image URLs for term from SerpApi’s engine, following pagination up to max_images
search_images_serpapi follows pagination links until it has max_images URLs or there are no more pages. It raises HTTP errors rather than returning partial results. Pass engine='google_images' to search Google images instead of Bing.