Комбинирайте няколко изображения хоризонтално с Python

Опитвам се да комбинирам хоризонтално някои JPEG изображения в Python.

проблем

Имам 3 изображения - всяко е 148 x 95 - вижте приложеното. Току-що направих 3 копия на едно и също изображение - затова са еднакви.

въведете описание на изображението туквъведете описание на изображението туквъведете описание на изображението тук

Моят опит

Опитвам се да ги съединя хоризонтално, като използвам следния код:

import sys
from PIL import Image

list_im = ['Test1.jpg','Test2.jpg','Test3.jpg']

# creates a new empty image, RGB mode, and size 444 by 95
new_im = Image.new('RGB', (444,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')

Това обаче създава изхода, прикачен като test.jpg.

въведете описание на изображението тук

Въпрос

Има ли начин за хоризонтално свързване на тези изображения, така че подизображенията в test.jpg да нямат показване на допълнително частично изображение?

Допълнителна информация

Търся начин за хоризонтално свързване на n изображения. Бих искал да използвам този код като цяло, така че бих предпочел да:

  • да не кодирате твърдо размерите на изображението, ако е възможно
  • посочете размери в един ред, така че да могат лесно да се променят

person edesz    schedule 14.05.2015    source източник
comment
Защо има for i in xrange(...) във вашия код? Не трябва ли paste да се погрижи за трите файла с изображения, които сте посочили?   -  person msw    schedule 14.05.2015
comment
въпрос, вашите изображения винаги ли ще бъдат с еднакъв размер?   -  person dermen    schedule 14.05.2015
comment
възможен дубликат на Python Библиотека с изображения: Как да комбинирам 4 изображения в мрежа 2 x 2?   -  person jsbueno    schedule 14.05.2015
comment
dermen: да, изображенията винаги ще бъдат с еднакъв размер. msw: Не бях сигурен как да преминавам през изображенията, без да оставям празно пространство между тях - моят подход вероятно не е най-добрият за използване.   -  person edesz    schedule 15.05.2015


Отговори (10)


Можете да направите нещо подобно:

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

въведете описание на изображението тук


Вложеното за for i in xrange(0,444,95): поставя всяко изображение 5 пъти, разположено на 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('new_' + elem + '.jpg')

въведете описание на изображението туквъведете описание на изображението тук въведете описание на изображението тук

person dting    schedule 14.05.2015
comment
Два въпроса: 1. x_offset = 0 - това ли е залитането между центровете на изображението? 2. За вертикална конкатенация, как се променя вашият подход? - person edesz; 14.05.2015
comment
Вторият аргумент на paste е кутия. Аргументът на полето е или 2-кортеж, даващ горния ляв ъгъл, 4-кортеж, определящ лявата, горната, дясна и долната пикселна координата, или Няма (същото като (0, 0)). Така че в двойката използваме x_offset като left. За вертикално свързване следете y-offset или top. Вместо sum(widths) и max(height), направете sum(heights) и max(widths) и използвайте втория аргумент на кутията с 2 кортежа. увеличение y_offset с im.size[1]. - person dting; 14.05.2015
comment
Хубаво решение. Обърнете внимание в python3, че картите могат да бъдат итерирани само веднъж, така че ще трябва да направите images = map(Image.open, image_files) отново, преди да итерирате изображенията втори път. - person Naijaba; 11.01.2017
comment
Jaijaba Аз също се сблъсках с проблема, който описвате, така че редактирах решението на DTing, за да използва разбиране на списък вместо карта. - person Ben Quigley; 29.06.2018
comment
Трябваше да използвам разбиране на списъка вместо map в python3.6 - person ClementWalter; 30.11.2018
comment
Отбелязвайки същото като Naijaba: в python3 map връща итератор, но вместо да извиквате map два пъти, можете да създадете списък от итератора list(map(Image.open, infile_paths)) или да използвате разбиране на списък [Image.open(path) for path in infile_paths]. - person cheshirekow; 20.07.2019
comment
Мога ли да попитам защо импортирате sys? - person Jakub Bláha; 04.12.2019

Бих пробвал това:

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

Трябва да работи, стига всички изображения да са от едно и също разнообразие (всички RGB, всички RGBA или всички скала на сивото). Не би трябвало да е трудно да се гарантира, че това е така с още няколко реда код. Ето моите примерни изображения и резултата:

Тест1.jpg

Test1.jpg

Test2.jpg

Test2.jpg

Test3.jpg

Test3.jpg

Trifecta.jpg:

комбинирани изображения

Trifecta_vertical.jpg

въведете описание на изображението тук

person dermen    schedule 14.05.2015
comment
Благодаря много. Още един добър отговор. Как биха се променили min_shape =.... и imgs_comb.... за вертикална конкатенация? Бихте ли го публикували тук като коментар или във вашия отговор? - person edesz; 14.05.2015
comment
За вертикално променете hstack на vstack. - person dermen; 14.05.2015
comment
Още един въпрос: Вашето първо изображение (Test1.jpg) е по-голямо от другите изображения. Във вашето крайно (хоризонтално или вертикално) свързано изображение всички изображения са с еднакъв размер. Бихте ли обяснили как успяхте да намалите първото изображение, преди да го свържете? - person edesz; 14.05.2015
comment
Използвах Image.resize от PIL. min_shape е набор от (min_width, min_height) и след това (np.asarray( i.resize(min_shape) ) for i in imgs ) ще свие всички изображения до този размер. Всъщност min_shape може да бъде всяко (width,height), което желаете, само имайте предвид, че уголемяването на изображения с ниска разделителна способност ще ги направи размазани! - person dermen; 14.05.2015
comment
Ако търсите просто да комбинирате изображения заедно без никакви специфики, това е може би най-простият и най-гъвкавият отговор тук. Той отчита различния размер на изображението, който и да е # изображения и различните формати на картината. Това беше много добре обмислен отговор и ИЗКЛЮЧИТЕЛНО полезен. Никога не бих си помислил да използвам numpy. Благодаря ти. - person Noctsol; 11.01.2019

Редактиране: Отговорът на DTing е по-приложим към вашия въпрос, тъй като използва PIL, но ще оставя това, в случай че искате да знаете как да го направите в numpy.

Ето решение numpy/matplotlib, което трябва да работи за N изображения (само цветни изображения) с всякакъв размер/форма.

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

Ето примерна употреба:

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

въведете описание на изображението тук

person derricw    schedule 14.05.2015
comment
Вашият output = concat_images(output, ... е това, което търсех, когато започнах да търся начин да направя това. Благодаря. - person edesz; 14.05.2015
comment
Здравейте ballsatballsdotballs, имам един въпрос относно вашия отговор. Ако искам да добавя подзаглавието за всяко подизображение, как да го направя? Благодаря. - person user297850; 11.12.2016

Въз основа на отговора на DTing създадох функция, която е по-лесна за използване:

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

Позволява избор на цвят на фона и подравняване на изображението. Също така е лесно да се направи рекурсия:

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

„Примерно

person teekarna    schedule 07.10.2017
comment
Не съм на 100% къде е проблемът, но тази функция прави нещо странно с изображения, карайки обектите, които итерирам, да преминат от общо тегло от 25mb до 2gb. така че внимавайте с този метод - person FlyingZebra1; 07.12.2020

Ето функция, обобщаваща предишни подходи, създавайки мрежа от изображения в 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

Той ще свие всеки ред и колони от мрежата до минимум. Можете да имате само ред, като използвате pil_grid(images), или само колона, като използвате pil_grid(images, 1).

Едно предимство от използването на PIL спрямо базирани на numpy-масив решения е, че можете да работите с изображения, структурирани по различен начин (като изображения в сива скала или базирани на палитра изображения).

Примерни резултати

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

въведете описание на изображението тук

pil_grid(dummy_images, 1):

въведете описание на изображението тук

person Maxim    schedule 22.10.2017
comment
Този ред в pil_grid: h_sizes, v_sizes = [0] * n_horiz, [0] * (n_images // n_horiz) трябва да гласи: h_sizes, v_sizes = [0] * n_horiz, [0] * ((n_images // n_horiz) + (1 if n_images % n_horiz > 0 else 0)) Причина: Ако хоризонталната ширина не разделя броя на изображенията в цели числа, трябва да се съобразите с допълнителния, ако е непълен ред. - person Bernhard Wagner; 30.05.2019

Ако всички височини на изображението са еднакви,

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

може би можете да промените размера на изображенията преди конкатенацията по този начин,

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
  )
)
person plhn    schedule 27.03.2019
comment
Просто и лесно. Благодаря - person Mike de Klerk; 24.03.2020

Ето моето решение:

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
    )

За тези изображения:

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

Резултатите ще изглеждат така:


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

въведете описание на изображението тук


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

).show()

въведете описание на изображението тук


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

въведете описание на изображението тук

person Mikhail Gerasimov    schedule 12.01.2020

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

Изход

въведете описание на изображението тук

person Jayesh Baviskar    schedule 07.09.2018

Просто добавям към вече предложените решения. Предполага същата височина, без преоразмеряване.

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')
person Kelmok    schedule 06.09.2018

моето решение би било:

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]
person Avral    schedule 03.06.2020