Kombinieren Sie mehrere Bilder horizontal mit Python

121

Ich versuche, einige JPEG-Bilder in Python horizontal zu kombinieren.

Problem

Ich habe 3 Bilder - jedes ist 148 x 95 - siehe Anhang. Ich habe gerade 3 Kopien desselben Bildes gemacht - deshalb sind sie gleich.

Geben Sie hier die Bildbeschreibung einGeben Sie hier die Bildbeschreibung einGeben Sie hier die Bildbeschreibung ein

Mein Versuch

Ich versuche, sie mit dem folgenden Code horizontal zu verbinden:

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

Dies erzeugt jedoch die Ausgabe, die als angehängt ist test.jpg.

Geben Sie hier die Bildbeschreibung ein

Frage

Gibt es eine Möglichkeit, diese Bilder horizontal zu verketten, sodass in den Teilbildern in test.jpg kein zusätzliches Teilbild angezeigt wird?

zusätzliche Information

Ich suche nach einer Möglichkeit, n Bilder horizontal zu verketten. Ich möchte diesen Code allgemein verwenden, daher würde ich es vorziehen:

  • Wenn möglich, Bildabmessungen nicht fest codieren
  • Geben Sie die Abmessungen in einer Zeile an, damit sie leicht geändert werden können
edesz
quelle
2
Warum ist for i in xrange(...)in Ihrem Code ein? Sollten Sie sich nicht pasteum die drei von Ihnen angegebenen Bilddateien kümmern?
Msw
Frage, werden Ihre Bilder immer die gleiche Größe haben?
Dermen
dermen: ja, bilder haben immer die gleiche größe. msw: Ich war mir nicht sicher, wie ich die Bilder durchlaufen sollte, ohne dazwischen ein Leerzeichen zu lassen - mein Ansatz ist wahrscheinlich nicht der beste.
Edesz

Antworten:

170

Sie können so etwas tun:

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

Geben Sie hier die Bildbeschreibung ein


Das verschachtelte for i in xrange(0,444,95):Bild fügt jedes Bild fünfmal ein und ist 95 Pixel voneinander entfernt. Jede Iteration der äußeren Schleife wird über die vorherige eingefügt.

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

Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein

DTing
quelle
Zwei Fragen: 1. x_offset = 0- Ist dies die Staffelung zwischen Bildzentren? 2. Wie ändert sich Ihr Ansatz bei einer vertikalen Verkettung?
Edesz
2
Das zweite Argument für das Einfügen ist eine Box. "Das Box-Argument ist entweder ein 2-Tupel, das die obere linke Ecke angibt, ein 4-Tupel, das die linke, obere, rechte und untere Pixelkoordinate definiert, oder Keine (wie (0, 0))." Also im 2-Tupel verwenden wir x_offsetals left. Verfolgen Sie bei vertikaler Konkation das y-offset, oder top. Anstelle von sum(widths)und max(height), do sum(heights)und max(widths)und verwenden Sie das zweite Argument der 2-Tupel-Box. inkrementieren y_offsetum im.size[1].
DTing
21
Schöne Lösung. Beachten Sie in Python3, dass Maps nur einmal wiederholt werden können, sodass Sie images = map (Image.open, image_files) erneut ausführen müssen, bevor Sie die Bilder das zweite Mal durchlaufen.
Naijaba
1
Jaijaba Ich bin auch auf das von Ihnen beschriebene Problem gestoßen, also habe ich die Lösung von DTing bearbeitet, um ein Listenverständnis anstelle einer Karte zu verwenden.
Ben Quigley
1
Ich musste Listenverständnis anstelle von mapin Python3.6
ClementWalter
89

Ich würde das versuchen:

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

Es sollte funktionieren, solange alle Bilder von der gleichen Art sind (alle RGB, alle RGBA oder alle Graustufen). Es sollte nicht schwierig sein, sicherzustellen, dass dies bei einigen weiteren Codezeilen der Fall ist. Hier sind meine Beispielbilder und das Ergebnis:

Test1.jpg

Test1.jpg

Test2.jpg

Test2.jpg

Test3.jpg

Test3.jpg

Trifecta.jpg:

kombinierte Bilder

Trifecta_vertical.jpg

Geben Sie hier die Bildbeschreibung ein

dermen
quelle
Vielen Dank. Eine weitere gute Antwort. Wie würde min_shape =....und imgs_comb....ändert sich für eine vertikale Verkettung? Könnten Sie das hier als Kommentar oder in Ihrer Antwort posten?
Edesz
3
Für vertikal wechseln Sie hstackzu vstack.
Dermen
Noch eine Frage: Ihr erstes Bild ( Test1.jpg ) ist größer als die anderen Bilder. In Ihrem endgültigen (horizontalen oder vertikalen) verketteten Bild haben alle Bilder die gleiche Größe. Können Sie erklären, wie Sie das erste Bild verkleinern konnten, bevor Sie es verketten?
Edesz
Ich habe Image.resizevon PIL verwendet. min_shapeist ein Tupel von (min_width, min_height) und (np.asarray( i.resize(min_shape) ) for i in imgs )verkleinert dann alle Bilder auf diese Größe. In der Tat min_shapekann jeder sein, den (width,height)Sie wünschen, denken Sie daran, dass das Vergrößern von Bildern mit niedriger Auflösung sie verschwimmen lässt!
Dermen
3
Wenn Sie nur Bilder ohne Einzelheiten miteinander kombinieren möchten, ist dies hier wahrscheinlich die einfachste und flexibelste Antwort. Es berücksichtigt unterschiedliche Bildgrößen, eine beliebige Anzahl von Bildern und unterschiedliche Bildformate. Dies war eine sehr gut durchdachte Antwort und EXTREM nützlich. Hätte nie daran gedacht, Numpy zu verwenden. Danke dir.
Noctsol
26

Bearbeiten: Die Antwort von DTing ist besser auf Ihre Frage anwendbar, da PIL verwendet wird. Ich lasse dies jedoch für den Fall, dass Sie wissen möchten, wie es in Numpy geht.

Hier ist eine Numpy / Matplotlib-Lösung, die für N Bilder (nur Farbbilder) jeder Größe / Form funktionieren sollte.

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

Hier ist ein Beispiel:

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

Geben Sie hier die Bildbeschreibung ein

derricw
quelle
Sie haben output = concat_images(output, ...gesucht, als ich nach einem Weg suchte, dies zu tun. Vielen Dank.
Edesz
Hallo Ballsatballsdotballs, ich habe eine Frage zu Ihrer Antwort. Wenn ich den Untertitel für jedes Teilbild hinzufügen möchte, wie geht das? Vielen Dank.
user297850
12

Basierend auf der Antwort von DTing habe ich eine Funktion erstellt, die einfacher zu verwenden ist:

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

Es ermöglicht die Auswahl einer Hintergrundfarbe und Bildausrichtung. Es ist auch einfach, eine Rekursion durchzuführen:

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

Beispiel verkettetes Bild

teekarna
quelle
8

Hier ist eine Funktion, die frühere Ansätze verallgemeinert und ein Bildraster in PIL erstellt:

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

Dadurch werden alle Zeilen und Spalten des Rasters auf ein Minimum verkleinert. Sie können nur eine Zeile mit pil_grid (Bilder) oder nur eine Spalte mit pil_grid (Bilder, 1) haben.

Ein Vorteil der Verwendung von PIL gegenüber auf Numpy-Arrays basierenden Lösungen besteht darin, dass Sie mit unterschiedlich strukturierten Bildern (wie Graustufen- oder Palettenbildern) umgehen können.

Beispielausgaben

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

Geben Sie hier die Bildbeschreibung ein

pil_grid(dummy_images, 1)::

Geben Sie hier die Bildbeschreibung ein

Maxime
quelle
Diese Zeile in pil_grid: h_sizes, v_sizes = [0] * n_horiz, [0] * (n_images // n_horiz) sollte lauten: h_sizes, v_sizes = [0] * n_horiz, [0] * ((n_images // n_horiz) + (1 if n_images % n_horiz > 0 else 0)) Grund: Wenn die horizontale Breite die Anzahl der Bilder nicht in Ganzzahlen teilt, müssen Sie die zusätzliche, wenn auch unvollständige Zeile berücksichtigen.
Bernhard Wagner
3

Wenn alle Bildhöhen gleich sind,

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

Vielleicht können Sie die Größe von Bildern vor der Verkettung wie folgt ändern:

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
  )
)
plhn
quelle
1
Simpel und einfach. Danke
Mike de Klerk
2

Hier ist meine Lösung:

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
    )

Für diese Bilder:

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

Die Ergebnisse sehen folgendermaßen aus:


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

Geben Sie hier die Bildbeschreibung ein


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

).show()

Geben Sie hier die Bildbeschreibung ein


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

Geben Sie hier die Bildbeschreibung ein

Mikhail Gerasimov
quelle
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'
Raj Yadav
quelle
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'))

Ausgabe

Geben Sie hier die Bildbeschreibung ein

Jayesh Baviskar
quelle
0

Fügen Sie einfach die bereits vorgeschlagenen Lösungen hinzu. Nimmt die gleiche Höhe an, keine Größenänderung.

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')
Kelmok
quelle
0

Meine Lösung wäre:

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]
Avral
quelle