Gabungkan beberapa gambar secara horizontal dengan Python

121

Saya mencoba menggabungkan secara horizontal beberapa gambar JPEG dengan Python.

Masalah

Saya memiliki 3 gambar - masing-masing 148 x 95 - lihat terlampir. Saya baru saja membuat 3 salinan dari gambar yang sama - itulah mengapa mereka sama.

masukkan deskripsi gambar di sinimasukkan deskripsi gambar di sinimasukkan deskripsi gambar di sini

Upaya saya

Saya mencoba untuk bergabung dengan mereka secara horizontal menggunakan kode berikut:

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

Namun, ini menghasilkan keluaran yang terpasang sebagai test.jpg.

masukkan deskripsi gambar di sini

Pertanyaan

Apakah ada cara untuk menggabungkan gambar-gambar ini secara horizontal sehingga sub-gambar di test.jpg tidak menampilkan gambar parsial tambahan?

informasi tambahan

Saya mencari cara untuk menggabungkan n gambar secara horizontal. Saya ingin menggunakan kode ini secara umum, jadi saya lebih suka:

  • bukan untuk dimensi gambar hard-code, jika memungkinkan
  • tentukan dimensi dalam satu baris agar dapat diubah dengan mudah
edesz
sumber
2
Mengapa ada for i in xrange(...)di kode Anda? Tidakkah seharusnya pastemenangani tiga file gambar yang Anda tentukan?
msw
Pertanyaannya, apakah gambar Anda akan selalu berukuran sama?
Dermen
dermen: ya, gambar akan selalu berukuran sama. msw: Saya tidak yakin bagaimana melakukan loop melalui gambar, tanpa meninggalkan ruang kosong di antaranya - pendekatan saya mungkin bukan yang terbaik untuk digunakan.
edesz

Jawaban:

172

Anda bisa melakukan sesuatu seperti ini:

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

masukkan deskripsi gambar di sini


Bertingkat untuk for i in xrange(0,444,95):menempelkan setiap gambar 5 kali, dipisahkan dengan jarak 95 piksel. Setiap iterasi loop luar menempel di atas sebelumnya.

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

masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini masukkan deskripsi gambar di sini

DTing
sumber
Dua pertanyaan: 1. x_offset = 0- apakah ini pemisah antara pusat gambar? 2. Untuk penggabungan vertikal, bagaimana pendekatan Anda berubah?
edesz
2
Argumen tempel kedua adalah kotak. "Argumen kotaknya adalah 2-tupel yang memberikan sudut kiri atas, 4-tupel yang menentukan koordinat piksel kiri, atas, kanan, dan bawah, atau None (sama dengan (0, 0))." Jadi dalam 2-tupel kami gunakan x_offsetsebagai left. Untuk concat vertikal, perhatikan y-offset, atau top. Dari pada sum(widths)dan max(height), lakukan sum(heights)dan max(widths)dan gunakan argumen kedua dari kotak 2-tupel. increment y_offsetoleh im.size[1].
DTing
21
Solusi bagus. Perhatikan di python3 bahwa peta hanya dapat diulangi sekali, jadi Anda harus melakukan gambar = peta (Image.open, image_files) lagi sebelum mengulang melalui gambar untuk kedua kalinya.
Naijaba
1
Jaijaba Saya juga mengalami masalah yang Anda jelaskan, jadi saya mengedit solusi DTing untuk menggunakan pemahaman daftar alih-alih peta.
Ben Quigley
1
Saya harus menggunakan pemahaman daftar alih-alih mapdi python3.6
ClementWalter
89

Saya akan mencoba ini:

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

Ini harus berfungsi selama semua gambar memiliki variasi yang sama (semua RGB, semua RGBA, atau semua skala abu-abu). Seharusnya tidak sulit untuk memastikan hal ini terjadi dengan beberapa baris kode lagi. Berikut adalah contoh gambar saya, dan hasilnya:

Test1.jpg

Test1.jpg

Test2.jpg

Test2.jpg

Test3.jpg

Test3.jpg

Trifecta.jpg:

gambar gabungan

Trifecta_vertical.jpg

masukkan deskripsi gambar di sini

dermen
sumber
Terima kasih banyak. Jawaban bagus lainnya. Bagaimana min_shape =....dan imgs_comb....mengubah penggabungan vertikal? Bisakah Anda mempostingnya di sini sebagai komentar, atau dalam balasan Anda?
edesz
3
Untuk vertikal, ubah hstackke vstack.
Dermen
Satu pertanyaan lagi: Gambar pertama Anda ( Test1.jpg ) lebih besar dari gambar lainnya. Pada gambar gabungan akhir (horizontal atau vertikal), semua gambar memiliki ukuran yang sama. Bisakah Anda menjelaskan bagaimana Anda bisa mengecilkan gambar pertama sebelum menggabungkannya?
edesz
Saya digunakan Image.resizedari PIL. min_shapeadalah tupel dari (min_width, min_height) dan kemudian (np.asarray( i.resize(min_shape) ) for i in imgs )akan mengecilkan semua gambar ke ukuran tersebut. Sebenarnya, min_shapebisa apa saja yang (width,height)Anda inginkan, perlu diingat bahwa memperbesar gambar beresolusi rendah akan membuatnya buram!
Dermen
3
Jika Anda hanya ingin menggabungkan gambar tanpa spesifik, ini mungkin jawaban paling sederhana dan paling fleksibel di sini. Ini menjelaskan ukuran gambar yang berbeda, # gambar apa pun, dan berbagai format gambar. Ini adalah jawaban yang dipikirkan dengan sangat baik dan SANGAT berguna. Tidak akan pernah terpikir untuk menggunakan numpy. Terima kasih.
Noctsol
26

Edit: Jawaban DTing lebih dapat diterapkan pada pertanyaan Anda karena menggunakan PIL, tetapi saya akan membiarkannya jika Anda ingin tahu bagaimana melakukannya secara numpy.

Berikut adalah solusi numpy / matplotlib yang seharusnya berfungsi untuk gambar N (hanya gambar berwarna) dari berbagai ukuran / bentuk.

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

Berikut ini contoh penggunaan:

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

masukkan deskripsi gambar di sini

derricw
sumber
Anda output = concat_images(output, ...adalah apa yang saya cari ketika saya mulai mencari cara untuk melakukan ini. Terima kasih.
edesz
Hai ballsatballsdotballs, saya punya satu pertanyaan tentang jawaban Anda. Jika saya ingin menambahkan sub-judul untuk setiap sub-gambar, bagaimana cara melakukannya? Terima kasih.
pengguna297850
12

Berdasarkan jawaban DTing saya membuat fungsi yang lebih mudah digunakan:

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

Ini memungkinkan memilih warna latar belakang dan penyelarasan gambar. Rekursi juga mudah dilakukan:

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

Contoh gambar bersambung

teekarna
sumber
8

Berikut adalah fungsi yang menggeneralisasi pendekatan sebelumnya, membuat kisi gambar di 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

Ini akan mengecilkan setiap baris dan kolom dari grid ke minimum. Anda hanya dapat memiliki satu baris dengan menggunakan pil_grid (gambar), atau hanya kolom dengan menggunakan pil_grid (gambar, 1).

Salah satu keuntungan menggunakan PIL dibandingkan solusi berbasis numpy-array adalah Anda dapat menangani gambar yang terstruktur secara berbeda (seperti gambar berbasis grayscale atau palet).

Contoh keluaran

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

masukkan deskripsi gambar di sini

pil_grid(dummy_images, 1):

masukkan deskripsi gambar di sini

Pepatah
sumber
Baris ini di pil_grid: h_sizes, v_sizes = [0] * n_horiz, [0] * (n_images // n_horiz) harus dibaca: h_sizes, v_sizes = [0] * n_horiz, [0] * ((n_images // n_horiz) + (1 if n_images % n_horiz > 0 else 0)) Alasan: Jika lebar horizontal tidak membagi jumlah gambar dalam bilangan bulat, Anda perlu mengakomodasi baris tambahan jika tidak lengkap.
Bernhard Wagner
3

Jika semua tinggi gambar sama,

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

mungkin Anda bisa mengubah ukuran gambar sebelum penggabungan seperti ini,

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
sumber
1
Sederhana dan mudah. Terima kasih
Mike de Klerk
2

Inilah solusi saya:

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
    )

Untuk gambar-gambar ini:

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

Hasilnya akan terlihat seperti:


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

masukkan deskripsi gambar di sini


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

).show()

masukkan deskripsi gambar di sini


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

masukkan deskripsi gambar di sini

Mikhail Gerasimov
sumber
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
sumber
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'))

Keluaran

masukkan deskripsi gambar di sini

Jayesh Baviskar
sumber
0

Hanya menambahkan solusi yang sudah disarankan. Asumsikan ketinggian yang sama, tidak ada pengubahan ukuran.

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
sumber
0

solusi saya adalah:

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
sumber