【问题标题】:How to Split Image Into Multiple Pieces in Python如何在 Python 中将图像拆分为多个部分
【发布时间】:2011-08-22 15:23:30
【问题描述】:

我正在尝试使用 PIL 将一张照片分成多张。

def crop(Path,input,height,width,i,k,x,y,page):
    im = Image.open(input)
    imgwidth = im.size[0]
    imgheight = im.size[1]
    for i in range(0,imgheight-height/2,height-2):
        print i
        for j in range(0,imgwidth-width/2,width-2):
            print j
            box = (j, i, j+width, i+height)
            a = im.crop(box)
            a.save(os.path.join(Path,"PNG","%s" % page,"IMG-%s.png" % k))
            k +=1

但它似乎不起作用。它会分割照片,但不是以精确的方式(你可以试试)。

【问题讨论】:

  • “精确”宽度和高度是什么意思?
  • 使用 NumPy 库:tiles = [im[x:x+M,y:y+N] for x in range(0,im.shape[0],M) for y in range(0,im.shape[1],N)] - 请参阅下面的答案

标签: python image split python-imaging-library crop


【解决方案1】:

对于任何寻求简单方法的人,这里有一个简单的工作函数,用于将图像分成 NxN 部分。

def slice_image(filename, N):

    i = Image.open(filename)

    width = i.width
    height = i.height

    for x in range(N):

        for y in range(N):

            index = (x * pieces) + 1 + y

            img = i.crop((x * width/N, y * height/N,
                          x * width/N+ width/N, y * height/N+ height/N))

            img.save(f"{filename}_sliced_{index}.jpeg")

【讨论】:

    【解决方案2】:

    作为替代解决方案,我们将通过使用itertools.product 生成坐标网格来构建图块。我们将忽略边缘的部分图块,仅迭代两个区间之间的笛卡尔积, range(0, h-h%d, d) X range(0, w-w%d, d)

    给定filename:图像文件名,d:图块大小,dir_in:包含图像的目录的路径,dir_out:输出图块的目录:

    from PIL import Image
    from itertools import product
    
    def tile(filename, dir_in, dir_out, d):
        name, ext = os.path.splitext(filename)
        img = Image.open(os.path.join(dir_in, filename))
        w, h = img.size
        
        grid = product(range(0, h-h%d, d), range(0, w-w%d, d))
        for i, j in grid:
            box = (j, i, j+d, i+d)
            out = os.path.join(dir_out, f'{name}_{i}_{j}{ext}')
            img.crop(box).save(out)
    

    【讨论】:

    • 非常实用的解决方案,谢谢。将“fp”作为参数发送给函数可能会很有用,因为“文件名”和“fp”变量可能会造成混淆。
    • 谢谢@Kubra。我似乎在我的源代码中使用了fp,我已将其更改为正确的参数命名。
    • 真的需要这个解决方案
    【解决方案3】:

    最简单的方法:

    import image_slicer
    image_slicer.slice('/Address of image for exp/A1.png',16)
    

    此命令将图像分成 16 个切片,并将它们保存在输入图像所在的目录中。 你应该先安装 image_slicer:

    pip install image_slicer
    

    【讨论】:

      【解决方案4】:

      这是另一种解决方案,只是使用 NumPy 内置的np.array_split

      def divide_img_blocks(img, n_blocks=(5, 5)):
          horizontal = np.array_split(img, n_blocks[0])
          splitted_img = [np.array_split(block, n_blocks[1], axis=1) for block in horizontal]
          return np.asarray(splitted_img, dtype=np.ndarray).reshape(n_blocks)
      

      它返回一个 NumPy 数组,其维度作为 n_blocks 传递。 数组的每个元素都是一个块,因此要访问每个块并将其保存为图像,您应该编写如下内容:

      result = divide_img_blocks(my_image)
      
      for i in range(result.shape[0]):
          for j in range(result.shape[1]):
              cv2.imwrite(f"my_block_{i}_{j}.jpg", result[i,j])
      

      这个答案非常快,比@Nir 的答案快,后者在发布的答案中是最干净的。此外,它比建议的包(即image_slicer)快了近三个数量级。

      Time taken by divide_img_blocks: 0.0009832382202148438
      Time taken by Nir answer: 0.002960681915283203
      Time taken by image_slicer.slice: 0.4419238567352295
      

      希望它仍然有用。

      【讨论】:

      • 这是否假设图像是正方形的?如果您可以使用测试图像更新您的答案,那就太好了——您知道它是有效的。谢谢!
      • 嗨@pookie,该方法将采用任意图像大小和任意数量的“块”。 Here is a way of testing it.
      【解决方案5】:

      我建议使用多处理而不是常规的 for 循环,如下所示:

      from PIL import Image
      import os
      
      def crop(infile,height,width):
          im = Image.open(infile)
          imgwidth, imgheight = im.size
          for i in range(imgheight//height):
              for j in range(imgwidth//width):
                  box = (j*width, i*height, (j+1)*width, (i+1)*height)
                  yield im.crop(box)
      
      def til_image(infile):
          infile=...
          height=...
          width=...
          start_num=...
          for k,piece in enumerate(crop(infile,height,width),start_num):
              img=Image.new('RGB', (height,width), 255)
              img.paste(piece)
              path=os.path.join('/tmp',"IMG-%s.png" % k)
              img.save(path)
      
      from multiprocessing import Pool, cpu_count
      try:
          pool = Pool(cpu_count())
          pool.imap_unordered(tile_image, os.listdir(root), chunksize=4)
      finally:
          pool.close()
      

      【讨论】:

        【解决方案6】:
        import os
        import sys
        from PIL import Image
        
        savedir = r"E:\new_mission _data\test"
        filename = r"E:\new_mission _data\test\testing1.png"
        img = Image.open(filename)
        width, height = img.size
        start_pos = start_x, start_y = (0, 0)
        cropped_image_size = w, h = (1024,1024)
        
        frame_num = 1
        for col_i in range(0, width, w):
            for row_i in range(0, height, h):
                crop = img.crop((col_i, row_i, col_i + w, row_i + h))
                save_to= os.path.join(savedir, "testing_{:02}.png")
                crop.save(save_to.format(frame_num))
                frame_num += 1
        

        【讨论】:

        • 此代码适用于我,因为您可以随意更改cropped_image_size
        • 这段代码可以将一张大图裁剪成多张小图
        【解决方案7】:

        这是适用于 Python 3 的较晚答案

        from PIL import Image
        import os
        
        def imgcrop(input, xPieces, yPieces):
            filename, file_extension = os.path.splitext(input)
            im = Image.open(input)
            imgwidth, imgheight = im.size
            height = imgheight // yPieces
            width = imgwidth // xPieces
            for i in range(0, yPieces):
                for j in range(0, xPieces):
                    box = (j * width, i * height, (j + 1) * width, (i + 1) * height)
                    a = im.crop(box)
                    try:
                        a.save("images/" + filename + "-" + str(i) + "-" + str(j) + file_extension)
                    except:
                        pass
        

        用法:

        imgcrop("images/testing.jpg", 5, 5)
        

        然后图片会按照指定的X和Y块裁剪成块,我这里是5 x 5 = 25块

        【讨论】:

          【解决方案8】:
          import cv2
          
          def crop_image(image_path, output_path):
              im =  cv2.imread(os.listdir()[2])
              imgheight=im.shape[0]
              imgwidth=im.shape[1]
          
              y1 = 0
              M = 2000
              N = 2000
              for y in range(0,imgheight,M):
                  for x in range(0, imgwidth, N):
                      y1 = y + M
                      x1 = x + N
                      tiles = im[y:y+M,x:x+N]
                      if tiles.shape[0] < 100 or  tiles.shape[1]<100:
                          continue
          
                      cv2.rectangle(im, (x, y), (x1, y1), (0, 255, 0))
                      cv2.imwrite(output_path +  str(x) + '_' + str(y)+"{}.png".format(image_path),tiles)
          crop_image(os.listdir()[2], './cutted/')
          

          【讨论】:

            【解决方案9】:

            我尝试了上述解决方案,但有时您只能自己动手。 在某些情况下可能会偏离一个像素,但总体上可以正常工作。

            import matplotlib.pyplot as plt
            import numpy as np
            def image_to_tiles(im, number_of_tiles = 4, plot=False):
                """
                Function that splits SINGLE channel images into tiles
                :param im: image: single channel image (NxN matrix)
                :param number_of_tiles: squared number
                :param plot:
                :return tiles:
                """
                n_slices = np.sqrt(number_of_tiles)
                assert int(n_slices + 0.5) ** 2 == number_of_tiles, "Number of tiles is not a perfect square"
            
                n_slices = n_slices.astype(np.int)
                [w, h] = cropped_npy.shape
            
                r = np.linspace(0, w, n_slices+1)
                r_tuples = [(np.int(r[i]), np.int(r[i+1])) for i in range(0, len(r)-1)]
                q = np.linspace(0, h, n_slices+1)
                q_tuples = [(np.int(q[i]), np.int(q[i+1])) for i in range(0, len(q)-1)]
            
                tiles = []
                for row in range(n_slices):
                    for column in range(n_slices):
                        [x1, y1, x2, y2] = *r_tuples[row], *q_tuples[column] 
                        tiles.append(im[x1:y1, x2:y2])
            
                if plot:
                    fig, axes = plt.subplots(n_slices, n_slices, figsize=(10,10))
                    c = 0
                    for row in range(n_slices):
                        for column in range(n_slices):
                            axes[row,column].imshow(tiles[c])
                            axes[row,column].axis('off')
                            c+=1
            
                return tiles
            

            希望对你有帮助。

            【讨论】:

              【解决方案10】:

              不确定这是否是最有效的答案,但它对我有用:

              import os
              import glob
              from PIL import Image
              Image.MAX_IMAGE_PIXELS = None # to avoid image size warning
              
              imgdir = "/path/to/image/folder"
              # if you want file of a specific extension (.png):
              filelist = [f for f in glob.glob(imgdir + "**/*.png", recursive=True)]
              savedir = "/path/to/image/folder/output"
              
              start_pos = start_x, start_y = (0, 0)
              cropped_image_size = w, h = (500, 500)
              
              for file in filelist:
                  img = Image.open(file)
                  width, height = img.size
              
                  frame_num = 1
                  for col_i in range(0, width, w):
                      for row_i in range(0, height, h):
                          crop = img.crop((col_i, row_i, col_i + w, row_i + h))
                          name = os.path.basename(file)
                          name = os.path.splitext(name)[0]
                          save_to= os.path.join(savedir, name+"_{:03}.png")
                          crop.save(save_to.format(frame_num))
                          frame_num += 1
              

              这主要基于 DataScienceGuy 回答 here

              【讨论】:

                【解决方案11】:

                这是一个简洁的纯 python 解决方案,适用于 python 3 和 2:

                from PIL import Image
                
                infile = '20190206-135938.1273.Easy8thRunnersHopefully.jpg'
                chopsize = 300
                
                img = Image.open(infile)
                width, height = img.size
                
                # Save Chops of original image
                for x0 in range(0, width, chopsize):
                   for y0 in range(0, height, chopsize):
                      box = (x0, y0,
                             x0+chopsize if x0+chopsize <  width else  width - 1,
                             y0+chopsize if y0+chopsize < height else height - 1)
                      print('%s %s' % (infile, box))
                      img.crop(box).save('zchop.%s.x%03d.y%03d.jpg' % (infile.replace('.jpg',''), x0, y0))
                

                注意事项:

              • 超出原始图像右侧和底部的裁剪被调整为原始图像限制,并且仅包含原始像素。
              • 通过在上面的代码中使用两个chopsize 变量并根据需要替换chopsize,可以很容易地为w 和h 选择不同的chopsize。
              • 【讨论】:

                • 仅适用于 png,不适用于目录中的文件,但这非常有帮助,谢谢!
                • 用 jpg's 对我来说是一种享受。谢谢。
                【解决方案12】:

                编辑:我相信这个答案错过了将图像切割成列和行的矩形的意图。这个答案只分成几行。它看起来像其他答案在列和行中切割。

                使用别人发明的轮子比所有这些都简单 :) 设置可能更复杂,但使用起来很容易。

                这些说明适用于 Windows 7;它们可能需要适应其他操作系统。

                here 获取并安装 pip。

                下载安装存档,并将其解压缩到您的 Python 安装根目录。打开控制台并输入(如果我没记错的话):

                python get-pip.py install
                

                然后通过 pip 获取并安装 image_slicer 模块,在控制台输入以下命令:

                python -m pip install image_slicer
                

                将要切片的图像复制到 Python 根目录,打开 Python shell(不是“命令行”),然后输入以下命令:

                import image_slicer
                image_slicer.slice('huge_test_image.png', 14)
                

                这个模块的美妙之处在于它

                1. 安装在python中
                2. 可以用两行代码调用图像分割
                3. 接受任何偶数作为图像切片参数(例如本例中的 14)
                4. 获取该参数并自动将给定图像拆分为多个切片,并将生成的编号切片自动保存在同一目录中,最后
                5. 具有将图像拼贴重新拼接在一起的功能(我尚未测试);文件显然必须按照您将在测试 image_slicer.slice 函数后在拆分文件中看到的约定命名。

                【讨论】:

                • 它看起来不错,但它的文档很差。它还可以在创建切片后很好地控制切片,但要查看如何切片图像并不容易。我期待一种元组来设置行数和列数
                • Per cmets on other answers,不,这可能不是内存受限系统的选项。
                【解决方案13】:
                from PIL import Image
                
                def crop(path, input, height, width, k, page, area):
                    im = Image.open(input)
                    imgwidth, imgheight = im.size
                    for i in range(0,imgheight,height):
                        for j in range(0,imgwidth,width):
                            box = (j, i, j+width, i+height)
                            a = im.crop(box)
                            try:
                                o = a.crop(area)
                                o.save(os.path.join(path,"PNG","%s" % page,"IMG-%s.png" % k))
                            except:
                                pass
                            k +=1
                

                【讨论】:

                • 为什么k是这个函数的参数?调用函数时不应该总是0吗?还有什么是area?为什么你crop图像两次?
                • 你所有的论点是什么意思?
                【解决方案14】:

                我发现skimage.util.view_as_windows 或 `skimage.util.view_as_blocks 更容易,它还允许您配置步骤

                http://scikit-image.org/docs/dev/api/skimage.util.html?highlight=view_as_windows#skimage.util.view_as_windows

                【讨论】:

                  【解决方案15】:

                  这是我的脚本工具,将 css-sprit 图像拆分为图标非常示例:

                  Usage: split_icons.py img dst_path width height
                  Example: python split_icons.py icon-48.png gtliu 48 48
                  

                  将代码保存到 split_icons.py 中:

                  #!/usr/bin/env python
                  # -*- coding:utf-8 -*-
                  import os
                  import sys
                  import glob
                  from PIL import Image
                  
                  def Usage():
                      print '%s img dst_path width height' % (sys.argv[0])
                      sys.exit(1)
                  
                  if len(sys.argv) != 5:
                      Usage()
                  
                  src_img = sys.argv[1]
                  dst_path = sys.argv[2]
                  
                  if not os.path.exists(sys.argv[2]) or not os.path.isfile(sys.argv[1]):
                      print 'Not exists', sys.argv[2], sys.argv[1]
                      sys.exit(1)
                  
                  w, h = int(sys.argv[3]), int(sys.argv[4])
                  im = Image.open(src_img)
                  im_w, im_h = im.size
                  print 'Image width:%d height:%d  will split into (%d %d) ' % (im_w, im_h, w, h)
                  w_num, h_num = int(im_w/w), int(im_h/h)
                  
                  for wi in range(0, w_num):
                      for hi in range(0, h_num):
                          box = (wi*w, hi*h, (wi+1)*w, (hi+1)*h)
                          piece = im.crop(box)
                          tmp_img = Image.new('L', (w, h), 255)
                          tmp_img.paste(piece)
                          img_path = os.path.join(dst_path, "%d_%d.png" % (wi, hi))
                          tmp_img.save(img_path)
                  

                  【讨论】:

                    【解决方案16】:

                    将图像分割成 MxN 像素的图块(假设 im 是 numpy.ndarray):

                    tiles = [im[x:x+M,y:y+N] for x in range(0,im.shape[0],M) for y in range(0,im.shape[1],N)]
                    

                    如果您想将图像分成四块:

                    M = im.shape[0]//2
                    N = im.shape[1]//2
                    

                    tiles[0] 持有左上角的 tile

                    【讨论】:

                    • 对我来说是最简单的解决方案,计算成本也不高
                    • 如果 M 和 N 设置不正确,生成的图块可能会超出图像大小。
                    【解决方案17】:
                    1. crop 会更可重用 功能,如果你分开 裁剪代码 图像保存 代码。它也会打电话 签名更简单。
                    2. im.crop 返回一个 Image._ImageCrop 实例。这样的 实例没有保存方法。 相反,您必须粘贴 Image._ImageCrop 实例到一个 新Image.Image
                    3. 您的范围没有权限 步长。 (为什么height-2 而不是 height?例如。为什么停在 imgheight-(height/2)?)。

                    所以,你可以试试这样的:

                    import Image
                    import os
                    
                    def crop(infile,height,width):
                        im = Image.open(infile)
                        imgwidth, imgheight = im.size
                        for i in range(imgheight//height):
                            for j in range(imgwidth//width):
                                box = (j*width, i*height, (j+1)*width, (i+1)*height)
                                yield im.crop(box)
                    
                    if __name__=='__main__':
                        infile=...
                        height=...
                        width=...
                        start_num=...
                        for k,piece in enumerate(crop(infile,height,width),start_num):
                            img=Image.new('RGB', (height,width), 255)
                            img.paste(piece)
                            path=os.path.join('/tmp',"IMG-%s.png" % k)
                            img.save(path)
                    

                    【讨论】:

                    • 感谢您的解决方案,但它不适用于我,图片没有裁剪好,我看到红色,我认为问题可能在这里:img.paste(piece)
                    • 如果您有内存限制,这是一个特别好的解决方案。使用 image_slicer 时,大图像可能会在内存不足的机器上失败。
                    • @Elteroooo 得到的行为是因为代码有错误,在第 18 行:img=Image.new('RGB', (height,width), 255)widthheight 应该已经切换。我建议编辑,但被拒绝了¯_(ツ)_/¯
                    猜你喜欢
                    • 2011-08-26
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2010-12-05
                    • 1970-01-01
                    • 2018-07-23
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多