J'essaie de combiner horizontalement certaines images JPEG en Python.
J'ai 3 images - chacune est de 148 x 95 - voir ci-joint. Je viens de faire 3 copies de la même image - c'est pourquoi elles sont les mêmes.
J'essaie de les joindre horizontalement en utilisant le code suivant:
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')
Cependant, cela produit la sortie attachée en tant que test.jpg
.
Existe-t-il un moyen de concaténer horizontalement ces images de sorte que les sous-images de test.jpg ne présentent pas d'image partielle supplémentaire?
Je cherche un moyen de concaténer horizontalement n images. J'aimerais utiliser ce code en général, donc je préférerais:
Vous pouvez faire quelque chose comme ça:
import sys
from PIL import Image
images = map(Image.open, ['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
Test2.jpg
Test3.jpg
test.jpg
L'imbrication de for i in xrange(0,444,95):
consiste à coller chaque image 5 fois, en les décalant de 95 pixels. Chaque itération de boucle externe collant sur la précédente.
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')
Je voudrais essayer ceci:
import numpy as np
import PIL
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' )
Cela devrait fonctionner aussi longtemps que toutes les images sont de la même variété (tout RVB, tout RGBA ou tout en niveaux de gris) Il ne devrait pas être difficile de s’assurer que c’est le cas de quelques lignes de code supplémentaires. Voici mes images d'exemple et le résultat:
Edit: La réponse de DTing s’applique mieux à votre question car elle utilise la méthode PIL, mais je vais la laisser au cas où vous voudriez savoir comment procéder en numpy.
Voici une solution numpy/matplotlib qui devrait fonctionner pour N images (uniquement des images couleur) de toutes tailles/formes.
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
Voici un exemple d'utilisation:
>>> images = ["ronda.jpeg", "rhod.jpeg", "ronda.jpeg", "rhod.jpeg"]
>>> output = concat_n_images(images)
>>> import matplotlib.pyplot as plt
>>> plt.imshow(output)
>>> plt.show()
Sur la base de la réponse de DTing, j'ai créé une fonction plus facile à utiliser:
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
Il permet de choisir une couleur d'arrière-plan et un alignement d'image. C'est aussi facile de faire de la récursivité:
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')
Voici une fonction généralisant les approches précédentes, créant une grille d'images dans 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
Cela réduira chaque ligne et chaque colonne de la grille au minimum. Vous ne pouvez avoir qu'une ligne en utilisant pil_grid (images), ou seulement une colonne en utilisant pil_grid (images, 1).
L'un des avantages de l'utilisation de PIL sur des solutions reposant sur un tableau numérique est que vous pouvez traiter des images structurées différemment (comme des images en niveaux de gris ou basées sur une palette).
Exemple de sorties
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)
:
pil_grid(dummy_images, 3)
:
pil_grid(dummy_images, 1)
:
La réponse la plus votée ne fonctionne pas bien dans Python 3.6
Ce que j'ai changé, c'est d'utiliser list
pour convertir map(image.open, [])
iterator en objet de liste.
Alors ça marche bien.
import sys
from PIL import Image
images = list(map(Image.open, ['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')
Si toutes les hauteurs d’image sont identiques,
imgs = [‘a.jpg’, ‘b.jpg’, ‘c.jpg’]
concatenated = Image.fromarray(
np.concatenate(
[np.array(Image.open(x)) for x in imgs],
axis=1
)
)
peut-être pouvez-vous redimensionner les images avant la concaténation de la sorte,
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
)
)
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'))
"""
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'
Ajoutant juste aux solutions déjà suggérées. Assume la même hauteur, pas de redimensionnement.
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')