Reputation: 157
I have a folder which has multiple images lets say images names are----
AB_1.jpg, AB_2.jpg, AB_3.jpg, AB_4.jpg, AB_5.jpg, AB_6.jpg, AB_7.jpg, AB_8.jpg, BC_1.jpg, BC_2.jpg, BC_3.jpg, CA_1.jpg, CA_2.jpg
Here my objective is to concatenate all the images till _5 or less than _5 as one image....
So basically what I am saying is that--- AB_1.jpg, AB_2.jpg, AB_3.jpg, AB_4.jpg, AB_5.jpg
will get concatenated vertically and file name lets say will be AB_Concat.jpg
_6,_7,_8 are skipped because we want to concatenate only images till _5 as one....
Similarly,, BC_1.jpg, BC_2.jpg, BC_3.jpg
will get concatenated vertically and file name lets say will be BC_Concat.jpg as you can see here BC_3 is last one which is less than _5 so all will be concatenated vertically.....
So if anyone have any idea on how to achieve the same please help....
This question is not similar to the once where two images are concatenated since here we are concatenating images on the basis of value after underscore in a image....
This is something which I tried---
import cv2
import shutil
import numpy as np
import os
from PIL import Image
path = r"D:\split"
out = r"D:\concat_test"
os.makedirs(out,exist_ok=True)
imag_name =[]
imag_name1 = []
for img in os.listdir(path):
folder = img.rsplit("_",1)[0]
imag_name1.append((folder,img))
images = cv2.imread(os.path.join(path,img),0)
imag_name.append((folder,images))
# to create a folder for multiple images contain same name
for image,name in zip(imag_name,imag_name1):
if image[0] == name[0]:
folder_n = os.path.join(out,name[0])
final_fldr = os.path.join(folder_n,name[1])
os.makedirs(folder_n,exist_ok=True)
cv2.imwrite(final_fldr,image[1])
# to concatinate the images
for root,dirs,files in os.walk(out):
np_images = [cv2.imread(os.path.join(root,file),0) for file in files]
np_name = list(set([file.rsplit("_",1)[0] for file in files]))
if len(np_images) != 0:
print(np_name)
concat = cv2.vconcat(np_images)
concatinated_images = os.path.join(out,"concatinated_images")
os.makedirs(concatinated_images,exist_ok=True)
cv2.imwrite(os.path.join(concatinated_images,*np_name)+".tif",concat)
#to remove the unwanted folder
for remove_folder in os.listdir(out):
if remove_folder != "concatinated_images":
folders_to_remove = os.path.join(out,remove_folder)
shutil.rmtree(folders_to_remove)
'''
Upvotes: 1
Views: 2954
Reputation: 2147
Either it does, or it doesn't. Tired, gonna shower.
#! /usr/bin/env python3
import 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 not in 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 in range( 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 )
Upvotes: 0
Reputation: 15619
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
def create_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
def divide_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 in range(0, len(input_list), len_of_chunk):
yield input_list[i:i + len_of_chunk]
def add_blank_checks(input_list, temp_dir):
"""
:param input_list:
:param temp_dir:
:return:
"""
number_of_images = (5 - len(input_list))
for _ in range(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
def merge_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 = 0
for im in images:
if counter == 0:
new_im.paste(im, (0, new_pos))
new_pos += im.size[1]
counter += 1
else:
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 += 1
if len(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')
----------------------------------------
System information
----------------------------------------
Platform: macOS Catalina
Python: 3.8.0
Pillow: 8.0.1
----------------------------------------
Upvotes: 2
Reputation: 207465
I had 10 minutes to write some code - explanation afterwards:
#!/usr/bin/env python3
import 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 filenames
for prefix in prefixes:
stack = cv2.imread(f'{prefix}_1.jpg', cv2.IMREAD_COLOR)
for N in range(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" files
for 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
Upvotes: 1
Reputation: 18905
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 folder
for root, dirs, files in os.walk("your/folder/somewhere/"):
current_prefix = ''
output = None
for 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 is not None:
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, concatenate
elif 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
----------------------------------------
Upvotes: 1
Reputation: 3
nums = [1, 2, 3, 4, 5]
for img in img_dir:
for i in nums:
if str(i) in img:
img = img.replace('i', 'Concat')
Upvotes: 0