How To Concatenate 5 Images Out Of Multiple Images In A Folder
Solution 1:
Your question has lots of moving parts, so I will attempt to address all of them. Hopefully the code helps with your use case.
The code below does this:
Determine what images exist in directory X
Group images based on file name
Create a temporary directory to store images based on group. This directory will be removed automatically.
Copy local files to temporary directory
Chunk the files into blocks (5 files max)
Merge these chucks into a new image with a border separator
Special Note
During testing I noted that any list that did not contained the required 5 items would not format correctly. To fix this problem I created a blank_check.jpg, which allowed me to format the new image correctly.
import os
import shutil
import tempfile
import itertools
from PIL import Image
from PIL import ImageOps
defcreate_temp_directory(name):
"""
This function create a temporary directory, which will
be used to store files based on specific parts of a filename.
:param name:
:return:
"""
temp_dir = tempfile.TemporaryDirectory(suffix=None, prefix=f'temp_directory_{name}', dir=None)
return temp_dir
defdivide_list_into_chunks(input_list, len_of_chunk):
"""
This function will divide a list into chunks.
:param input_list:
:param len_of_chunk:
:return:
"""for i inrange(0, len(input_list), len_of_chunk):
yield input_list[i:i + len_of_chunk]
defadd_blank_checks(input_list, temp_dir):
"""
:param input_list:
:param temp_dir:
:return:
"""
number_of_images = (5 - len(input_list))
for _ inrange(number_of_images):
shutil.copy(f'blank_check.jpg', f'{temp_dir.name}/blank_check.jpg')
input_list.append(f'{temp_dir.name}/blank_check.jpg')
return input_list
defmerge_images_vertically(list_of_images, file_name):
"""
This function is designed to merge images vertically
:param list_of_images: current list of images to process
:param file_name: file name for the new image
:return:
"""# open images using Pillow
images = [Image.open(im) for im in list_of_images]
# Create separate lists to store the heights and widths# of the images
widths, heights = zip(*(i.size for i in images))
width_of_new_image = min(widths)
height_of_new_image = sum(heights)
# create a new output image
new_im = Image.new('RGB', (width_of_new_image, height_of_new_image))
new_pos = 0
counter = 0for im in images:
if counter == 0:
new_im.paste(im, (0, new_pos))
new_pos += im.size[1]
counter += 1else:
color = "black"
border = (0, 10, 0, 0)
img_with_border = ImageOps.expand(im, border=border, fill=color)
new_im.paste(img_with_border, (0, new_pos))
new_pos += im.size[1]
new_im.save(f'{file_name}', "JPEG", quality=75, optimize=True, progressive=True)
return
image_directory = "sample_images"
image_directory_abspath = os.path.abspath(image_directory)
images = os.listdir(image_directory_abspath)
accepted_extensions = ('.bmp', '.gif', '.jpg', '.jpeg', '.png', '.svg', '.tiff')
valid_image_extensions = [im for im in images if im.endswith(accepted_extensions)]
image_groups = [list(g) for _, g in itertools.groupby(sorted(valid_image_extensions), lambda x: x[0:2])]
for image_group in image_groups:
count = 0
name_group = image_group[0][:2]
temp_directory = create_temp_directory(name_group)
image_list = [shutil.copy(f'{image_directory_abspath}/{item}', f'{temp_directory.name}/{item}') for item in image_group]
max_number_of_items = 5
chunks = list(divide_list_into_chunks(image_list, max_number_of_items))
for chunk in chunks:
count += 1iflen(chunk) == 5:
merge_images_vertically(chunk, f'{name_group}_merged_{count}.jpeg')
else:
new_checks = add_blank_checks(chunk, temp_directory)
merge_images_vertically(new_checks, f'{name_group}_merged_{count}.jpeg')
Sample of merged image files
----------------------------------------
System information
----------------------------------------
Platform: macOS Catalina
Python: 3.8.0
Pillow: 8.0.1
----------------------------------------
Solution 2:
For iterating all files in the folder, use Python's os.walk
method. For every file, use Python's str.split
to get the prefix and the number of the file. Do some checking regarding prefix changes and numbers above 5
, and just concatenate the images inside the loop. Since you concatenate only five images, doing that using vectorized approaches (e.g. via NumPy) will just give more unreadable code for a minor performance win.
Here's my full code:
import cv2
import numpy as np
import os
# Iterate all files in folderfor root, dirs, files in os.walk("your/folder/somewhere/"):
current_prefix = ''
output = Nonefor file in files:
# Split filename
splits = file.split('_')
# Get prefix and number of filename
prefix = splits[0]
number = int(splits[1].split('.')[0])
# If there's a prefix change...if prefix != current_prefix:
# ... save last output image...if current_prefix != ''and output isnotNone:
cv2.imwrite(root + current_prefix + '_Concat.jpg', output)
# ... and set new prefix and initialize new output image
current_prefix = prefix
output = cv2.imread(root + file)
# As long the current number is less or equal 5, concatenateelif number <= 5:
output = np.concatenate([output, cv2.imread(root + file)], axis=0)
# Save last output image separately
cv2.imwrite(root + current_prefix + '_Concat.jpg', output)
I tested that code with a folder containing files AB_1.jpg
... AB_8.jpg
and BC_1.jpg
... BC_3.jpg
. As results I get AB_Concat.jpg
with the first five images concatenated vertically, and BC_Concat.jpg
with the existing three images.
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.8.5
NumPy: 1.19.4
OpenCV: 4.4.0
----------------------------------------
Solution 3:
I had 10 minutes to write some code - explanation afterwards:
#!/usr/bin/env python3import glob, re
import numpy as np
import cv2
# Get list of filename prefixes by looking for "XX_1.jpg" images
prefixes = [ re.split('_', f)[0] for f in glob.glob('[A-Z][A-Z]_1.jpg') ]
# Iterate over prefixes forming sequentially numbered filenamesfor prefix in prefixes:
stack = cv2.imread(f'{prefix}_1.jpg', cv2.IMREAD_COLOR)
for N inrange(2,6):
# Load image and concatenate onto stack
im = cv2.imread(f'{prefix}_{N}.jpg', cv2.IMREAD_COLOR)
stack = np.concatenate((stack, im), axis=0)
# Save concatenated stack
cv2.imwrite(f'{prefix}_concat.jpg', stack)
The simplest way is to look for all the XX_1.jpg
files to get the stem (i.e. prefix) of your filenames. Then construct the 5 numbered versions and make a list and then use Numpy np.concat()
to stack them atop each other.
Without writing all the code, something like this:
import glob, re
import numpy as np
# iterate over all "XX_1.jpg" filesfor f in glob.glob('[A-Z][A-Z]_1.jpg'):
print(f)
stem = re.split('_', f)[0]
print(stem)
That will produce this list of all your prefixes:
DE_1.jpg
DE
BC_1.jpg
BC
AB_1.jpg
AB
Solution 4:
nums = [1, 2, 3, 4, 5]
forimgin img_dir:
foriin nums:
ifstr(i) in img:
img = img.replace('i', 'Concat')
Solution 5:
Either it does, or it doesn't. Tired, gonna shower.
#! /usr/bin/env python3import os
import cv2
import numpy as np
from PIL import Image
path = r'D:\split'
out = r'D:\concat_test'for root, _, files in os.walk( path ):
heads = {}
for file in files:
head, tail = file.split( '_', 1 )
version, extension = tail .split( '.', 1 )
if head notin heads or version > heads [head] ['vers']:
heads [head] = { 'vers':version, 'ext':extension }
for h in heads: ## AB, BC, CA
head = heads [h] ## local reference
images = []
for i inrange( head ['vers'] )
imagename = h +i +head ['ext']
img = cv2.imread( os.path.join( root, imagename ), 0 ) ## color, grey, alpha
images .append( img )
concat = cv2.vconcat( images )
concat_name = os.path.join( out, h +'concat' +head ['ext'] )
cv2.imwrite( concat_name, concat )
Post a Comment for "How To Concatenate 5 Images Out Of Multiple Images In A Folder"