I have a collection of individual images in a folder and want to display them in a custom grid (the size and shape of which will vary but i'll use 4*16 in the code below).
My current code uses matplotlib and numpy but it is very slow (>1min for 64 images) and the resolution of the final image is poor.
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import os
def make_array(folder):
filename_list = [];
im_list = [];
workingdir = os.getcwd();
if folder != "":
workingdir += "/"+folder
for file in os.listdir(workingdir):
if file.endswith(".JPG"):
filename_list.append(file);
filename_list.sort();
os.chdir(workingdir)
for i in range(0,16):
im_list.append(np.asarray(Image.open(filename_list[i]).convert('RGB')));
im_list.append(np.asarray(Image.open(filename_list[i+16]).convert('RGB')));
im_list.append(np.asarray(Image.open(filename_list[i+32]).convert('RGB')));
im_list.append(np.asarray(Image.open(filename_list[i+48]).convert('RGB')));
return np.array(im_list)
def gallery(array, ncols=4):
nindex, height, width, intensity = array.shape
nrows = nindex//ncols
assert nindex == nrows*ncols
# want result.shape = (height*nrows, width*ncols, intensity)
result = (array.reshape(nrows, ncols, height, width, intensity)
.swapaxes(1,2)
.reshape(height*nrows, width*ncols, intensity))
return result
def plot_array(gallery, name):
f = plt.figure()
f.set_size_inches(30, 120)
axes = plt.gca()
plt.xticks([])
plt.yticks([])
plt.imshow(gallery)
plt.show()
f.savefig(name, bbox_inches='tight')
# EDIT TO MATCH THE DESIRED PARAMETERS
#Note: The images will be ploted in the 'writing order' left to right then top to bottom
name = "4_days_per_particle"; #Name of the output file (.png)
folder="Pictures_4days" #Name of folder containing the pictures in the working directory (if not cwd itself)
#Save initial working directory
mainDir = os.getcwd();
#Creates the array of images
array = make_array(folder)
#Reorders the axis to shape the gallery
gal = gallery(array)
#Plots and saves the figure
plot_array(gal, name)
#Cleanup directory
os.chdir(mainDir);
How can I achieve the same result faster and control the output resolution (up to keeping the original resolution of the image files)?
Thank you!
I ended up finding a cleaner way to do this using OpenCV inspired from this gist:
https://gist.github.com/pgorczak/95230f53d3f140e4939c#file-imgmatrix-py
In my experience, this approach is somewhat faster and bypassing matplotlib enables full control of the output resolution.
In addition, cv2.resize() can be used to rescale the image if necessary and the IMWRITE_JPEG_QUALITY argument can be used to set the JPEG export quality as a handle to control filesize.
import itertools
import cv2
import os
import numpy as np
#User defined variables
dirname = "my_directory" #Name of the directory containing the images
name = "my_image_name" + ".jpg" #Name of the exported file
margin = 20 #Margin between pictures in pixels
w = 8 # Width of the matrix (nb of images)
h = 8 # Height of the matrix (nb of images)
n = w*h
filename_list = []
for file in os.listdir(dirname):
if file.endswith(".JPG"):
filename_list.append(file)
filename_list.sort();
print(filename_list)
imgs = [cv2.imread(os.getcwd()+"/"+dirname+"/"+file) for file in filename_list]
#Define the shape of the image to be replicated (all images should have the same shape)
img_h, img_w, img_c = imgs[0].shape
#Define the margins in x and y directions
m_x = margin
m_y = margin
#Size of the full size image
mat_x = img_w * w + m_x * (w - 1)
mat_y = img_h * h + m_y * (h - 1)
#Create a matrix of zeros of the right size and fill with 255 (so margins end up white)
imgmatrix = np.zeros((mat_y, mat_x, img_c),np.uint8)
imgmatrix.fill(255)
#Prepare an iterable with the right dimensions
positions = itertools.product(range(h), range(w))
for (y_i, x_i), img in zip(positions, imgs):
x = x_i * (img_w + m_x)
y = y_i * (img_h + m_y)
imgmatrix[y:y+img_h, x:x+img_w, :] = img
resized = cv2.resize(imgmatrix, (mat_x//3,mat_y//3), interpolation = cv2.INTER_AREA)
compression_params = [cv2.IMWRITE_JPEG_QUALITY, 90]
cv2.imwrite(name, resized, compression_params)
Related
I'm currently trying to create an image preprocessor that adds specific noise for AI training. In this case, I'm trying to add contour lines over the top of my input image that resemble sketch lines.
So far I have been able to get these lines drawn on my image, but they are very sharp and pixelated, which obviously doesn't resemble real artist-drawn lines. I need some way to apply a slight blur to soften these edges, but so far I have not been able to do this.
Here is a visual guide to what I am trying to achieve:
I need to find the contours in image (A) and draw them onto a new layer with alpha channel (B). I then need to blur these lines (C) and paste it back onto the original image (D).
Here is the section I'm having problems with:
# Convert the image to a NumPy array
augmented_image = np.array(augmented_image)
augmented_shape = augmented_image.shape
# Convert image back to color
grey_image = color.rgb2gray(augmented_image)
# Detect the contours of the image using the Canny edge detector
edges = feature.canny(grey_image, sigma=3)
# Create a blank image with dimensions 256 x 256
blank_image = np.zeros((256, 256, 4))
# Create a copy of the image to draw the contours on and convert to 4 layers rgba
alpha = np.ones((augmented_image.shape[0], augmented_image.shape[1], 1), dtype=augmented_image.dtype) * 255
augmented_image = np.concatenate([augmented_image, alpha], axis=2)
# Iterate over the contours
for contour in measure.find_contours(edges, 0.8):
# Set offset
offset = 10
# Select a random point along the contour
point = np.random.randint(0, len(contour))
start_row, start_col = contour[point]
start_row = start_row + offset
start_col = start_col + offset
start_row = np.clip(start_row, 0, augmented_shape[0] - 1)
start_col = np.clip(start_col, 0, augmented_shape[0] - 1)
# Select a random point along the contour that is not the same as the first point
point = np.random.randint(0, len(contour))
while point == start_row:
point = np.random.randint(0, len(contour))
end_row, end_col = contour[point]
end_row = end_row + offset
end_col = end_col + offset
end_row = np.clip(end_row, 0, augmented_shape[0] - 1)
end_col = np.clip(end_col, 0, augmented_shape[0] - 1)
# Draw the line on the image using the draw.line function
rr, cc = draw.line(int(start_row), int(start_col), int(end_row), int(end_col))
blank_image[rr, cc] = 30
# Smooth the contour lines using the gaussian function
blank_image = filters.gaussian(blank_image, sigma=1)
# Make sure image is same data-type
blank_image = blank_image.astype(augmented_image.dtype)
# Create a mask for the contour lines
blank_alpha = blank_image[:, :, 3:]
mask = np.any(blank_alpha > 0, axis=2)
# Apply the smooth image to the masked region of the original image
augmented_image[mask] = blank_image[mask]
# Convert image back to 3 layers rgb
augmented_image = augmented_image[:, :, :3]
I know that the problem lies somewhere in the 'mask' variable definition. Something about it being a boolean type just pastes a line of pure black squares on my image rather than the expected blurred line. No amount of messing with layer order or adding extra layers to copy from has fixed this.
Doing this process without trying to blur the lines works great, minus the fact that it's very pixelated and doesn't fit the style of the training data. Blurring the image without trying to re-combine anything produces an adequate blurred line as well, however the entire image is blurred.
Here's what I can produce without the blurring process, and a rough idea of what I would like the final product to look like (made in Photoshop)
It's only when I try to mask and combine that this becomes a problem. I will post the full code below for anyone to run on their own system:
import random
import numpy as np
import skimage
from skimage.transform import rotate, resize
from skimage import draw, feature, color, measure, filters, util
from skimage.util import random_noise
import PIL
from PIL import Image
import os
import argparse
import cv2
from matplotlib import pyplot as plt
import matplotlib.image as mpimg
from pathlib import Path
import imghdr
# Set parser args
parser = argparse.ArgumentParser()
parser.add_argument("--dirty_dir", help="path to folder containing dirty images")
parser.add_argument("--clean_dir", help="path to folder containing clean images")
parser.add_argument("--dirty_savedir", help="path to dirty output folder")
parser.add_argument("--clean_savedir", help="path to clean output folder")
a = parser.parse_args()
# Set folder paths
dirty_dir = a.dirty_dir + '/'
clean_dir = a.clean_dir + '/'
dirty_savedir = a.dirty_savedir + '/'
clean_savedir = a.clean_savedir + '/'
print(f"Source Folder: {dirty_dir}")
print(f"Source Folder: {clean_dir}")
print(f"Output Folder: {dirty_savedir}")
print(f"Output Folder: {clean_savedir}")
def augment_image(image, filename, clean_dir):
for i in range(8):
# Create list for clean imgs
clean_list = []
# Randomly select a 256x256 region
w, h = image.size[0], image.size[1]
print(w,h)
top = random.randint(0, h - 256)
left = random.randint(0, w - 256)
right = left + 256
bottom = top + 256
dims = [left, top, right, bottom]
print(f'{filename} dimensions:{dims}')
# Add dimensions to clean_list
clean_list.extend(dims)
augmented_image = image.crop((dims))
print(f'{filename} shape: {augmented_image.size}')
# Randomly rotate the image by 90, 180, or 270 degrees
angle = random.choice([0, 90, 180, 270])
augmented_image = augmented_image.rotate(angle)
# Add angle to list
clean_list.append(angle)
# Randomly flip the image horizontally
flip_lr = random.choice([True, False])
if flip_lr == True:
augmented_image = augmented_image.transpose(Image.FLIP_LEFT_RIGHT)
clean_list.append("flip_lr")
else:
clean_list.append("none")
# Randomly flip the image vertically
flip_tb = random.choice([True, False])
if flip_tb == True:
augmented_image = augmented_image.transpose(Image.FLIP_TOP_BOTTOM)
clean_list.append("flip_tb")
else:
clean_list.append("none")
# Convert the image to a NumPy array
augmented_image = np.array(augmented_image)
augmented_shape = augmented_image.shape
# Convert image back to color
grey_image = color.rgb2gray(augmented_image)
# Detect the contours of the image using the Canny edge detector
edges = feature.canny(grey_image, sigma=3)
# Create a blank image with dimensions 256 x 256
blank_image = np.zeros((256, 256, 4))
# Create a copy of the image to draw the contours on and convert to 4 layers rgba
alpha = np.ones((augmented_image.shape[0], augmented_image.shape[1], 1), dtype=augmented_image.dtype) * 255
augmented_image = np.concatenate([augmented_image, alpha], axis=2)
# Iterate over the contours
for contour in measure.find_contours(edges, 0.8):
# Set offset
offset = 10
# Select a random point along the contour
point = np.random.randint(0, len(contour))
start_row, start_col = contour[point]
start_row = start_row + offset
start_col = start_col + offset
start_row = np.clip(start_row, 0, augmented_shape[0] - 1)
start_col = np.clip(start_col, 0, augmented_shape[0] - 1)
# Select a random point along the contour that is not the same as the first point
point = np.random.randint(0, len(contour))
while point == start_row:
point = np.random.randint(0, len(contour))
end_row, end_col = contour[point]
end_row = end_row + offset
end_col = end_col + offset
end_row = np.clip(end_row, 0, augmented_shape[0] - 1)
end_col = np.clip(end_col, 0, augmented_shape[0] - 1)
# Draw the line on the image using the draw.line function
rr, cc = draw.line(int(start_row), int(start_col), int(end_row), int(end_col))
blank_image[rr, cc] = 30
# Smooth the contour lines using the gaussian function
blank_image = filters.gaussian(blank_image, sigma=1)
# Make sure image is same data-type
blank_image = blank_image.astype(augmented_image.dtype)
# Create a mask for the contour lines
blank_alpha = blank_image[:, :, 3:]
mask = np.any(blank_alpha > 0, axis=2)
# Apply the smooth image to the masked region of the original image
augmented_image[mask] = blank_image[mask]
# Convert image back to 3 layers rgb
augmented_image = augmented_image[:, :, :3]
## Add more noise types (lines, wrinkles, color)/make noise random chance to occur ##
# Add random noise to the image
noise = random_noise(augmented_image, mode='pepper', amount=0.011)
# Convert the noisy image back to a PIL image
augmented_image = np.random.random_sample(augmented_image.shape) * 255
augmented_image = np.array(255 * noise, dtype=np.uint8)
augmented_image = Image.fromarray(augmented_image)
# Save file
augmented_image.save(dirty_savedir + '_' + str(i) + '_' + filename)
print(clean_list)
# Function to mirror edits onto clean images
def clean_aug(clean_dir, clean_list):
# Open clean directory
for filename in os.listdir(f"{clean_dir}"):
# Rule out any weird Mac files
if not filename.startswith("._"):
with Image.open(clean_dir + filename) as image:
# Define clean dimensions
clean_dims = clean_list[0:4]
# Crop image
clean_augmented = image.crop((clean_dims))
# Rotate clean image
clean_augmented = clean_augmented.rotate(clean_list[4])
# Flip clean image
if clean_list[5] == 'flip_lr':
clean_augmented = clean_augmented.transpose(Image.FLIP_LEFT_RIGHT)
if clean_list[6] == 'flip_tb':
clean_augmented = clean_augmented.transpose(Image.FLIP_TOP_BOTTOM)
# Save clean images
clean_augmented.save(clean_savedir + '_' + str(i) + '_' + filename)
print("Clean alterations copied successfully")
clean_aug(clean_dir, clean_list)
# Clean up unnecessary files
def file_scrub():
dirty_dir = dirty_savedir
image_extensions = [".png", ".jpg"] # add there all your images file extensions
img_type_accepted_by_tf = ["bmp", "gif", "jpeg", "png"]
for filepath in Path(dirty_dir).rglob("*"):
if filepath.suffix.lower() in image_extensions:
img_type = imghdr.what(filepath)
if img_type is None:
print(f"{filepath} is not an image")
elif img_type not in img_type_accepted_by_tf:
print(f"{filepath} is a {img_type}, not accepted by TensorFlow")
def image_aug(dirty_dir, clean_dir):
for filename in os.listdir(f"{dirty_dir}"):
# Check if the filename starts with "._"
if not filename.startswith("._"):
with Image.open(dirty_dir + filename) as image:
# Open the image
augment_image(image, filename, clean_dir)
image_aug(dirty_dir, clean_dir)
file_scrub()
Apologies for the cumbersome codebase. I just wanted to minimize the risk of discrepancies arising from an attempt at concatenating my script. If there is any clarification I can provide please let me know!
I have a static image that I would like to animate to appear like this (except starting from a black image, not a white image):
(image is from this post: Create animated gif from static image)
Here is the code:
import random
import imageio
import numpy as np
from PIL import Image
img = Image.open('/Users/tom/Desktop/sink.jpeg')
pixels = img.load()
width, height = img.size
img2 = Image.new('RGB', img.size, color='black')
pixels2 = img2.load()
coord = []
for x in range(width):
for y in range(height):
coord.append((x, y))
images = []
while coord:
x, y = random.choice(coord)
pixels2[x, y] = pixels[x, y]
coord.remove((x, y))
if len(coord) % 500 == 0:
images.append(np.array(img2))
imageio.mimsave('/Users/tom/Desktop/sink.gif', images)
When I run the code, the script never stops/outputs anything. Anyone know why?
Your code works, it is just very slow. If you are okay with a transparent background you can do something like this:
import numpy as np
import imageio.v3 as iio
rng = np.random.default_rng()
px_per_iter = 1000
img = iio.imread("imageio:chelsea.png")
n_pixels = img.shape[0] * img.shape[1]
batches = int(np.ceil(n_pixels / px_per_iter)) # number of frames
pixels = rng.permutation(n_pixels) # order in which pixels are revealed
frames = np.zeros((batches + 1, *img.shape[:2], 4), dtype=np.uint8)
for batch_idx in range(batches):
idx_batch = pixels[px_per_iter*batch_idx:px_per_iter*(batch_idx+1)]
y_idx, x_idx = np.unravel_index(idx_batch, img.shape[:2])
frame = frames[batch_idx+1]
frame[y_idx, x_idx, :3] = img[y_idx, x_idx]
frame[y_idx, x_idx, 3] = 255 # make added pixels non-transparent
iio.imwrite("fancy.gif", frames, loop=True)
(500kb GIF)
If you need the black background, you can use something like this; however, be aware that it will produce larger files:
import numpy as np
import imageio.v3 as iio
rng = np.random.default_rng()
px_per_iter = 1000
img = iio.imread("imageio:chelsea.png")
n_pixels = img.shape[0] * img.shape[1]
batches = int(np.ceil(n_pixels / px_per_iter)) # number of frames
pixels = rng.permutation(n_pixels) # order in which pixels are revealed
frames = np.zeros((batches + 1, *img.shape), dtype=np.uint8)
for batch_idx in range(batches):
idx_batch = pixels[px_per_iter*batch_idx:px_per_iter*(batch_idx+1)]
y_idx, x_idx = np.unravel_index(idx_batch, img.shape[:2])
frame = frames[batch_idx+1]
frame[:] = frames[batch_idx]
frame[y_idx, x_idx] = img[y_idx, x_idx]
iio.imwrite("fancy.gif", frames)
(result exceeds 2MB, which is SO's limit)
I have 15 tiles or tiff files a folder and I would like combine it as a single file with all the images as one tiff image. All the tiles should be stitched as a single tiff image. How do I do that?
What I tried so far?
import imageio
import os
path = "path/to/dir"
image_path_list = os.listdir(path)
with imageio.get_writer("new_image.tif") as new_image:
for image_path in image_path_list:
image = imageio.imread(path+image_path)
new_image.append_data(image)
This saves as a separate image in a tiff file. I would like to stitch all the images together and save it like the following:
1,2,3...,15 represent the tiles. Needs to be stitched as a single image.
It seems from your comments that you are prepared to consider a non-Python solution, so I used ImageMagick in the Terminal to montage 15 images as follows:
magick montage -tile 3x -geometry +0+0 09*tif result.tif
To demonstrate how you can lay out 5 images across instead of 3, add a different background and affect the horizontal and vertical spacing differently, here is a variation:
magick montage -background magenta -tile 5x -geometry +5+15 09*tif result.tif
Just FYI, I made the 15 randomly coloured blocks like this:
for x in {a..o} ; do magick xc: +noise random -scale 80x50\! 09$x.tif ; done
given one directory with 15 images of same size
using PIL (pillow), I ended up with:
from PIL import Image
import os
path_to_file ='tiff-files'
images = []
for i in os.listdir(path_to_file):
with Image.open(path_to_file+'/'+i) as im:
images.append(im.copy())
new_image = Image.new(images[0].mode, (images[0].size[0]*3,images[0].size[1]*5))
new_image.paste(images[0])
new_image.paste(images[1],(images[0].size[0]*1,0))
new_image.paste(images[2],(images[0].size[0]*2,0))
new_image.paste(images[3],(0,images[0].size[1]*1))
new_image.paste(images[4],(images[0].size[0]*1,images[0].size[1]*1))
new_image.paste(images[5],(images[0].size[0]*2,images[0].size[1]*1))
new_image.paste(images[6],(0,images[0].size[1]*2))
new_image.paste(images[7],(images[0].size[0]*1,images[0].size[1]*2))
new_image.paste(images[8],(images[0].size[0]*2,images[0].size[1]*2))
new_image.paste(images[9],(0,images[0].size[1]*3))
new_image.paste(images[10],(images[0].size[0]*1,images[0].size[1]*3))
new_image.paste(images[11],(images[0].size[0]*2,images[0].size[1]*3))
new_image.paste(images[12],(0,images[0].size[1]*4))
new_image.paste(images[13],(images[0].size[0]*1,images[0].size[1]*4))
new_image.paste(images[14],(images[0].size[0]*2,images[0].size[1]*4))
new_image.show()
let me know if it works.....
After Mark Setchell suggestion here a new version, hope it is better
from PIL import Image
import os
path_to_file ='tiff-files'
def stich_tile(path_to_file, xx , yy):
images = []
for i in os.listdir(path_to_file):
images.append(i)
if len(images) >= xx*yy:
pass
else:
raise ValueError('not enough images in path_to_file !!!!!!!!!!!')
sq_x = xx
sq_y = yy
img_x = (Image.open(path_to_file+'/'+images[0]).size[0])
img_y = (Image.open(path_to_file+'/'+images[0]).size[1])
img_mode = (Image.open(path_to_file+'/'+images[0]).mode)
new_image = Image.new(img_mode, (img_x*sq_x, img_y*sq_y))
x = 0
y = 0
cnt = 0
for i in images:
with Image.open(path_to_file+'/'+i) as img:
new_image.paste(img, (x,y))
cnt += 1
x += img_x
if cnt == sq_x:
x = 0
y += img_y
cnt = 0
else:
pass
return new_image
stich_tile(path_to_file, 3, 5).show()
And thinking more along the lines of https://stackoverflow.com/a/68468658/2836621
import numpy as np
from PIL import Image
import os
# path_to_file ='tiff-files'
path_to_file ='tiff-files2'
# path_to_file ='tiff-files3'
image = []
for i in os.listdir(path_to_file):
with Image.open(path_to_file+'/'+i) as im:
image.append(im.copy())
w, h = image[0].size
new_image = np.zeros((4 * h, 3 * w)).astype('uint8')
col = 0
row = -1
for i, img in enumerate(image):
if not i % 3 :
row += 1
col = 0
img = np.array(img)
new_image[row * h: (row + 1) * h, col * w: (col + 1) * w] = img
col += 1
image_pillow = Image.fromarray(new_image, mode = 'L')
image_pillow.save('prova.tif', mode = 'L')
image_pillow.show()
tested with .tif images grayscale 8-bit
modify adding 3 channel for RGB et similia:
new_image = np.zeros((3 * h, 3 * w,3)).astype('uint8')
new_image[row * h: (row + 1) * h,col * w: (col + 1) * w,:] = img
once more the last example as function for 8 bit grayscale images:
import numpy as np
from PIL import Image
import os
path_to_file ='tiff-files'
# path_to_file ='tiff-files2'
# path_to_file ='tiff-files3'
# path_to_file ='tiff-files5'
def stich_img(path_to_file, x , y):
image = []
for i in os.listdir(path_to_file):
image.append(path_to_file+'/'+i)
print(image)
if len(image) >= x*y:
pass
else:
# raise ValueError('not enough images in path_to_file !!!!!!!!!!!')
raise ValueError('EXCEPTION not enough images in path_to_file !!!!!!!!!!!', x*y ,'images needed : ', len(image),'images present !!!')
image = image[:x*y] #-----> riduce lista immagini al numero richiesto
with Image.open(image[0]) as img0:
w, h = img0.size
# new_image = np.zeros((4 * h, 3 * w)).astype('uint8')
new_image = np.zeros((y * h, x * w)).astype('uint8')
col = 0
row = -1
for i, imgs in enumerate(image):
with Image.open(imgs) as img:
if not i % x :
row += 1
col = 0
img = np.array(img)
new_image[row * h: (row + 1) * h, col * w: (col + 1) * w] = img
col += 1
image_pillow = Image.fromarray(new_image, mode = 'L')
return image_pillow
img_stiched = stich_img(path_to_file, 3,5)
# img_stiched.save('prova.tif', mode = 'L')
img_stiched.show()
Read all images in a list. Iterate over this list using two nested for loops. One in range of 3 and one in range of 5. Use numpy.hstack() and numpy.vstack() to make a final 3x5 image assuming that the size of each tile image is same.
Using numpy:
This script accepts generator of images (to work faster with large images). It does not check their size in advance. If image height does not fit row height or if rows have not the same width, it will fail.
#!/usr/bin/env python3
import numpy as np
from imageio import imread, imwrite
from pathlib import Path
def tile_images(images, cols):
"""Tile images of same size to grid with given number of columns.
Args:
images (collection of ndarrays)
cols (int): number of colums
Returns:
ndarray: stitched image
"""
images = iter(images)
first = True
rows = []
i = 0
while True:
try:
im = next(images)
print(f"add image, shape: {im.shape}, type: {im.dtype}")
except StopIteration:
if first:
break
else:
im = np.zeros_like(im) # black background
if first:
row = im # start next row
first = False
else:
row = np.concatenate((row, im), axis=1) # append to row
i += 1
if not i % cols:
print(f"row done, shape: {row.shape}")
rows.append(row) # finished row
first = True
tiled = np.concatenate(rows) # stitch rows
return tiled
def main():
images = (imread(f) for f in Path().glob("*.*") if f.suffix in (".jpg", ".png") if f.name != "new.png")
new = tile_images(images, cols=3)
imwrite("new.png", new)
def test():
im1 = np.arange(65536).reshape(256,256)
im2 = np.arange(65536/2).reshape(128,256)
images = [im1,im1,im1,im2,im2,im2]
# works
new = tile_images(images, 3)
imwrite("new.png", new)
# failes
new = tile_images(images, 2)
imwrite("new2.png", new)
if __name__ == "__main__":
main()
# test()
The following elaborates on #saad_saeed answer.
Note, the following will break:
if your list_of_images doesn't have enough images to build the num_mosaic_rows x num_mosaic_cols mosaic. I've left it to the user to add the handling of this (e.g. adding an if/else).
if each img in your list_of_images doesn't have the same shape
def build_mosaic(list_of_images, num_mosaic_rows, num_mosaic_cols):
list_of_mosaic_rows = []
for row_number in range(num_mosaic_rows):
list_of_mosaic_rows = list_of_images[row_number*num_mosaic_cols,(row_number+1)*num_mosaic_cols]
mosaic = np.vstack(list_of_mosaic_rows)
return mosaic
In below example there are three images on a white background in order. How to achieve this in python using CV2 or PIL or any working code.
Thank you.
Image must be aligned according to aspect ratio.
Input = 3 images with BG,
Output = single image as shown in above picture
UPDATE !!!!
Each and every loop only one image gets pasted on BG.
from PIL import Image
import cv2
import numpy as np
d=0
folder = 'save'
image_paths = []
for path, subdirs, files in os.walk(folder):
for filename in files:
f = os.path.join(path, filename)
if f.endswith(".jpg"):
image_paths.append(f)
if f.endswith(".png"):
image_paths.append(f)
if f.endswith(".JPG"):
image_paths.append(f)
if f.endswith(".PNG"):
image_paths.append(f)
if f.endswith(".jpeg"):
image_paths.append(f)
if f.endswith(".JPEG"):
image_paths.append(f)
for image in image_paths:
image = cv2.imread(image)
r = 720.0 / image.shape[1]
dim = (720, int(image.shape[0] * r))
resized = cv2.resize(image, dim)
#resized = resized[:,:,0]
h, w, z = resized.shape
back = cv2.imread('template.jpg')
yoff = round((1080-h)/4)
xoff = round((1920-w)/6)
d+=1
result = back.copy()
result[yoff:yoff+h, xoff:xoff+w] = resized
#result = np.stack((result)*3)
cv2.imwrite('saves/resized_centered_%d.jpg'%d, result)
So multiple images in input gets pasted in a background but the thing is, i want three images to paste in the background instead of one image in order.
NOTE: THE IMAGE ON TOP IS JUST TO REPRESENT MY HELP !!! YOU CAN TELL ME WHATEVER POSSIBLE APART FROM THAT !!!
This line of code moves the image towards top-left and seated properly but likewise i need two more image to be seated on top-right and as well as bottom.
yoff = round((1080-h)/4)
xoff = round((1920-w)/6)
I assume some template like this:
The "final image" has dimensions (1920, 1080) (cf. your calculations on xoff and yoff). Since you wrote, you want to keep the aspect ratio for each "single image", you'd need to check both cases: Resize w.r.t. to the single image's width, and if the resulting height is too large, re-resize w.r.t. to the single image's height.
What's left is to track the number of single images per final image inside the loop, and set up proper xoff and yoff values for each of the three cases. Maybe, looking at the code here helps more than long explanations:
import cv2
import numpy as np
import os
folder = 'path/to/your/images'
image_paths = []
for path, subdirs, files in os.walk(folder):
for filename in files:
f = os.path.join(path, filename)
if f.endswith((".jpg", ".png", ".JPG", ".PNG", ".jpeg", ".JPEG")):
image_paths.append(f)
d = 0 # Final image counter
e = 0 # Single image counter
back = np.ones((1080, 1920, 3), np.uint8) * 255 # Background
result = back.copy() # Final image
for i, image in enumerate(image_paths):
# Read image
image = cv2.imread(image)
h, w = image.shape[:2]
# First two single images: Enforce subimage with h_max = 480 and w_max = 900
if e <= 1:
r = 900.0 / w
dim = (900, int(h * r))
if dim[1] > 480:
r = 480.0 / h
dim = (int(w * r), 480)
resized = cv2.resize(image, dim)
hr, wr = resized.shape[:2]
x_off = 40
if e == 0:
y_off = 40
else:
y_off = 560
# Third single image: Enforce subimage with h_max = 1000 and w_max = 900
else:
r = 900.0 / w
dim = (900, int(h * r))
if dim[1] > 1000:
r = 1000.0 / h
dim = (int(w * r), 1000)
resized = cv2.resize(image, dim)
hr, wr = resized.shape[:2]
x_off, y_off = 980, 40
# Add single image to final image
result[y_off:y_off + hr, x_off:x_off + wr] = resized
# Increment single image counter
e += 1
# After three single images: Write final image; start new final image
if (e == 3) or (i == (len(image_paths) - 1)):
cv2.imwrite('resized_centered_%d.jpg' % d, result)
result = back.copy()
d += 1
e = 0
For some random images from my StackOverflow archive, I get the following outputs:
If you want to have different sized boxes or margins around or between the single images, just adapt the corresponding values in the code.
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.9.1
PyCharm: 2021.1.1
NumPy: 1.20.2
OpenCV: 4.5.1
----------------------------------------
I have read in some images with the code below. These images are of different sizes. In order to get them to equal sizes, I would like to add a black frame around the images. I found some code to do this for a single image but not for a list as in my case.
import cv2
import numpy
import glob
import matplotlib.pyplot as plt
from PIL import Image, ImageOps
folders = glob.glob(r'path\to\images\*')
imagenames_list = []
for folder in folders:
for f in glob.glob(folder+'/*.png'):
imagenames_list.append(f)
read_images = []
for image in imagenames_list:
read_images.append(cv2.imread(image, cv2.IMREAD_GRAYSCALE))
To add a black frame for a single picture I used this code:
from PIL import Image
import numpy as np
old_im = Image.open('path/to/single/picture/*.png')
old_size = old_im.size
print(old_size)
new_size = (500, 500)
print(new_size)
new_im = Image.new("RGB", new_size)
x = int((new_size[0]-old_size[0])/2)
y = int((new_size[1]-old_size[1])/2)
new_im.paste(old_im, (x,y))
Image read by OpenCV are just numpy arrays. You can just use numpy slicing to copy:
def makeborder(cv2img, new_width, new_height):
'''
cv2img: an image returned by cv2.imread()
'''
# gray scale or BGR/BGRA
if len(cv2img.shape) == 2:
new_shape = (new_height, new_width)
else:
new_shape = (new_height, new_width, cv2img.shape[-1])
new_img = np.zeros(new_shape, dtype=cv2img.dtype)
# compute the offsets, similar to your x & y
offset_height = (new_height - cv2img.shape[0])//2
offset_weight = (new_width - cv2img.shape[1])//2
# should check offset_height >= 0 and offset_weight >= 0
# but we skip here
# ...
# now we just use numpy slicing to copy
new_img[offset_height:offset_height + cv2img.shape[0],
offset_width: offset_width + cv2img.shape[1]] \
= cv2img
return new_img