Ani
Ani

Reputation: 157

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

Answers (5)

Doyousketch2
Doyousketch2

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

Life is complex
Life is complex

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:

  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

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')

Sample of merged image files

enter image description here

----------------------------------------
System information
----------------------------------------
Platform:    macOS Catalina
Python:      3.8.0
Pillow:      8.0.1
----------------------------------------

Upvotes: 2

Mark Setchell
Mark Setchell

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

HansHirse
HansHirse

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

Maksudur Rahman
Maksudur Rahman

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

Related Questions