Collage Creator

Friday, January 05 2024

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)
Comment
Optional
No comments yet...