使用Python水平组合多个图像

ede*_*esz 98 paste python-imaging-library python-2.7

我试图在Python中水平组合一些JPEG图像.

问题

我有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')
Run Code Online (Sandbox Code Playgroud)

但是,这会产生附加的输出test.jpg.

在此输入图像描述

有没有办法水平连接这些图像,使test.jpg中的子图像没有显示额外的部分图像?

附加信息

我正在寻找一种水平连接n个图像的方法.我想一般使用这个代码所以我更愿意:

  • 如果可能的话,不要硬编码图像尺寸
  • 在一行中指定尺寸,以便可以轻松更改它们

DTi*_*ing 136

你可以这样做:

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')
Run Code Online (Sandbox Code Playgroud)

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')
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述 在此输入图像描述 在此输入图像描述

  • 好的解决方案 请注意,在python3中,map只能迭代一次,因此在第二次迭代图像之前,你必须再次执行images = map(Image.open,image_files). (21认同)
  • paste 的第二个参数是一个盒子。“box 参数要么是给出左上角的 2 元组,要么是定义左、上、右和下像素坐标的 4 元组,要么是 None(与 (0, 0) 相同)。” 所以在 2 元组中,我们使用 `x_offset` 作为 `left`。对于垂直连接,请跟踪“y-offset”或“top”。代替 `sum(widths)` 和 `max(height)`,执行 `sum(heights)` 和 `max(widths)` 并使用 2 元组框的第二个参数。通过 `im.size[1]` 增加 `y_offset`。 (2认同)

der*_*men 77

我会试试这个:

import numpy as np
import PIL

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' )
Run Code Online (Sandbox Code Playgroud)

只要所有图像具有相同的种类(所有RGB,所有RGBA或所有灰度),它都应该工作.确定这是多行代码的情况应该不难.这是我的示例图像,结果如下:

Test1.jpg

Test1.jpg

Test2.jpg

Test2.jpg

Test3.jpg

Test3.jpg

Trifecta.jpg:

合并图像

Trifecta_vertical.jpg

在此输入图像描述

  • 对于垂直,将````hstack```改为```vstack```. (3认同)
  • 如果您只想将图像组合在一起而没有任何细节,这可能是最简单,最灵活的答案.它考虑了不同的图像大小,任何图像数量和不同的图像格式.这是一个非常深思熟虑的答案,非常有用.从来没有想过使用numpy.谢谢. (3认同)

der*_*icw 22

编辑: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
Run Code Online (Sandbox Code Playgroud)

以下是示例用法:

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

在此输入图像描述


tee*_*rna 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
Run Code Online (Sandbox Code Playgroud)

它允许选择背景颜色和图像对齐.递归也很容易:

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')
Run Code Online (Sandbox Code Playgroud)

示例连接图像


plh*_*lhn 8

如果所有图像的高度相同,

from PIL import Image
import numpy as np

imgs = ['a.jpg', 'b.jp', 'c.jpg']
concatenated = Image.fromarray(
  np.concatenate(
    [np.array(Image.open(x)) for x in imgs],
    axis=1
  )
)
Run Code Online (Sandbox Code Playgroud)

也许您可以在像这样连接之前调整图像大小,

import numpy as np

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
  )
)
Run Code Online (Sandbox Code Playgroud)


Max*_*xim 6

这是一个概括以前方法的函数,在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
Run Code Online (Sandbox Code Playgroud)

它会将网格的每一行和每列缩小到最小.您可以通过使用pil_grid(图像)只使用一行,或者通过使用pil_grid(图像,1)只使用一列.

使用PIL而不是基于numpy-array的解决方案的一个好处是,您可以处理不同结构的图像(如灰度或基于调色板的图像).

示例输出

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)]
Run Code Online (Sandbox Code Playgroud)

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))` 原因:如果水平宽度没有以整数划分图像数量,则需要容纳额外的如果不完整的行。 (2认同)