【问题标题】:Automatically cropping an image with python/PIL使用 python/PIL 自动裁剪图像
【发布时间】:2012-12-22 02:10:42
【问题描述】:

谁能帮我弄清楚我的图像自动裁剪脚本中发生了什么?我有一个具有大透明区域/空间的 png 图像。我希望能够自动裁剪该空间并留下必需品。原始图像有一个方形画布,最好是矩形,只封装分子。

这是原始图像:

通过谷歌搜索,我发现了 PIL/python 代码,据报道该代码可以工作,但是在我手中,运行下面的代码会过度裁剪图像。

import Image
import sys

image=Image.open('L_2d.png')
image.load()

imageSize = image.size
imageBox = image.getbbox()

imageComponents = image.split()

rgbImage = Image.new("RGB", imageSize, (0,0,0))
rgbImage.paste(image, mask=imageComponents[3])
croppedBox = rgbImage.getbbox()
print imageBox
print croppedBox
if imageBox != croppedBox:
    cropped=image.crop(croppedBox)
    print 'L_2d.png:', "Size:", imageSize, "New Size:",croppedBox
    cropped.save('L_2d_cropped.png')

输出是这样的:

任何更熟悉图像处理/PLI 的人都可以帮我解决这个问题吗?

【问题讨论】:

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


    【解决方案1】:

    最近看到这篇文章,发现 PIL 库发生了变化。我用 openCV 重新实现了这个:

    import cv2
    
    def crop_im(im, padding=0.1):
        """
        Takes cv2 image, im, and padding % as a float, padding,
        and returns cropped image.
        """
        bw = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
        rows, cols = bw.shape
        non_empty_columns = np.where(bw.min(axis=0)<255)[0]
        non_empty_rows = np.where(bw.min(axis=1)<255)[0]
        cropBox = (int(min(non_empty_rows) * (1 - padding)),
                    int(min(max(non_empty_rows) * (1 + padding), rows)),
                    int(min(non_empty_columns) * (1 - padding)),
                    int(min(max(non_empty_columns) * (1 + padding), cols)))
        cropped = im[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]
    
        return cropped
    
    im = cv2.imread('testimage.png')
    cropped = crop_im(im)
    cv2.imshow('', cropped)
    cv2.waitKey(0)
    

    【讨论】:

    • TypeError: 切片索引必须是整数或 None 或具有 index 方法
    • 如果您更改裁剪框中的变量类型,此解决方案效果很好。目前它们是浮点数,而它们应该是整数。这给出了@Aqua4 得到的错误。
    • 类型转换为 int,现在应该修复错误。
    【解决方案2】:

    这是另一个使用 pyvips 的版本。

    import sys
    import pyvips
    
    image = pyvips.Image.new_from_file(sys.argv[1])
    left, top, width, height = image.find_trim(threshold=2, background=[255, 255, 255])
    image = image.crop(left, top, width, height)
    image.write_to_file(sys.argv[2])
    

    pyvips 修剪器对摄影图像很有用。它执行中值过滤,减去背景,找到超过阈值的像素,并删除该集合之外的第一行和最后一行和列。中值和阈值意味着它不会被 JPEG 压缩之类的东西所抛弃,因为噪声或不可见的压缩伪影会混淆其他修剪器。

    如果您不提供 background 参数,它将使用 (0, 0) 处的像素。 threshold 默认为 10,这对于 JPEG 来说差不多。

    它在8k x 8k pixel NASA earth image 上运行:

    $ time ./trim.py /data/john/pics/city_lights_asia_night_8k.jpg x.jpg
    real    0m1.868s
    user    0m13.204s
    sys     0m0.280s
    peak memory: 100mb
    

    之前:

    之后:

    有一个blog post with some more discussion here

    【讨论】:

      【解决方案3】:

      pilkit 已经包含用于自动裁剪的处理器TrimBorderColor。像这样的东西应该可以工作:

      from pilkit.lib import Image
      from pilkit.processors import TrimBorderColor
      
      img = Image.open('/path/to/my/image.png')
      processor = TrimBorderColor()
      new_img = processor.process(img)
      

      https://github.com/matthewwithanm/pilkit/blob/b24990167aacbaab3db6d8ec9a02f9ad42856898/pilkit/processors/crop.py#L33

      【讨论】:

        【解决方案4】:

        我测试了这篇文章中回答的大部分答案,但是,我最终得到了自己的答案。我使用了蟒蛇python3。

        from PIL import Image, ImageChops
        
        def trim(im):
            bg = Image.new(im.mode, im.size, im.getpixel((0,0)))
            diff = ImageChops.difference(im, bg)
            diff = ImageChops.add(diff, diff, 2.0, -100)
            #Bounding box given as a 4-tuple defining the left, upper, right, and lower pixel coordinates.
            #If the image is completely empty, this method returns None.
            bbox = diff.getbbox()
            if bbox:
                return im.crop(bbox)
        
        if __name__ == "__main__":
            bg = Image.open("test.jpg") # The image to be cropped
            new_im = trim(bg)
            new_im.show()
        

        【讨论】:

        • 此代码具有适用于任何颜色和 alpha 的巨大优势。
        • 我在处理低色图像时遇到了这个特定代码的问题,特别是那些使用单笔和单一画布颜色的图像。如果左上角的像素恰好是笔颜色,则它不会正确生成遮罩(您称其为 diff)。我通过用代表纸张颜色的元组替换 im.getpixel((0,0)) 解决了这个问题。
        • 我用了这个方法,但是发现当我的背景颜色是白色的时候,图片的内容是接近白色的,会被裁剪掉。我通过将-100 常量更改为0 来修复它。
        • 这个方法给了我与convert image.png -format "%@" info:完全相同的边界框值结果
        【解决方案5】:

        这是对 snew 回复的改进,它适用于透明背景。使用mathematical morphology,我们可以让它在白色背景(而不是透明)上工作,代码如下:

        from PIL import Image
        from skimage.io import imread
        from skimage.morphology import convex_hull_image
        im = imread('L_2d.jpg')
        plt.imshow(im)
        plt.title('input image')
        plt.show()
        # create a binary image
        im1 = 1 - rgb2gray(im)
        threshold = 0.5
        im1[im1 <= threshold] = 0
        im1[im1 > threshold] = 1
        chull = convex_hull_image(im1)
        plt.imshow(chull)
        plt.title('convex hull in the binary image')
        plt.show()
        imageBox = Image.fromarray((chull*255).astype(np.uint8)).getbbox()
        cropped = Image.fromarray(im).crop(imageBox)
        cropped.save('L_2d_cropped.jpg')
        plt.imshow(cropped)
        plt.show()
        

        【讨论】:

          【解决方案6】:

          对我来说,它的工作原理是:

          import Image
          
          image=Image.open('L_2d.png')
          
          imageBox = image.getbbox()
          cropped=image.crop(imageBox)
          cropped.save('L_2d_cropped.png')
          

          当您通过mask=imageComponents[3] 搜索边界时,您只搜索蓝色通道。

          【讨论】:

          • 点赞,不过,numpy-find-all-empty-cols-rows 方式更有趣。
          • 如果这不起作用,可能是因为图像的“空白”区域是不透明的白色 (255) 而不是透明 (0)。
          • 仅供参考,谁想知道:pip install pillow
          • 安装pillow后使用from PIL import Image
          【解决方案7】:

          您可以使用 numpy,将图像转换为数组,查找所有非空列和行,然后从这些创建图像:

          import Image
          import numpy as np
          
          image=Image.open('L_2d.png')
          image.load()
          
          image_data = np.asarray(image)
          image_data_bw = image_data.max(axis=2)
          non_empty_columns = np.where(image_data_bw.max(axis=0)>0)[0]
          non_empty_rows = np.where(image_data_bw.max(axis=1)>0)[0]
          cropBox = (min(non_empty_rows), max(non_empty_rows), min(non_empty_columns), max(non_empty_columns))
          
          image_data_new = image_data[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]
          
          new_image = Image.fromarray(image_data_new)
          new_image.save('L_2d_cropped.png')
          

          结果看起来像

          如果有什么不清楚的,尽管问。

          【讨论】:

          • (...)cropBox[2]:cropBox[3]+1 , :]
          • 如果将Image 导入为from PIL import Image(已为Python3 安装PILLOW),此方法适用于Python3。
          • 这适用于 RGB 和 RGBA 图像,但不适用于 P 模式图像.. 能否请您指教?
          • @user12345,我不确定您所说的 p 模式图像是什么意思。请解释。你有例子吗?
          • 在边缘情况下为我解决此问题的轻微修正:将 image_data_bw = image_data.max(axis=2) 更改为 image_data_bw = image_data.take(3, axis=2) 所以它实际上查看透明度值
          【解决方案8】:

          我知道这篇文章很旧,但由于某种原因,没有一个建议的答案对我有用。所以我从现有答案中破解了我自己的版本:

          import Image
          import numpy as np
          import glob
          import shutil
          import os
          
          grey_tolerance = 0.7 # (0,1) = crop (more,less)
          
          f = 'test_image.png'
          file,ext = os.path.splitext(f)
          
          def get_cropped_line(non_empty_elms,tolerance,S):
              if (sum(non_empty_elms) == 0):
                  cropBox = ()
              else:
                  non_empty_min = non_empty_elms.argmax()
                  non_empty_max = S - non_empty_elms[::-1].argmax()+1
                  cropBox = (non_empty_min,non_empty_max)
              return cropBox
          
          def get_cropped_area(image_bw,tol):
              max_val = image_bw.max()
              tolerance = max_val*tol
              non_empty_elms = (image_bw<=tolerance).astype(int)
              S = non_empty_elms.shape
              # Traverse rows
              cropBox = [get_cropped_line(non_empty_elms[k,:],tolerance,S[1]) for k in range(0,S[0])]
              cropBox = filter(None, cropBox)
              xmin = [k[0] for k in cropBox]
              xmax = [k[1] for k in cropBox]
              # Traverse cols
              cropBox = [get_cropped_line(non_empty_elms[:,k],tolerance,S[0]) for k in range(0,S[1])]
              cropBox = filter(None, cropBox)
              ymin = [k[0] for k in cropBox]
              ymax = [k[1] for k in cropBox]
              xmin = min(xmin)
              xmax = max(xmax)
              ymin = min(ymin)
              ymax = max(ymax)
              ymax = ymax-1 # Not sure why this is necessary, but it seems to be.
              cropBox = (ymin, ymax-ymin, xmin, xmax-xmin)
              return cropBox
          
          def auto_crop(f,ext):
              image=Image.open(f)
              image.load()
              image_data = np.asarray(image)
              image_data_bw = image_data[:,:,0]+image_data[:,:,1]+image_data[:,:,2]
              cropBox = get_cropped_area(image_data_bw,grey_tolerance)
              image_data_new = image_data[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]
              new_image = Image.fromarray(image_data_new)
              f_new = f.replace(ext,'')+'_cropped'+ext
              new_image.save(f_new)
          

          【讨论】:

            猜你喜欢
            • 2012-10-01
            • 1970-01-01
            • 1970-01-01
            • 2012-04-16
            • 1970-01-01
            • 2010-10-24
            • 2013-03-06
            • 2011-09-21
            相关资源
            最近更新 更多