Skip to content Skip to sidebar Skip to footer

How To Concatenate 5 Images Out Of Multiple Images In A Folder

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

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:

  1. Determine what images exist in directory X

  2. Group images based on file name

  3. Create a temporary directory to store images based on group. This directory will be removed automatically.

  4. Copy local files to temporary directory

  5. Chunk the files into blocks (5 files max)

  6. 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

enter image description here

----------------------------------------
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"