Implementing Photomosaics

Last Updated : 24 Jan, 2026

A photomosaic is an image composed of many smaller images arranged in a grid. From a distance the mosaic appears to be a single target image. When viewed up close it reveals thousands of individual images carefully selected to match different regions of the target.

This illusion works because the human visual system blends colors and shapes at a distance allowing coarse color matching to recreate fine visual detail. Photomosaics are widely used in:

  • Digital art and posters
  • Marketing visuals
  • Image processing research
  • Educational projects for computer vision fundamentals

Types of Photomosaics

1. Simple (Average-Color-Based) Photomosaic

  • Each target tile and library image is reduced to a single average RGB color and the tile is replaced by the library image with the closest color match.
  • This method is fast, scalable and easy to implement but it cannot preserve fine textures or details.

2. Advanced (Pixel-by-Pixel Matching) Photomosaic

  • Each pixel of a target tile is compared with the corresponding pixels of every library image and the image with the smallest total difference is selected.
  • This produces much higher visual quality and detail but is computationally expensive and often requires GPU acceleration.

How to Create Photomosaics

The process of creating a photomosaic involves transforming a target image into a grid of tiles and replacing each tile with the most visually similar image from a collection of smaller images. The complete workflow consists of the following steps:

  • Load tile (library) images that will be used to construct the mosaic.
  • Read the target image and divide it into an M × N grid of equal-sized tiles.
  • Analyze each target tile to extract its color characteristics.
  • Match each target tile with the most similar tile image from the library.
  • Assemble the final mosaic by arranging the selected tile images in their respective grid positions.

1. Splitting the Images into Tiles

The target image is divided into an M x N grid, where each tile represents a rectangular region of the image. For a tile at position (i, j), the coordinates are computed using the tile width (w) and height (h) to ensure uniform partitioning.

ml
Splitting

2. Averaging Color Values

Each image tile is represented by its average RGB color, calculated by averaging the red, green and blue values of all pixels in the tile. This reduces a complex image region to a single color vector, enabling fast and efficient comparison.

(r, g, b)_{avg} = \left( \frac{1}{N} \sum r_i, \frac{1}{N} \sum g_i, \frac{1}{N} \sum b_i \right)

where

  • (r, g, b)_{avg}: average color of an image or image tile in the RGB color space
  • r_{i},g_{i},b_{i}: The red, green and blue values of the i-th pixel in the image or tile
  • N: The total number of pixels in the image or tile being analyzed

3. Matching Images

For each target tile the average RGB value is compared with the average RGB values of all library images using a distance metric. The tile image with the minimum color distance is selected as the best match.

D = \sqrt{(r_1 - r_2)^2 + (g_1 - g_2)^2 + (b_1 - b_2)^2}

where D Represents the Euclidean distance between two colors in RGB color space

Step By Step Implementation

Here we implement Photomosaic

Step 1: Install Dependencies

Here we Installs Pillow for image processing and NumPy for numerical operations.

Python
!pip install pillow numpy

Step 2: Import Libraries

Here we import os, zipfile, numpy, Image, files and Matplotlib

Python
import os
import zipfile
import numpy as np
from PIL import Image
from google.colab import files
import matplotlib.pyplot as plt

Step 3: Define Function to Compute Average RGB Color

  • Converts an image to a NumPy array and calculates the mean red, green and blue values across all pixels.
  • This single vector represents the color of the tile for comparison.
Python
def average_color(image):
    img = np.asarray(image)
    return img.mean(axis=(0, 1))

Step 4: Upload Target Image

Here we upload taget image and converts the image to RGB mode for consistent processing.

dfsd8-90
Input image
Python
uploaded_target = files.upload()
target_path = list(uploaded_target.keys())[0]
target_image = Image.open(target_path).convert("RGB")

Step 5: Upload Tile Images ZIP

  • user upload a ZIP file containing tile images.
  • Extracts the images into a tiles folder.

You can download a ZIP file of the color dataset from Kaggle

Python
uploaded_zip = files.upload()
zip_path = list(uploaded_zip.keys())[0]
tile_folder = "tiles"
os.makedirs(tile_folder, exist_ok=True)

with zipfile.ZipFile(zip_path, 'r') as zip_ref:
    zip_ref.extractall(tile_folder)

Step 6: Load Tile Images and Precompute Colors

Loads each image from the folder, converts it to RGB and resizes it to match tile dimensions. Computes average RGB color for each tile.

Python
def load_tile_images(folder, tile_size):
    tiles = []
    colors = []

    for file in os.listdir(folder):
        path = os.path.join(folder, file)
        try:
            img = Image.open(path).convert("RGB")
            img = img.resize(tile_size)
            tiles.append(img)
            colors.append(average_color(img))
        except:
            pass

    return tiles, np.array(colors)

Step 7: Find Best Matching Tile

Calculates Euclidean distance between the target tile color and all tile images. Returns the index of the closest matching tile.

Python
def find_best_match(target_color, tile_colors):
    distances = np.linalg.norm(tile_colors - target_color, axis=1)
    return np.argmin(distances)

Step 8: Create Photomosaic

Divides the target image into a grid of MxN tiles. For each tile computes the average color, finds the best matching tile and pastes it into the mosaic.

Python
def create_mosaic(target_image, tiles, tile_colors, grid_size):
    W, H = target_image.size
    M, N = grid_size

    tile_w, tile_h = W // M, H // N
    mosaic = Image.new("RGB", (W, H))

    for i in range(M):
        for j in range(N):
            box = (i * tile_w, j * tile_h,
                   (i + 1) * tile_w, (j + 1) * tile_h)

            tile = target_image.crop(box)
            target_color = average_color(tile)
            best_idx = find_best_match(target_color, tile_colors)

            mosaic.paste(tiles[best_idx], box)

    return mosaic

Step 9: User Input for Grid Size

Allows the user to choose the grid resolution. Higher grid numbers give more detailed mosaics, but require more tile images.

Python
M = int(input("Enter number of tiles along width (e.g., 64): "))
N = int(input("Enter number of tiles along height (e.g., 64): "))
grid_size = (M, N)

Output:

Enter number of tiles along width (e.g., 64): 96

Enter number of tiles along height (e.g., 64): 96

Step 10: Handle Tile Folder Structure

  • Handles cases where images are in a subfolder. Ensures the code finds the correct folder containing actual tile images.
  • Loads all tile images and computes their average colors.
  • Raises an error if no valid images are found, guiding the user to fix the ZIP content.
Python
tile_w = target_image.size[0] // M
tile_h = target_image.size[1] // N

actual_tile_image_folder = tile_folder
extracted_items = os.listdir(tile_folder)

if len(extracted_items) == 1 and os.path.isdir(os.path.join(tile_folder, extracted_items[0])):
    actual_tile_image_folder = os.path.join(tile_folder, extracted_items[0])
elif 'Images' in extracted_items and os.path.isdir(os.path.join(tile_folder, 'Images')):
    actual_tile_image_folder = os.path.join(tile_folder, 'Images')
elif 'images' in extracted_items and os.path.isdir(os.path.join(tile_folder, 'images')):
    actual_tile_image_folder = os.path.join(tile_folder, 'images')

tiles, tile_colors = load_tile_images(actual_tile_image_folder, (tile_w, tile_h))

if not tiles:
    raise ValueError("No tile images were successfully loaded from the ZIP file.")

Step 11: Generate Mosaic

Here we creates the photomosaic using the target image, tile images and user-specified grid size

Python
mosaic = create_mosaic(target_image, tiles, tile_colors, grid_size)
plt.figure(figsize=(10, 10))
plt.imshow(mosaic)
plt.axis("off")

mosaic.save("photomosaic_output.jpg")
files.download("photomosaic_output.jpg")

Output:

imhg45
Output

You can download full code from here

Comment