Połącz kilka obrazów w poziomie za pomocą Pythona

122

Próbuję połączyć w poziomie niektóre obrazy JPEG w Pythonie.

Problem

Mam 3 zdjęcia - każdy ma wymiary 148 x 95 - patrz załączony. Właśnie wykonałem 3 kopie tego samego obrazu - dlatego są takie same.

wprowadź opis obrazu tutajwprowadź opis obrazu tutajwprowadź opis obrazu tutaj

Moja próba

Próbuję dołączyć do nich poziomo za pomocą następującego kodu:

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

Jednak jest to generowanie danych wyjściowych dołączonych jako test.jpg.

wprowadź opis obrazu tutaj

Pytanie

Czy istnieje sposób na poziome połączenie tych obrazów w taki sposób, aby obrazy podrzędne w pliku test.jpg nie miały dodatkowego częściowego obrazu?

Dodatkowe informacje

Szukam sposobu na poziome połączenie n obrazów. Chciałbym ogólnie używać tego kodu, więc wolałbym:

  • jeśli to możliwe, nie zakodować na stałe wymiarów obrazu
  • określ wymiary w jednej linii, aby można je było łatwo zmienić
edesz
źródło
2
Dlaczego for i in xrange(...)w Twoim kodzie jest znak ? Nie powinieneś pastezająć się trzema określonymi plikami graficznymi?
msw
pytanie, czy Twoje obrazy będą zawsze miały ten sam rozmiar?
dermen
dermen: tak, obrazy zawsze będą tego samego rozmiaru. msw: Nie byłem pewien, jak przewijać obrazy bez pozostawienia pustej przestrzeni pomiędzy - moje podejście prawdopodobnie nie jest najlepsze w użyciu.
edesz

Odpowiedzi:

173

Możesz zrobić coś takiego:

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

wprowadź opis obrazu tutaj


Zagnieżdżenie dla for i in xrange(0,444,95):polega na wklejaniu każdego obrazu 5 razy w odstępie 95 pikseli. Każda iteracja pętli zewnętrznej wklejana na poprzednią.

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

wprowadź opis obrazu tutaj wprowadź opis obrazu tutaj wprowadź opis obrazu tutaj

DTing
źródło
Dwa pytania: 1. x_offset = 0- czy to jest rozchodzenie się między centrami obrazu? 2. W przypadku konkatenacji pionowej, jak zmienia się twoje podejście?
edesz
2
Drugim argumentem wklejania jest pudełko. „Argument ramka jest albo 2 krotką, która określa lewy górny róg, 4 krotką określającą współrzędne lewego, górnego, prawego i dolnego piksela, albo Brak (to samo co (0, 0))." Więc w 2-krotce używamy x_offsetas left. W przypadku łączenia pionowego śledź y-offsetlub top. Zamiast sum(widths)and max(height), zrób sum(heights)i max(widths)i użyj drugiego argumentu z 2 krotki. zwiększyć y_offseto im.size[1].
DTing
21
Niezłe rozwiązanie. Zauważ w pythonie3, że mapy mogą być iterowane tylko raz, więc musiałbyś ponownie wykonać images = map (Image.open, image_files) przed iteracją przez obrazy po raz drugi.
Naijaba
1
Jaijaba Natknąłem się również na opisywany przez ciebie problem, więc zredagowałem rozwiązanie DTing tak, aby zamiast mapy używać list złożonych.
Ben Quigley
1
Musiałem użyć rozumienia list zamiast mapw
Pythonie3.6
89

Spróbowałbym tego:

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

Powinien działać, o ile wszystkie obrazy są tej samej odmiany (wszystkie RGB, wszystkie RGBA lub wszystkie skale szarości). Nie powinno być trudno upewnić się, że tak jest w przypadku kilku dodatkowych wierszy kodu. Oto moje przykładowe obrazy i wynik:

Test1.jpg

Test1.jpg

Test2.jpg

Test2.jpg

Test3.jpg

Test3.jpg

Trifecta.jpg:

połączone obrazy

Trifecta_vertical.jpg

wprowadź opis obrazu tutaj

dermen
źródło
Wielkie dzięki. Kolejna dobra odpowiedź. W jaki sposób min_shape =....i imgs_comb....zmiana na pionowej konkatenacji? Czy możesz zamieścić to tutaj jako komentarz lub w swojej odpowiedzi?
edesz
3
W przypadku pozycji pionowej zmień hstackna vstack.
dermen
Jeszcze jedno pytanie: Twój pierwszy obraz ( Test1.jpg ) jest większy niż pozostałe. W ostatecznym (poziomym lub pionowym) połączonym obrazie wszystkie obrazy mają ten sam rozmiar. Czy mógłbyś wyjaśnić, w jaki sposób udało ci się zmniejszyć pierwszy obraz przed połączeniem go?
edesz
Korzystałem Image.resizez PIL. min_shapejest krotką (min_width, min_height), a następnie (np.asarray( i.resize(min_shape) ) for i in imgs )zmniejszy wszystkie obrazy do tego rozmiaru. W rzeczywistości min_shapemoże to być dowolny (width,height), ale pamiętaj, że powiększanie obrazów o niskiej rozdzielczości sprawi, że będą rozmazane!
dermen
3
Jeśli chcesz po prostu połączyć obrazy bez żadnych szczegółów, jest to prawdopodobnie najprostsza i najbardziej elastyczna odpowiedź. Uwzględnia różny rozmiar obrazu, dowolną liczbę obrazów i różne formaty obrazów. To była bardzo dobrze przemyślana odpowiedź i BARDZO przydatna. Nigdy nie pomyślałbym o użyciu numpy. Dziękuję Ci.
Noctsol,
26

Edycja: odpowiedź DTing jest bardziej odpowiednia dla twojego pytania, ponieważ używa PIL, ale zostawię to na wypadek, gdybyś chciał wiedzieć, jak to zrobić w numpy.

Oto rozwiązanie numpy / matplotlib, które powinno działać dla N obrazów (tylko kolorowych obrazów) o dowolnym rozmiarze / kształcie.

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

Oto przykład użycia:

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

wprowadź opis obrazu tutaj

derricw
źródło
Twoja output = concat_images(output, ...jest tym, czego szukałem, kiedy zacząłem szukać sposobu, aby to zrobić. Dzięki.
edesz
Cześć, mam jedno pytanie dotyczące twojej odpowiedzi. Jeśli chcę dodać podtytuł do każdego obrazu podrzędnego, jak to zrobić? Dzięki.
user297850
12

Na podstawie odpowiedzi DTing stworzyłem funkcję, która jest łatwiejsza w użyciu:

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

Pozwala na wybór koloru tła i wyrównanie obrazu. Rekurencję można również łatwo wykonać:

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

Przykładowy obraz połączony

teekarna
źródło
8

Oto funkcja uogólniająca poprzednie podejścia, tworząca siatkę obrazów w 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

Zmniejszy każdy wiersz i kolumny siatki do minimum. Możesz mieć tylko wiersz, używając pil_grid (obrazy), lub tylko kolumnę, używając pil_grid (obrazy, 1).

Jedną z zalet korzystania z PIL w porównaniu z rozwiązaniami opartymi na tablicach numpy jest to, że można radzić sobie z obrazami o różnej strukturze (jak obrazy w skali szarości lub oparte na palecie).

Przykładowe wyjścia

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

wprowadź opis obrazu tutaj

pil_grid(dummy_images, 1):

wprowadź opis obrazu tutaj

Maksyma
źródło
Ten wiersz w pil_grid: h_sizes, v_sizes = [0] * n_horiz, [0] * (n_images // n_horiz) powinien brzmieć: h_sizes, v_sizes = [0] * n_horiz, [0] * ((n_images // n_horiz) + (1 if n_images % n_horiz > 0 else 0)) Powód: Jeśli pozioma szerokość nie dzieli liczby obrazów w liczbach całkowitych, musisz uwzględnić dodatkową, jeśli niekompletną linię.
Bernhard Wagner
3

Jeśli wszystkie wysokości obrazu są takie same,

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

może możesz zmienić rozmiar obrazów przed połączeniem w ten sposób,

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
źródło
1
Proste i łatwe. Dzięki
Mike de Klerk
2

Oto moje rozwiązanie:

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
    )

W przypadku tych obrazów:

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

Wyniki będą wyglądać następująco:


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

wprowadź opis obrazu tutaj


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

).show()

wprowadź opis obrazu tutaj


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

wprowadź opis obrazu tutaj

Michaił Gierasimow
źródło
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
źródło
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'))

Wynik

wprowadź opis obrazu tutaj

Jayesh Baviskar
źródło
0

Po prostu dodając do rozwiązań już sugerowanych. Przyjmuje tę samą wysokość, bez zmiany rozmiaru.

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
źródło
0

moim rozwiązaniem byłoby:

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
źródło