Поєднайте кілька зображень горизонтально з Python


121

Я намагаюся горизонтально поєднувати деякі зображення JPEG в Python.

Проблема

У мене є 3 зображення - кожне розміром 148 x 95 - див. Я щойно зробив 3 копії одного і того ж зображення - тому вони однакові.

введіть тут опис зображеннявведіть тут опис зображеннявведіть тут опис зображення

Моя спроба

Я намагаюся горизонтально приєднатись до них за допомогою наступного коду:

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

Однак це дає результат, що додається як test.jpg.

введіть тут опис зображення

Питання

Чи існує спосіб горизонтального об'єднання цих зображень таким чином, щоб у під-зображень у test.jpg не було додаткового часткового зображення?

Додаткова інформація

Я шукаю спосіб горизонтального об'єднання російських зображень. Я хотів би використовувати цей код загалом, тому я вважаю за краще:

  • не для жорсткого кодування розмірів зображення, якщо це можливо
  • задайте розміри в одному рядку, щоб їх можна було легко змінити

2
Чому for i in xrange(...)в коді є? Чи не pasteслід дбати про вказані вами три файли зображень?
msw

питання, чи завжди ваші зображення будуть однакового розміру?
дермен


дермен: так, зображення завжди будуть однакового розміру. msw: Я не знав, як прокрутити зображення, не залишаючи порожнього проміжку між собою - мій підхід, мабуть, не найкраще використовувати.
edesz

Відповіді:


171

Ви можете зробити щось подібне:

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

введіть тут опис зображення введіть тут опис зображення введіть тут опис зображення


Дві запитання: 1. x_offset = 0- це розбіжність між центрами зображень? 2. Що стосується вертикальної конкатенації, як змінюється ваш підхід?
edesz

2
Другий аргумент пасти - це поле. "Аргумент поля - це 2-кортеж, що дає верхній лівий кут, 4-кортеж, що визначає ліву, верхню, праву та нижню координати пікселів, або None (такий же, як (0, 0))." Тож у 2-кортежі ми використовуємо x_offsetяк left. Для вертикальної лаконічності слідкуйте за y-offset, або top. Замість sum(widths)і max(height), зробіть sum(heights)і max(widths)використовуйте другий аргумент 2-кратного поля. приріст y_offsetна im.size[1].
DTing

21
Приємне рішення. Зауважте в python3, що карти можна повторювати лише один раз, тому вам доведеться робити зображення = map (Image.open, image_files) ще до повторного повторення зображень.
Найджаба

1
Jaijaba Я також зіткнувся з проблемою, яку ви описуєте, тому я відредагував рішення DTing, щоб використовувати розуміння списку замість карти.
Ben Quigley

1
Мені довелося використовувати розуміння списку замість mappython3.6
ClementWalter

89

Я б спробував це:

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 або всі градації сірого). Переконатися, що це так, має бути ще кілька рядків коду. Ось мої приклади зображень та результат:

Test1.jpg

Test1.jpg

Test2.jpg

Test2.jpg

Test3.jpg

Test3.jpg

Trifecta.jpg:

комбіновані зображення

Trifecta_vertical.jpg

введіть тут опис зображення


Дуже дякую. Ще одна гарна відповідь. Як би min_shape =....і imgs_comb....змінити для вертикальної конкатенації? Чи можете ви опублікувати це як коментар чи у відповіді?
edesz

3
Для вертикалі змініть hstackна vstack.
дермен

Ще одне питання: Ваше перше зображення ( Test1.jpg ) більше, ніж інші зображення. У вашому кінцевому (горизонтальному або вертикальному) зв'язаному зображенні всі зображення однакового розміру. Чи можете ви пояснити, як вам вдалося зменшити перше зображення перед тим, як його об'єднати?
edesz

Я використовував Image.resizeз PIL. min_shapeє кортежем (min_width, min_height), а потім (np.asarray( i.resize(min_shape) ) for i in imgs )зменшить усі зображення до цього розміру. Насправді це min_shapeможе бути будь- (width,height)який бажаючий, лише пам’ятайте, що збільшення зображень із низькою роздільною здатністю зробить їх розмитими!
дермен

3
Якщо ви хочете просто комбінувати зображення разом без будь-якої конкретності, це, мабуть, найпростіша та гнучка відповідь тут. Він пояснює різний розмір зображення, будь-яку кількість # зображень та різні формати зображення. Це була дуже добре продумана відповідь і НАДЗВИЧНО корисна. Ніколи б не думав використовувати нуме. Дякую.
Ноктсол

26

Редагувати: Відповідь DTing більше стосується вашого питання, оскільки він використовує PIL, але я залишу це питання, якщо ви хочете знати, як це зробити в нуме.

Ось рішення 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()

введіть тут опис зображення


Ви output = concat_images(output, ...то , що я шукав, коли я почав шукати спосіб , щоб зробити це. Дякую.
edesz

Привіт ballatballsdotballs, у мене є одне питання щодо вашої відповіді. Якщо я хочу додати підзаголовок для кожного під-зображення, як це зробити? Дякую.
user297850

12

На основі відповіді 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')

Приклад з’єднаного зображення


8

Ось функція, що узагальнює попередні підходи, створюючи сітку зображень у 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 (зображення) або лише стовпець, використовуючи pil_grid (зображення, 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):

введіть тут опис зображення


Цей рядок у 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)) Причина: Якщо горизонтальна ширина не ділить кількість зображень на цілі числа, вам потрібно вмістити додатковий, якщо неповний рядок.
Бернхард Вагнер

3

Якщо висота всіх зображень однакова,

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

1
Просто і легко. Спасибі
Майк де Клерк

2

Ось моє рішення:

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

введіть тут опис зображення


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

Вихідні дані

введіть тут опис зображення


0

Просто додавання до запропонованих рішень. Припускає однакову висоту, без зміни розміру.

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

моє рішення було б:

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]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.