Kết hợp một số hình ảnh theo chiều ngang với Python


121

Tôi đang cố gắng kết hợp một số hình ảnh JPEG theo chiều ngang bằng Python.

Vấn đề

Tôi có 3 hình ảnh - mỗi hình là 148 x 95 - xem đính kèm. Tôi chỉ tạo 3 bản sao của cùng một hình ảnh - đó là lý do tại sao chúng giống nhau.

nhập mô tả hình ảnh ở đâynhập mô tả hình ảnh ở đâynhập mô tả hình ảnh ở đây

Nỗ lực của tôi

Tôi đang cố gắng nối chúng theo chiều ngang bằng cách sử dụng mã sau:

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

Tuy nhiên, điều này đang tạo ra đầu ra kèm theo như test.jpg.

nhập mô tả hình ảnh ở đây

Câu hỏi

Có cách nào để nối những hình ảnh này theo chiều ngang để các hình ảnh phụ trong test.jpg không hiển thị thêm một phần hình ảnh không?

thông tin thêm

Tôi đang tìm một cách để nối n hình ảnh theo chiều ngang. Tôi muốn sử dụng mã này nói chung nên tôi muốn:

  • không mã hóa kích thước hình ảnh, nếu có thể
  • chỉ định kích thước trong một dòng để có thể dễ dàng thay đổi chúng

2
Tại sao lại có for i in xrange(...)mã của bạn? Bạn không nên pastequan tâm đến ba tệp hình ảnh mà bạn chỉ định?
msw

câu hỏi, hình ảnh của bạn sẽ luôn có cùng kích thước?
trận dermen


dermen: có, hình ảnh sẽ luôn có cùng kích thước. msw: Tôi không chắc làm thế nào để lặp lại các hình ảnh mà không để lại khoảng trống ở giữa - cách tiếp cận của tôi có lẽ không phải là cách tốt nhất để sử dụng.
edesz

Câu trả lời:


171

Bạn có thể làm điều gì đó như sau:

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

nhập mô tả hình ảnh ở đây


Lồng nhau cho for i in xrange(0,444,95):là dán mỗi hình ảnh 5 lần, so le cách nhau 95 pixel. Mỗi lần lặp lại vòng lặp bên ngoài dán lên lần trước.

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

nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây nhập mô tả hình ảnh ở đây


Hai câu hỏi: 1. x_offset = 0- Đây có phải là sự chênh lệch giữa các trung tâm hình ảnh không? 2. Đối với một nối dọc, cách tiếp cận của bạn thay đổi như thế nào?
edesz

2
Đối số thứ hai của dán là một hộp. "Đối số hộp là 2 bộ cho góc trên bên trái, 4 bộ xác định tọa độ pixel trái, trên, phải và dưới hoặc Không có (giống như (0, 0))." Vì vậy, trong 2 tuple chúng tôi đang sử dụng x_offsetnhư left. Đối với kết nối dọc, hãy theo dõi dấu y-offset, hoặc top. Thay vì sum(widths)max(height), hãy làm sum(heights)max(widths)và sử dụng đối số thứ hai của hộp 2 bộ. tăng y_offsetdo im.size[1].
DTing

21
Giải pháp tốt. Lưu ý trong python3 rằng bản đồ chỉ có thể được lặp lại một lần, vì vậy bạn phải thực hiện lại images = map (Image.open, image_files) trước khi lặp lại các hình ảnh lần thứ hai.
Naijaba

1
Jaijaba Tôi cũng gặp phải vấn đề mà bạn mô tả, vì vậy tôi đã chỉnh sửa giải pháp của DTing để sử dụng khả năng hiểu danh sách thay vì bản đồ.
Ben Quigley

1
Tôi đã vào danh sách sử dụng sự hiểu biết thay vì maptrong python3.6
ClementWalter

89

Tôi sẽ thử điều này:

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

Nó sẽ hoạt động miễn là tất cả hình ảnh có cùng một loại (tất cả RGB, tất cả RGBA hoặc tất cả thang độ xám). Sẽ không khó để đảm bảo đây là trường hợp với một vài dòng mã nữa. Đây là hình ảnh ví dụ của tôi và kết quả:

Test1.jpg

Test1.jpg

Test2.jpg

Test2.jpg

Test3.jpg

Test3.jpg

Trifecta.jpg:

hình ảnh kết hợp

Trifecta_vertical.jpg

nhập mô tả hình ảnh ở đây


Cảm ơn rất nhiều. Một câu trả lời hay. Làm thế nào sẽ min_shape =....imgs_comb....thay đổi cho một nối dọc? Bạn có thể đăng điều đó ở đây như một bình luận, hoặc trong câu trả lời của bạn?
edesz

3
Đối với ngành dọc, hãy thay đổi hstackthành vstack.
trận dermen

Một câu hỏi nữa: Hình ảnh đầu tiên của bạn ( Test1.jpg ) lớn hơn các hình ảnh khác. Trong hình ảnh nối cuối cùng (ngang hoặc dọc) của bạn, tất cả các hình ảnh đều có cùng kích thước. Bạn có thể giải thích làm thế nào bạn có thể thu nhỏ hình ảnh đầu tiên trước khi nối nó?
edesz

Tôi đã sử dụng Image.resizetừ PIL. min_shapelà một bộ của (min_width, min_height) và sau đó (np.asarray( i.resize(min_shape) ) for i in imgs )sẽ thu nhỏ tất cả các hình ảnh về kích thước đó. Trên thực tế, min_shapecó thể là bất kỳ hình ảnh nào (width,height)bạn muốn, chỉ cần lưu ý rằng việc phóng to hình ảnh có độ phân giải thấp sẽ khiến chúng bị mờ!
trận dermen

3
Nếu bạn đang tìm cách chỉ kết hợp các hình ảnh với nhau mà không có bất kỳ chi tiết cụ thể nào, thì đây có lẽ là câu trả lời đơn giản nhất và linh hoạt nhất ở đây. Nó tính đến kích thước hình ảnh khác nhau, bất kỳ số lượng hình ảnh nào và các định dạng hình ảnh khác nhau. Đây là một câu trả lời được suy nghĩ rất kỹ và CỰC KỲ hữu ích. Sẽ không bao giờ nghĩ đến việc sử dụng numpy. Cảm ơn bạn.
Noctsol

26

Chỉnh sửa: Câu trả lời của DTing áp dụng hơn cho câu hỏi của bạn vì nó sử dụng PIL, nhưng tôi sẽ để lại điều này trong trường hợp bạn muốn biết cách thực hiện nó trong numpy.

Đây là một giải pháp numpy / matplotlib sẽ hoạt động với N hình ảnh (chỉ hình ảnh màu) có kích thước / hình dạng bất kỳ.

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

Đây là ví dụ sử dụng:

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

nhập mô tả hình ảnh ở đây


Của bạn output = concat_images(output, ...là những gì tôi đã tìm kiếm khi tôi bắt đầu tìm kiếm một cách để làm điều này. Cảm ơn.
edesz

Xin chào ballatballsdotballs, tôi có một câu hỏi liên quan đến câu trả lời của bạn. Nếu tôi muốn thêm tiêu đề phụ cho mỗi hình ảnh phụ, làm thế nào để làm điều đó? Cảm ơn.
user297850

12

Dựa trên câu trả lời của DTing, tôi đã tạo một hàm dễ sử dụng hơn:

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

Nó cho phép chọn màu nền và căn chỉnh hình ảnh. Cũng dễ dàng thực hiện đệ quy:

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

Ví dụ về hình ảnh nối


8

Đây là một hàm tổng quát hóa các cách tiếp cận trước đó, tạo một lưới hình ảnh trong 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

Nó sẽ thu nhỏ từng hàng và cột của lưới xuống mức tối thiểu. Bạn chỉ có thể có một hàng bằng cách sử dụng pil_grid (hình ảnh) hoặc chỉ một cột bằng cách sử dụng pil_grid (hình ảnh, 1).

Một lợi ích của việc sử dụng PIL thay vì các giải pháp dựa trên mảng numpy là bạn có thể xử lý các hình ảnh có cấu trúc khác nhau (như thang độ xám hoặc hình ảnh dựa trên bảng màu).

Kết quả đầu ra mẫu

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

nhập mô tả hình ảnh ở đây

pil_grid(dummy_images, 1):

nhập mô tả hình ảnh ở đây


Dòng này trong pil_grid: h_sizes, v_sizes = [0] * n_horiz, [0] * (n_images // n_horiz) nên đọc: h_sizes, v_sizes = [0] * n_horiz, [0] * ((n_images // n_horiz) + (1 if n_images % n_horiz > 0 else 0)) Lý do: Nếu chiều rộng ngang không chia số lượng hình ảnh theo dạng số nguyên, bạn cần phải bổ sung cho dòng bổ sung nếu không đầy đủ.
Bernhard Wagner

3

Nếu tất cả các chiều cao của hình ảnh đều giống nhau,

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

có thể bạn có thể thay đổi kích thước hình ảnh trước khi nối như thế này,

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
Đơn giản và dễ dàng. Cảm ơn
Mike de Klerk

2

Đây là giải pháp của tôi:

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
    )

Đối với những hình ảnh này:

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

Kết quả sẽ như sau:


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

nhập mô tả hình ảnh ở đây


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

).show()

nhập mô tả hình ảnh ở đây


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

nhập mô tả hình ảnh ở đây


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

Đầu ra

nhập mô tả hình ảnh ở đây


0

Chỉ thêm vào các giải pháp đã được đề xuất. Giả sử cùng một chiều cao, không thay đổi kích thước.

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

giải pháp của tôi sẽ là:

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]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.