Today, I wrote a Python script to create a collage. There are two
modes it can run in - auto_size_cell
mode, which assigns
row/column counts, and cell dimensions automatically, and
manual
mode, where you set row/column counts, and cell
dimensions yourself.
Here is the code.
import cv2
import os
import numpy as np
from statistics import median
import random
import re
def resize_and_pad(img, max_height, max_width):
h, w = img.shape[:2]
if h > max_height or w > max_width:
interpolation = cv2.INTER_AREA
else:
interpolation = cv2.INTER_CUBIC
aspect = w / h
if aspect > 1:
new_w = max_width
new_h = np.round(new_w / aspect).astype(int)
pad_vertical = (max_height - new_h) / 2
pad_top, pad_bot = np.floor(pad_vertical).astype(int), np.ceil(pad_vertical).astype(int)
pad_left, pad_right = 0, 0
elif aspect < 1:
new_h = max_height
new_w = np.round(new_h * aspect).astype(int)
pad_horizontal = (max_width - new_w) / 2
pad_left, pad_right = np.floor(pad_horizontal).astype(int), np.ceil(pad_horizontal).astype(int)
pad_top, pad_bot = 0, 0
else:
new_h, new_w = max_height, max_width
pad_left, pad_right, pad_top, pad_bot = 0, 0, 0, 0
scaled_img = cv2.resize(img, (new_w, new_h), interpolation=interpolation)
scaled_img = cv2.copyMakeBorder(
scaled_img,
pad_top,
pad_bot,
pad_left,
pad_right,
borderType=cv2.BORDER_CONSTANT,
value=[0] * len(img.shape),
)
return scaled_img
def create_mosaic(image_paths, col_count, row_count, cell_width, cell_height):
image_paths = image_paths[: col_count * row_count]
mosaic_height = row_count * cell_height
mosaic_width = col_count * cell_width
mosaic = np.zeros((mosaic_height, mosaic_width, 3), dtype=np.uint8)
for i, image_path in enumerate(image_paths):
img = cv2.imread(image_path)
img = resize_and_pad(img, cell_height, cell_width)
row = i // col_count
col = i % col_count
x1y1 = row * cell_height
x1y2 = (row + 1) * cell_height
x2y1 = col * cell_width
x2y2 = (col + 1) * cell_width
mosaic[x1y1:x1y2, x2y1:x2y2, :] = img
return mosaic
def auto_size(image_paths, scale=1):
img_count = len(image_paths)
heights = []
widths = []
for image_path in image_paths:
img = cv2.imread(image_path)
height, width = img.shape[:2]
heights.append(height)
widths.append(width)
median_w = median(widths)
median_h = median(heights)
cell_width = median_w * scale # max(median_w / 5, median_w / img_count)
cell_height = median_h * scale # max(median_h / 5, median_h / img_count)
col_count = int(img_count ** 0.5)
row_count = int(col_count * cell_width / cell_height)
return int(col_count), int(row_count), int(cell_width), int(cell_height)
runs = 1
output = True
input_folder = "/home/neetventures/Documents/images/shoegaze"
input_file_pattern = r'^.*(\.jpg|\.png|\.jpeg|\.gif)$'
output_path = '/home/neetventures/Documents/images/shoegaze/mosaic_{0}.png'
zfill = 3
shuffle_images = True
auto_size_cells = True
# if auto_size_cells:
scale = 1/4 # scaling factor to apply to the median cell heights and cell widths
img_count_max = 7 * 6
if not auto_size_cells:
col_count = 6
row_count = 6
img_count_max = col_count * row_count
cell_width = 100
cell_height = 100
os.chdir(input_folder)
image_paths = [p for p in os.listdir(input_folder) if re.fullmatch(input_file_pattern, p, re.IGNORECASE)][:img_count_max]
for i in range(runs):
assert image_paths, image_paths
if shuffle_images:
random.shuffle(image_paths)
if auto_size_cells:
col_count, row_count, cell_width, cell_height = auto_size(image_paths, scale=scale)
img = create_mosaic(image_paths, col_count, row_count, cell_width, cell_height)
if output:
cv2.imwrite(output_path.format(str(i).zfill(zfill)), img)