【问题标题】:Combine all tiff images into one single image将所有 tiff 图像合并为一张图像
【发布时间】:2021-07-23 14:36:56
【问题描述】:

我的文件夹中有 15 个图块或 tiff 文件,我想将其合并为一个文件,并将所有图像合并为一个 tiff 图像。所有的瓷砖都应该缝合为一个单一的 tiff 图像。我该怎么做?

到目前为止我尝试了什么?

import imageio
import os

path = "path/to/dir"
image_path_list = os.listdir(path)

with imageio.get_writer("new_image.tif") as new_image:
    for image_path in image_path_list:
        image = imageio.imread(path+image_path)
        new_image.append_data(image)

这会在 tiff 文件中另存为单独的图像。我想将所有图像拼接在一起并保存如下:

1,2,3...,15 代表瓷砖。需要拼接成单张图片。

【问题讨论】:

  • 所有图片的尺寸(宽度和高度)是否相同?
  • @Daweo 是的,所有图像的尺寸都相同
  • 您只需在终端中使用 ImageMagick 即可完成,无需 Python magick montage -tile 3x -geometry +0+0 image{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}.tif BigBoy.tif
  • 您可以在枕头中使用 Image.paste() 参见:pillow.readthedocs.io/en/stable/reference/…,首先您创建一个合适大小的空图像,然后将每个图像粘贴到其中
  • @Mark Setchell 如果我使用 ImageMagicksuch file or directory @ error/blob.c/OpenBlob/3537. montage: 'BigBoy.tiff' @ error/montage.c/MontageImageCommand/1806. 我会收到以下错误我该如何解决?

标签: python python-imaging-library image-stitching python-imageio


【解决方案1】:

从您的 cmets 看来,您准备考虑使用非 Python 解决方案,因此我在终端中使用 ImageMagick 来蒙太奇 15 张图像,如下所示:

magick montage -tile 3x -geometry +0+0 09*tif result.tif

为了演示如何布置 5 张图片而不是 3 张图片,添加不同的背景并以不同方式影响水平和垂直间距,这里有一个变体:

magick montage -background magenta -tile 5x -geometry +5+15 09*tif result.tif


仅供参考,我制作了 15 个随机颜色的块,如下所示:

for x in {a..o} ; do magick xc: +noise random -scale 80x50\! 09$x.tif ; done 

【讨论】:

  • 谢谢!这有帮助
【解决方案2】:

给定一个目录,包含 15 张相同大小的图像

使用 PIL(枕头),我最终得到:

from PIL import Image


import os

path_to_file ='tiff-files'


images = []



for i in os.listdir(path_to_file):
    with Image.open(path_to_file+'/'+i) as im:
        images.append(im.copy())

    
new_image = Image.new(images[0].mode, (images[0].size[0]*3,images[0].size[1]*5))



new_image.paste(images[0])
new_image.paste(images[1],(images[0].size[0]*1,0))
new_image.paste(images[2],(images[0].size[0]*2,0))
new_image.paste(images[3],(0,images[0].size[1]*1))
new_image.paste(images[4],(images[0].size[0]*1,images[0].size[1]*1))
new_image.paste(images[5],(images[0].size[0]*2,images[0].size[1]*1))
new_image.paste(images[6],(0,images[0].size[1]*2))
new_image.paste(images[7],(images[0].size[0]*1,images[0].size[1]*2))
new_image.paste(images[8],(images[0].size[0]*2,images[0].size[1]*2))
new_image.paste(images[9],(0,images[0].size[1]*3))
new_image.paste(images[10],(images[0].size[0]*1,images[0].size[1]*3))
new_image.paste(images[11],(images[0].size[0]*2,images[0].size[1]*3))
new_image.paste(images[12],(0,images[0].size[1]*4))
new_image.paste(images[13],(images[0].size[0]*1,images[0].size[1]*4))
new_image.paste(images[14],(images[0].size[0]*2,images[0].size[1]*4))

new_image.show()

让我知道它是否有效.....

Mark Setchell 建议这里有一个新版本,希望它更好

from PIL import Image
import os

path_to_file ='tiff-files'



def stich_tile(path_to_file, xx , yy):
    images = []
    for i in os.listdir(path_to_file):
            images.append(i)

    
    if len(images) >= xx*yy:
        pass
    
    else:
        raise ValueError('not enough images in path_to_file !!!!!!!!!!!')
        
    
    sq_x = xx
    sq_y = yy
    img_x = (Image.open(path_to_file+'/'+images[0]).size[0])
    img_y = (Image.open(path_to_file+'/'+images[0]).size[1])
    img_mode = (Image.open(path_to_file+'/'+images[0]).mode)
    
    new_image = Image.new(img_mode, (img_x*sq_x, img_y*sq_y))
    
    x = 0
    y = 0
    cnt = 0
    for i in images:
        with Image.open(path_to_file+'/'+i) as img:
            new_image.paste(img, (x,y))
            cnt += 1
            x += img_x 
            if cnt == sq_x:
                x = 0
                y += img_y
                cnt = 0
            else:
                pass
                
  
    return new_image
 

stich_tile(path_to_file, 3, 5).show()

更多地按照https://stackoverflow.com/a/68468658/2836621的思路思考

import numpy as np
from PIL import Image
import os

# path_to_file ='tiff-files'

path_to_file ='tiff-files2'

# path_to_file ='tiff-files3'



    

image = []
for i in os.listdir(path_to_file):
    with Image.open(path_to_file+'/'+i) as im:
        image.append(im.copy()) 
        
     


w, h = image[0].size



new_image = np.zeros((4 * h, 3 * w)).astype('uint8')


col = 0
row = -1
for i, img in enumerate(image):
    if not i % 3 :
        row += 1
        col = 0
    img = np.array(img)
    new_image[row * h: (row + 1) * h, col * w: (col + 1) * w] = img
    col += 1




image_pillow = Image.fromarray(new_image, mode = 'L')

image_pillow.save('prova.tif', mode = 'L')


image_pillow.show()

使用 .tif 灰度 8 位图像测试

修改为 RGB et similia 添加 3 通道:

new_image = np.zeros((3 * h, 3 * w,3)).astype('uint8')

new_image[row * h: (row + 1) * h,col * w: (col + 1) * w,:] = img

最后一个例子再次作为 8 位灰度图像的函数:

import numpy as np
from PIL import Image
import os

path_to_file ='tiff-files'

# path_to_file ='tiff-files2'

# path_to_file ='tiff-files3'

# path_to_file ='tiff-files5'

    
def stich_img(path_to_file, x , y):

    image = []
    for i in os.listdir(path_to_file):
            image.append(path_to_file+'/'+i)
    
    print(image)
         
    if len(image) >= x*y:
        pass
    
    else:
        # raise ValueError('not enough images in path_to_file !!!!!!!!!!!')
        raise ValueError('EXCEPTION not enough images in path_to_file !!!!!!!!!!!', x*y ,'images  needed : ', len(image),'images present !!!')
    
    
    image = image[:x*y] #-----> riduce lista immagini al numero richiesto
    
    
    with Image.open(image[0]) as img0:
        w, h = img0.size
   
    
    
    
    # new_image = np.zeros((4 * h, 3 * w)).astype('uint8')
    new_image = np.zeros((y * h, x * w)).astype('uint8')
    
    
     
    col = 0
    row = -1
    for i, imgs in enumerate(image):
        with Image.open(imgs) as img:
            if not i % x :
                row += 1
                col = 0
            img = np.array(img)
            new_image[row * h: (row + 1) * h, col * w: (col + 1) * w] = img
            col += 1
            
    

    
    image_pillow = Image.fromarray(new_image, mode = 'L')
    
    return image_pillow

img_stiched = stich_img(path_to_file, 3,5)   

# img_stiched.save('prova.tif', mode = 'L')


img_stiched.show()

【讨论】:

  • 与其一次将所有 15 张图像不必要地加载到内存中,不如一次加载一张并将对 RAM 的需求减半。此外,与其像这样进行 15 次重复的 paste() 内联调用,不如在循环中做得更好,在循环中,您只需将宽度和高度添加为每次迭代的 x 和 y 增量。
  • 从这里:'legacy.imagemagick.org/discourse-server/viewtopic.php?t=14991':ImageMagick 会在内存中分配一些图像,直到内存耗尽,然后将其余图像放在 SSD 上。 ....不确定它是否仍然有效
  • 我不是在谈论 ImageMagick,这是一个完全不同的主张,非常基于 易用性,即终端中的单行命令并且不需要编码知识。我的建议是,如果你打算不厌其烦地学习、编写和使用 Python,你可以做得比编写占用内存、重复、难以维护且实现不灵活的代码要好得多。
  • 我已经开始弄清楚如何实施您的建议。一个问题,为什么我的代码中的 images.append(im) 而不是 images.append(im.copy()) 会生成无法使用 .show() 显示的图像列表?错误说缺少属性但有大小和模式?
  • 第三个使用pillow而不是imageio,当将它传递给numpy数组时,pillow会交换x和y轴:img_numpy = numpy.array(imge_opened_with_pillow_Image.open)
【解决方案3】:

使用 numpy: 此脚本接受图像生成器(以更快地处理大图像)。它不会提前检查它们的大小。如果图像高度不适合行高或行的宽度不同,则会失败。

    #!/usr/bin/env python3
import numpy as np
from imageio import imread, imwrite
from pathlib import Path


def tile_images(images, cols):
    """Tile images of same size to grid with given number of columns.
    
    Args:
        images (collection of ndarrays)
        cols (int): number of colums 
    
    Returns:
        ndarray: stitched image
    """
    images = iter(images)
    first = True
    rows = []
    i = 0
    while True:
        
        try:
            im = next(images)
            print(f"add image, shape: {im.shape}, type: {im.dtype}")
        except StopIteration:
            if first:
                break
            else:
                im = np.zeros_like(im)  # black background
                
        if first:
            row = im  # start next row
            first = False  
        else:    
            row = np.concatenate((row, im), axis=1)  # append to row
            
        i += 1
        if not i % cols:
            print(f"row done, shape: {row.shape}")
            rows.append(row) # finished row
            first = True
            
    tiled = np.concatenate(rows)   # stitch rows    
    return tiled        

def main():
    images = (imread(f) for f in Path().glob("*.*") if f.suffix in (".jpg", ".png") if f.name != "new.png") 
    new = tile_images(images, cols=3)
    imwrite("new.png", new)


def test():
    im1 = np.arange(65536).reshape(256,256)
    im2 = np.arange(65536/2).reshape(128,256)
    
    images = [im1,im1,im1,im2,im2,im2]
    
    # works
    new = tile_images(images, 3)
    imwrite("new.png", new)
    
    # failes
    new = tile_images(images, 2)
    imwrite("new2.png", new)
    
    
if __name__ == "__main__":
    main()
    # test()

【讨论】:

  • 感谢您的帮助。但是,我收到以下错误。 ValueError: all the input array dimensions for the concatenation axis must match exactly, but along dimension 0, the array at index 0 has size 776 and the array at index 1 has size 5000我该如何解决?
  • 确保文件夹中的所有图像大小相同。这个脚本不在乎。它接受图像生成器,因此速度更快,但它不会提前检查图像的大小。它尝试拼接文件,如果一行中图像的高度不同或行的宽度不同,它将失败。我排除了“new.png”表单图像列表,可能这是不同大小的图像。
【解决方案4】:

读取列表中的所有图像。使用两个嵌套的 for 循环遍历此列表。一个在 3 范围内,一个在 5 范围内。使用 numpy.hstack()numpy.vstack() 制作最终的 3x5 图像,假设每个平铺图像的大小相同。

【讨论】:

    猜你喜欢
    • 2013-07-18
    • 1970-01-01
    • 1970-01-01
    • 2012-03-04
    • 2022-07-01
    • 2012-04-13
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    相关资源
    最近更新 更多