Implementation of photomosaics



Photomosaic — it is an image, broken down into a grid of rectangles, each of which is replaced by a different image that suits the purpose (the image you ultimately want to display in this photo). In other words, if you look at the mosaic from a distance, you will see an image of the target; but if you get closer, you can see that the image is actually made up of many small images. This works because of the way the human eye works.

There are two kinds of mosaics, depending on how the matching is done. In a simpler form, each portion of the target image is averaged to one color. Each of the images in the library is also reduced to one color. Each part of the target image is then replaced with one part from the library where the colors are as similar as possible. Essentially, the target image is downsampled (downsampled) and then each of the resulting pixels is replaced with an image whose average color corresponds to that pixel.

In a more advanced form of photographic mosaic, the target image is not downsampled. and matching is performed by comparing each pixel in the rectangle with a corresponding pixel from each library image. The rectangle in the target is then replaced with the library image, which minimizes the overall difference. This is much more computationally intensive than the simple type, but the results can be much better as the pixel-by-pixel matching can preserve the resolution of the target image.

How do I create a photo mosaic?

  1. Read the tile images that will replace the tiles in the original image.
  2. Read the target image and break it down into an M × N grid of tiles.
  3. For each tile, find the best match from the input images.
  4. Create a final mosaic by positioning the selected input images on an M × N grid

Tiling images

Now let`s see how to calculate coordinates for one tile from this grid. A tile with index (i, j) has an upper left corner coordinate (i * w, i * j) and a lower right corner coordinate ((i + 1) * w, (j + 1) * h), where w and h denote the width and height of the tiles, respectively. They can be used with PIL to crop and tile this image.

Averaging color values ​​

Every pixel in an image has a color that can be represented its values ​​for red, green and blue. In this case, you are using 8-bit images, so each of these components has an 8-bit value in the range [0, 255]. For an image with a total of N pixels, the average RGB value is calculated as follows:

Matching Images

For each tile in the target image, you need to find the matching image from the images in the user-specified input folder. Use RGB averages to determine if two images are the same. The closest match is the image with the nearest RGB mean.
The easiest way to do this is — calculate the distance between RGB values ​​in a pixel to find the best match among the input images. You can use the following distance calculation for 3D points from geometry:

Now let`s try to code this

# Import required libraries

import os, random, argparse

from PIL import Image

import imghdr

import numpy as np

 

def getAverageRGBOld (image ):

"" »

  For a given PIL image, return the average color value as (r, g, b)

"" "

  # # of pixels in the image

  npixels = image.size [ 0 ] * image.size [ 1 ]

# get colors as [(cnt1, (r1, g1, b1)), ...]

cols = image.getcolors (npixels)

  # get [(c1 * r1, c1 * g1, c1 * g2), ...]

sumRGB = [(x [ 0 ] * x [ 1 ] [ 0 ], x [ 0 ] * x [ 1 ] [ 1 ], x [ 0 ] * x [ 1 ] [ 2 ]) for x in cols] 

# calculate (sum (ci * ri) / np, sum (ci * gi) / np, sum (ci * bi) / np)

# zip code gives us [(c1 * r1, c2 * r2, ..), (c1 * g1, c1 * g2, ...) ...]

avg = tuple ([ int ( sum (x) / npixels) for x in zip ( * sumRGB)])

r eturn avg

 

def getAverageRGB (image):

  " ""

For a given PIL image, return the average color value as (r, g, b)

"" "

# get the image as an array

  im = np.array (image)

# get the form

w, h, d = im.shape

# get average

return tuple (np.average (im.reshape (w * h, d), axis = 0 ))

 

def splitImage (image, size):

" ""

Given Image and dims (rows, cols) return m * n image list

"" "

W, H = image.size [ 0 ], image.size [ 1 ]

m, n = size

w, h = int (W / n), int (H / m)

# list of images

imgs = []

# create a list of dimensions

for j in range (m):

for i in range (n) :

# add cropped image

imgs.append (image.crop ((i * w, j * h, (i + 1 ) * w, (j + 1 ) * h)))

return imgs

 

def getImages (imageDir):

"" "

  given the image directory, return a list of images

"" "

  files = os.listdir (imageDir)

images = []

  for file in files:

filePath = os.path.abspath (os.path.join (imageDir, file ) )

try :

# explicit loading, so we don`t face resource overload

fp = open (filePath, "rb" )

im = Image. open (fp)

  images.append (im)

# force loading image data from file

im.load () 

# close the file

fp.close () 

except :

# skip

print ( "Invalid image:% s" % (filePath,))

return images

  

def getImageFilenames (imageDir):

"" "

  given the image directory, return a list of image file names

"" "

  files = os.listdir (imageDir)

filenames = []

  for file in files:

filePath = os.path.abspath (os.path.join (imageDir, file ))

try :

  imgType = imghdr.what (filePath) 

if imgType:

filenames.append ( filePath)

except :

# skip

print ( " Invalid image:% s " % (filePath,))

  return filenames

  

def getBestMatchIndex (input_avg, avgs):

"" "

  index to return the best matching image based on distance to RGB value

"" "

 

# average value of the input image

avg = input_avg

 

# get closest input RGB value based on x / y / z distance

index = 0

  min_index = 0

min_dist = float ( "inf" )

for val in avgs:

dist = ((val [ 0 ] - avg [ 0 ]) * ( val [ 0 ] - avg [ 0 ]) +

(val [ 1 ] - avg [ 1 ]) * (val [ 1 ] - avg [ 1 ]) +

(val [ 2 ] - avg [ 2 ]) * (val [ 2 ] - avg [ 2 ]))

if dist & lt; min_dist:

mi n_dist = dist

  min_index = index

index + = 1

 

return min_index

 

 

def createImageGrid (images, dims):

"" "

Given the image list and grid size (m, n), create

image grid.

  “ »»

m, n = dims

 

# sanitary inspection

  assert m * n = = len (images)

 

# get maximum height and image width

# i.e. without assuming they are all equal

width = max ([img.size [ 0 ] for img in images])

height = max ([img.size [ 1 ] for img in images])

 

# create output image

  grid_img = Image.new ( ` RGB` , (n * width, m * height))

  

# insert images

for index in range ( len (images)):

row = int (index / n)

col = index - n * row

grid_img.paste (images [index], (col * width, row * height))

  

return grid_img

 

 

def createPhotomosaic (target_image, input_images, grid_size,

  reuse_images = True ):

"" "

  Creates a photo mosaic with the specified target and input images.

  "" "

  

  print ( `splitting input image ...` )

# split target image

target_images = splitImage (target_image, grid_size)

  

  print ( ` finding image matches ... ` )

  # for each target image, select one of the inputs

output_images = []

# for user feedback

count = 0

batch_size = int ( len (target_images) / 10 )

 

# compute averages of the input image

avgs = []

for img in input_images:

avgs.append (getAverageRGB (img))

 

for img in target_images:

# target subimage mean

avg = getAverageRGB (img)

# find match index

avgs.append (getAverageRGB (img))

 

for img in target_images:

# target subimage mean

avg = getAverageRGB (img)

# find the index of relevance

avgs.append (getAverageRGB (img))

 

for img in target_images:

# target subimage mean

avg = getAverageRGB (img)

# find the index of relevance