Combina diverse immagini orizzontalmente con Python


121

Sto cercando di combinare orizzontalmente alcune immagini JPEG in Python.

Problema

Ho 3 immagini - ciascuna è 148 x 95 - vedi allegato. Ho appena fatto 3 copie della stessa immagine, ecco perché sono uguali.

inserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagineinserisci qui la descrizione dell'immagine

Il mio tentativo

Sto cercando di unirli orizzontalmente usando il seguente codice:

import sys
from PIL import Image

list_im = ['Test1.jpg','Test2.jpg','Test3.jpg']
new_im = Image.new('RGB', (444,95)) #creates a new empty image, RGB mode, and size 444 by 95

for elem in list_im:
    for i in xrange(0,444,95):
        im=Image.open(elem)
        new_im.paste(im, (i,0))
new_im.save('test.jpg')

Tuttavia, questo produce l'output allegato come test.jpg.

inserisci qui la descrizione dell'immagine

Domanda

C'è un modo per concatenare orizzontalmente queste immagini in modo tale che le sotto-immagini in test.jpg non abbiano un'immagine parziale extra che mostra?

Informazioni aggiuntive

Sto cercando un modo per concatenare orizzontalmente n immagini. Vorrei utilizzare questo codice in generale, quindi preferirei:

  • non codificare le dimensioni dell'immagine, se possibile
  • specificare le dimensioni in una riga in modo che possano essere facilmente modificate

2
Perché c'è un for i in xrange(...)nel tuo codice? Non dovresti pasteoccuparti dei tre file di immagine che specifichi?
msw

domanda, le tue immagini avranno sempre le stesse dimensioni?
dermen


dermen: sì, le immagini avranno sempre le stesse dimensioni. msw: Non ero sicuro di come scorrere le immagini, senza lasciare uno spazio vuoto in mezzo - il mio approccio probabilmente non è il migliore da usare.
edesz

Risposte:


172

Puoi fare qualcosa del genere:

import sys
from PIL import Image

images = [Image.open(x) for x in ['Test1.jpg', 'Test2.jpg', 'Test3.jpg']]
widths, heights = zip(*(i.size for i in images))

total_width = sum(widths)
max_height = max(heights)

new_im = Image.new('RGB', (total_width, max_height))

x_offset = 0
for im in images:
  new_im.paste(im, (x_offset,0))
  x_offset += im.size[0]

new_im.save('test.jpg')

Test1.jpg

Test1.jpg

Test2.jpg

Test2.jpg

Test3.jpg

Test3.jpg

test.jpg

inserisci qui la descrizione dell'immagine


L'annidato per for i in xrange(0,444,95):incolla ciascuna immagine 5 volte, sfalsata di 95 pixel l'una dall'altra. Ogni iterazione del ciclo esterno viene incollata sulla precedente.

for elem in list_im:
  for i in xrange(0,444,95):
    im=Image.open(elem)
    new_im.paste(im, (i,0))
  new_im.save('new_' + elem + '.jpg')

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine


Due domande: 1. x_offset = 0- è questo lo sfalsamento tra i centri dell'immagine? 2. Per una concatenazione verticale, come cambia il tuo approccio?
edesz

2
Il secondo argomento della pasta è una scatola. "L'argomento box è una tupla di 2 che fornisce l'angolo in alto a sinistra, una tupla di 4 che definisce le coordinate dei pixel sinistra, superiore, destra e inferiore o Nessuno (uguale a (0, 0))." Quindi nella 2-tupla stiamo usando x_offsetcome left. Per la concatenazione verticale, tieni traccia di y-offset, o top. Invece di sum(widths)e max(height), fai sum(heights)e max(widths)e usa il secondo argomento della casella a 2 tuple. incremento y_offsetdi im.size[1].
DTing

21
Bella soluzione. Nota in python3 che le mappe possono essere ripetute solo una volta, quindi dovresti fare di nuovo images = map (Image.open, image_files) prima di iterare le immagini la seconda volta.
Naijaba

1
Jaijaba Mi sono anche imbattuto nel problema che descrivi, quindi ho modificato la soluzione di DTing per utilizzare una comprensione dell'elenco invece di una mappa.
Ben Quigley

1
Ho dovuto usare la comprensione delle liste invece che mapin python3.6
ClementWalter

89

Proverei questo:

import numpy as np
import PIL
from PIL import Image

list_im = ['Test1.jpg', 'Test2.jpg', 'Test3.jpg']
imgs    = [ PIL.Image.open(i) for i in list_im ]
# pick the image which is the smallest, and resize the others to match it (can be arbitrary image shape here)
min_shape = sorted( [(np.sum(i.size), i.size ) for i in imgs])[0][1]
imgs_comb = np.hstack( (np.asarray( i.resize(min_shape) ) for i in imgs ) )

# save that beautiful picture
imgs_comb = PIL.Image.fromarray( imgs_comb)
imgs_comb.save( 'Trifecta.jpg' )    

# for a vertical stacking it is simple: use vstack
imgs_comb = np.vstack( (np.asarray( i.resize(min_shape) ) for i in imgs ) )
imgs_comb = PIL.Image.fromarray( imgs_comb)
imgs_comb.save( 'Trifecta_vertical.jpg' )

Dovrebbe funzionare fintanto che tutte le immagini sono della stessa varietà (tutte RGB, tutte RGBA o tutte in scala di grigi). Non dovrebbe essere difficile garantire che questo sia il caso con poche righe di codice in più. Ecco le mie immagini di esempio e il risultato:

Test1.jpg

Test1.jpg

Test2.jpg

Test2.jpg

Test3.jpg

Test3.jpg

Trifecta.jpg:

immagini combinate

Trifecta_vertical.jpg

inserisci qui la descrizione dell'immagine


Molte grazie. Un'altra buona risposta. Come cambierebbe min_shape =....e imgs_comb....per una concatenazione verticale? Potresti postarlo qui come commento o nella tua risposta?
edesz

3
Per il verticale, passare hstacka vstack.
dermen

Un'altra domanda: la tua prima immagine ( Test1.jpg ) è più grande delle altre immagini. Nell'immagine concatenata finale (orizzontale o verticale), tutte le immagini hanno le stesse dimensioni. Puoi spiegare come sei riuscito a rimpicciolire la prima immagine prima di concatenarla?
edesz

Ho usato Image.resizeda PIL. min_shapeè una tupla di (min_width, min_height) e quindi (np.asarray( i.resize(min_shape) ) for i in imgs )ridurrà tutte le immagini a quella dimensione. In effetti, min_shapepuò essere qualsiasi (width,height)cosa desideri, tieni presente che ingrandire le immagini a bassa risoluzione le renderà sfocate!
dermen

3
Se stai cercando di combinare le immagini insieme senza alcuna specifica, questa è probabilmente la risposta più semplice e flessibile qui. Tiene conto delle diverse dimensioni delle immagini, di qualsiasi numero di immagini e di diversi formati di immagine. Questa è stata una risposta molto ben congegnata ed ESTREMAMENTE utile. Non avrei mai pensato di usare numpy. Grazie.
Noctsol

26

Modifica: la risposta di DTing è più applicabile alla tua domanda poiché utilizza PIL, ma lo lascerò nel caso in cui desideri sapere come farlo in numpy.

Ecco una soluzione numpy / matplotlib che dovrebbe funzionare per N immagini (solo immagini a colori) di qualsiasi dimensione / forma.

import numpy as np
import matplotlib.pyplot as plt

def concat_images(imga, imgb):
    """
    Combines two color image ndarrays side-by-side.
    """
    ha,wa = imga.shape[:2]
    hb,wb = imgb.shape[:2]
    max_height = np.max([ha, hb])
    total_width = wa+wb
    new_img = np.zeros(shape=(max_height, total_width, 3))
    new_img[:ha,:wa]=imga
    new_img[:hb,wa:wa+wb]=imgb
    return new_img

def concat_n_images(image_path_list):
    """
    Combines N color images from a list of image paths.
    """
    output = None
    for i, img_path in enumerate(image_path_list):
        img = plt.imread(img_path)[:,:,:3]
        if i==0:
            output = img
        else:
            output = concat_images(output, img)
    return output

Ecco un esempio di utilizzo:

>>> images = ["ronda.jpeg", "rhod.jpeg", "ronda.jpeg", "rhod.jpeg"]
>>> output = concat_n_images(images)
>>> import matplotlib.pyplot as plt
>>> plt.imshow(output)
>>> plt.show()

inserisci qui la descrizione dell'immagine


Il tuo output = concat_images(output, ...è quello che stavo cercando quando ho iniziato a cercare un modo per farlo. Grazie.
edesz

Ciao ballsatballsdotballs, ho una domanda sulla tua risposta. Se voglio aggiungere il sottotitolo per ogni sotto-immagine, come posso farlo? Grazie.
user297850

12

Sulla base della risposta di DTing ho creato una funzione più facile da usare:

from PIL import Image


def append_images(images, direction='horizontal',
                  bg_color=(255,255,255), aligment='center'):
    """
    Appends images in horizontal/vertical direction.

    Args:
        images: List of PIL images
        direction: direction of concatenation, 'horizontal' or 'vertical'
        bg_color: Background color (default: white)
        aligment: alignment mode if images need padding;
           'left', 'right', 'top', 'bottom', or 'center'

    Returns:
        Concatenated image as a new PIL image object.
    """
    widths, heights = zip(*(i.size for i in images))

    if direction=='horizontal':
        new_width = sum(widths)
        new_height = max(heights)
    else:
        new_width = max(widths)
        new_height = sum(heights)

    new_im = Image.new('RGB', (new_width, new_height), color=bg_color)


    offset = 0
    for im in images:
        if direction=='horizontal':
            y = 0
            if aligment == 'center':
                y = int((new_height - im.size[1])/2)
            elif aligment == 'bottom':
                y = new_height - im.size[1]
            new_im.paste(im, (offset, y))
            offset += im.size[0]
        else:
            x = 0
            if aligment == 'center':
                x = int((new_width - im.size[0])/2)
            elif aligment == 'right':
                x = new_width - im.size[0]
            new_im.paste(im, (x, offset))
            offset += im.size[1]

    return new_im

Consente di scegliere un colore di sfondo e l'allineamento dell'immagine. È anche facile eseguire la ricorsione:

images = map(Image.open, ['hummingbird.jpg', 'tiger.jpg', 'monarch.png'])

combo_1 = append_images(images, direction='horizontal')
combo_2 = append_images(images, direction='horizontal', aligment='top',
                        bg_color=(220, 140, 60))
combo_3 = append_images([combo_1, combo_2], direction='vertical')
combo_3.save('combo_3.png')

Esempio di immagine concatenata


8

Ecco una funzione che generalizza gli approcci precedenti, creando una griglia di immagini in PIL:

from PIL import Image
import numpy as np

def pil_grid(images, max_horiz=np.iinfo(int).max):
    n_images = len(images)
    n_horiz = min(n_images, max_horiz)
    h_sizes, v_sizes = [0] * n_horiz, [0] * (n_images // n_horiz)
    for i, im in enumerate(images):
        h, v = i % n_horiz, i // n_horiz
        h_sizes[h] = max(h_sizes[h], im.size[0])
        v_sizes[v] = max(v_sizes[v], im.size[1])
    h_sizes, v_sizes = np.cumsum([0] + h_sizes), np.cumsum([0] + v_sizes)
    im_grid = Image.new('RGB', (h_sizes[-1], v_sizes[-1]), color='white')
    for i, im in enumerate(images):
        im_grid.paste(im, (h_sizes[i % n_horiz], v_sizes[i // n_horiz]))
    return im_grid

Ridurrà ogni riga e colonna della griglia al minimo. Puoi avere solo una riga usando pil_grid (immagini), o solo una colonna usando pil_grid (immagini, 1).

Un vantaggio dell'utilizzo di PIL rispetto a soluzioni basate su array numpy è che puoi gestire immagini strutturate in modo diverso (come immagini in scala di grigi o basate su tavolozza).

Output di esempio

def dummy(w, h):
    "Produces a dummy PIL image of given dimensions"
    from PIL import ImageDraw
    im = Image.new('RGB', (w, h), color=tuple((np.random.rand(3) * 255).astype(np.uint8)))
    draw = ImageDraw.Draw(im)
    points = [(i, j) for i in (0, im.size[0]) for j in (0, im.size[1])]
    for i in range(len(points) - 1):
        for j in range(i+1, len(points)):
            draw.line(points[i] + points[j], fill='black', width=2)
    return im

dummy_images = [dummy(20 + np.random.randint(30), 20 + np.random.randint(30)) for _ in range(10)]

pil_grid(dummy_images):

line.png

pil_grid(dummy_images, 3):

inserisci qui la descrizione dell'immagine

pil_grid(dummy_images, 1):

inserisci qui la descrizione dell'immagine


Questa riga in pil_grid: h_sizes, v_sizes = [0] * n_horiz, [0] * (n_images // n_horiz) dovrebbe leggere: h_sizes, v_sizes = [0] * n_horiz, [0] * ((n_images // n_horiz) + (1 if n_images % n_horiz > 0 else 0)) Motivo: se la larghezza orizzontale non divide il numero di immagini in numeri interi, è necessario accettare la riga aggiuntiva se incompleta.
Bernhard Wagner

3

Se tutte le altezze dell'immagine sono uguali,

imgs = [‘a.jpg’, b.jpg’, c.jpg’]
concatenated = Image.fromarray(
  np.concatenate(
    [np.array(Image.open(x)) for x in imgs],
    axis=1
  )
)

forse puoi ridimensionare le immagini prima della concatenazione in questo modo,

imgs = [‘a.jpg’, b.jpg’, c.jpg’]
concatenated = Image.fromarray(
  np.concatenate(
    [np.array(Image.open(x).resize((640,480)) for x in imgs],
    axis=1
  )
)

1
Semplice e facile. Grazie
Mike de Klerk

2

Ecco la mia soluzione:

from PIL import Image


def join_images(*rows, bg_color=(0, 0, 0, 0), alignment=(0.5, 0.5)):
    rows = [
        [image.convert('RGBA') for image in row]
        for row
        in rows
    ]

    heights = [
        max(image.height for image in row)
        for row
        in rows
    ]

    widths = [
        max(image.width for image in column)
        for column
        in zip(*rows)
    ]

    tmp = Image.new(
        'RGBA',
        size=(sum(widths), sum(heights)),
        color=bg_color
    )

    for i, row in enumerate(rows):
        for j, image in enumerate(row):
            y = sum(heights[:i]) + int((heights[i] - image.height) * alignment[1])
            x = sum(widths[:j]) + int((widths[j] - image.width) * alignment[0])
            tmp.paste(image, (x, y))

    return tmp


def join_images_horizontally(*row, bg_color=(0, 0, 0), alignment=(0.5, 0.5)):
    return join_images(
        row,
        bg_color=bg_color,
        alignment=alignment
    )


def join_images_vertically(*column, bg_color=(0, 0, 0), alignment=(0.5, 0.5)):
    return join_images(
        *[[image] for image in column],
        bg_color=bg_color,
        alignment=alignment
    )

Per queste immagini:

images = [
    [Image.open('banana.png'), Image.open('apple.png')],
    [Image.open('lime.png'), Image.open('lemon.png')],
]

I risultati saranno come:


join_images(
    *images,
    bg_color='green',
    alignment=(0.5, 0.5)
).show()

inserisci qui la descrizione dell'immagine


join_images(
    *images,
    bg_color='green',
    alignment=(0, 0)

).show()

inserisci qui la descrizione dell'immagine


join_images(
    *images,
    bg_color='green',
    alignment=(1, 1)
).show()

inserisci qui la descrizione dell'immagine


1
""" 
merge_image takes three parameters first two parameters specify 
the two images to be merged and third parameter i.e. vertically
is a boolean type which if True merges images vertically
and finally saves and returns the file_name
"""
def merge_image(img1, img2, vertically):
    images = list(map(Image.open, [img1, img2]))
    widths, heights = zip(*(i.size for i in images))
    if vertically:
        max_width = max(widths)
        total_height = sum(heights)
        new_im = Image.new('RGB', (max_width, total_height))

        y_offset = 0
        for im in images:
            new_im.paste(im, (0, y_offset))
            y_offset += im.size[1]
    else:
        total_width = sum(widths)
        max_height = max(heights)
        new_im = Image.new('RGB', (total_width, max_height))

        x_offset = 0
        for im in images:
            new_im.paste(im, (x_offset, 0))
            x_offset += im.size[0]

    new_im.save('test.jpg')
    return 'test.jpg'

1
from __future__ import print_function
import os
from pil import Image

files = [
      '1.png',
      '2.png',
      '3.png',
      '4.png']

result = Image.new("RGB", (800, 800))

for index, file in enumerate(files):
path = os.path.expanduser(file)
img = Image.open(path)
img.thumbnail((400, 400), Image.ANTIALIAS)
x = index // 2 * 400
y = index % 2 * 400
w, h = img.size
result.paste(img, (x, y, x + w, y + h))

result.save(os.path.expanduser('output.jpg'))

Produzione

inserisci qui la descrizione dell'immagine


0

Basta aggiungere alle soluzioni già suggerite. Presuppone la stessa altezza, nessun ridimensionamento.

import sys
import glob
from PIL import Image
Image.MAX_IMAGE_PIXELS = 100000000  # For PIL Image error when handling very large images

imgs    = [ Image.open(i) for i in list_im ]

widths, heights = zip(*(i.size for i in imgs))
total_width = sum(widths)
max_height = max(heights)

new_im = Image.new('RGB', (total_width, max_height))

# Place first image
new_im.paste(imgs[0],(0,0))

# Iteratively append images in list horizontally
hoffset=0
for i in range(1,len(imgs),1):
    **hoffset=imgs[i-1].size[0]+hoffset  # update offset**
    new_im.paste(imgs[i],**(hoffset,0)**)

new_im.save('output_horizontal_montage.jpg')

0

la mia soluzione sarebbe:

import sys
import os
from PIL import Image, ImageFilter
from PIL import ImageFont
from PIL import ImageDraw 

os.chdir('C:/Users/Sidik/Desktop/setup')
print(os.getcwd())

image_list= ['IMG_7292.jpg','IMG_7293.jpg','IMG_7294.jpg', 'IMG_7295.jpg' ]

image = [Image.open(x) for x in image_list]  # list
im_1 = image[0].rotate(270)
im_2 = image[1].rotate(270)
im_3 = image[2].rotate(270)
#im_4 = image[3].rotate(270)

height = image[0].size[0]
width = image[0].size[1]
# Create an empty white image frame
new_im = Image.new('RGB',(height*2,width*2),(255,255,255))

new_im.paste(im_1,(0,0))
new_im.paste(im_2,(height,0))
new_im.paste(im_3,(0,width))
new_im.paste(im_4,(height,width))


draw = ImageDraw.Draw(new_im)
font = ImageFont.truetype('arial',200)

draw.text((0, 0), '(a)', fill='white', font=font)
draw.text((height, 0), '(b)', fill='white', font=font)
draw.text((0, width), '(c)', fill='white', font=font)
#draw.text((height, width), '(d)', fill='white', font=font)

new_im.show()
new_im.save('BS1319.pdf')   
[![Laser spots on the edge][1]][1]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.