首页 > 解决方案 > PIL 在裁剪时创建空白图像

问题描述

我正在处理约 50MB(约 19000 像素 x 25500 像素)的图像文件,并将它们裁剪成大小为 4705 像素 x 8375 像素的图像。我写了一个循环遍历包含 95 个图像的文件夹。在大多数情况下,裁剪效果很好,但是在随机图像上,当代码裁剪图像时,它的子图像会显示为空白图像。发生这种情况时,12 个图像中的第一个将正常显示(正确裁剪),但其余 11 个图像将显示为空白。当问题没有发生时,所有 12 张图像都会正确裁剪。

我在 MBP 10.14.5 上的 Spyder 3.3.4 上运行代码。PIL 是版本 1.1.7 和 Python 3.6。我检查了我在图像之间的循环是否正确。重新运行失败的图像(裁剪不正确),当我将它们裁剪成它们而不是 for 循环的一部分时,它们可以正常工作。

stepCounter = 4705

for folder in os.listdir(location):
    if folder == "MyFolder":
        for file in os.listdir(location+folder):
            resetCounter = -8375
            for i in range(12):
                print("Iteration", i, " on file", file)
                if i%4 == 0:
                    resetCounter += 8375
                    left = 0 
                    top = 0 + resetCounter
                    right = 4705
                    bottom = 8375 + resetCounter
                    fileLocation = location + folder + "/" + file
                    newLocation = location + folder + "/" + file[:-4] + str(i+1) + ".jpg"
                    img = Image.open(fileLocation)
                    img = img.crop((left, top, right, bottom))
                    img.save(newLocation)
                    img.close()
                else:
                    left = left + stepCounter
                    top = top 
                    right = right + stepCounter
                    bottom = bottom
                    fileLocation = location + folder + "/" + file
                    newLocation = location + folder + "/" + file[:-4] + str(i+1) + ".jpg"
                    img = Image.open(fileLocation)
                    img = img.crop((left, top, right, bottom))
                    img.save(newLocation)
                    img.close()
    else:
        print("Skipping", folder)

同样,我希望图像是较大图像的子图像,而不是空白图像。不确定这是内存问题,还是其他与代码无关的问题。

标签: pythonpython-imaging-librarycrop

解决方案


通过查看程序很难判断 - 如果每个图像都像您描述的那样,它会起作用 - 但是,您命名目标图像的代码没有使用防错的编程模式,因为它们没有充分利用一些语言设施。该代码现在可以工作,但可能需要一些试验和错误才能到达那里。所以,我敢打赌,在某个时间点,运行了此脚本的不正确版本,在生成目标切片文件时行为不正确。这次运行确实覆盖了一些图像,这些图像现在是单个切片的大小。

实际上,如果crop在 PIL 图像对象上调用超出图像像素大小的方法,则不会引发错误:而是静默创建零(黑色)图像。

您没有提到,但是如果您现在检查切片失败的图像,情况是您的原件可能已经被裁剪为较小的尺寸

此外,由于没有检查您正在裁剪哪些图像,如果您多次运行此代码,已保存的裁剪将再次被处理为大图像。

也就是说,在此脚本的第一次运行时,“image.jpg”将被保存并裁剪为“image1.jpg”到“image12.jpg” - 但在第二次运行时,这些“imageN.jpg”中的每一个都会变成“imageNM.jpg”——“M”再次从“1”变为“12”。此外,第一次运行的第 11 和第 12 图像“image11.jpg”和“image12.jpg”将被第二次运行的第一个和第二个输出替换。

因此,如果您仍然可以使用正好为 25500 x 19000 像素的图像恢复原始文件夹,并且只有这些图像,则可以运行此代码的重构版本,这将确保不会重新处理已经制作的切片。对图像宽度进行一次检查可以避免这种情况,更明确的命名模式也可能会更好。

此外,作为一些编码建议:

  • 利用 Python 的“f-strings”来操作名称,
  • 使用 Python pathlib.Path来操作文件夹名称并获取图像文件(这是 Python 3.5 的新功能,并且周围有很少的示例),
  • 避免在代码周围硬编码数字 - 只需将 then 放在列表的开头,作为变量
  • 在 x 和 y 上使用显式迭代,而不是使用线性计数器,然后使用一些容易出错的算法来达到要裁剪的限制
  • 最后,正如我上面提到的,注意不要多次重复阅读相同的图像,脚本会变得更加容易,并且不容易出错。

由于图像尺寸较大,并且从第二次开始无法加载大图像,因此您确实有可能确实遇到了 PIL 中的错误-但这不太可能。与此有关的问题宁愿以 MemoryError 停止程序。

import pathlib
from PIL import Image

# Whatever code you have to get the "location" variable
...

x_step = 8375
y_step = 4705

full_width = 25500

for image_path in pathlib.Path(location).glob("**/*.jpg"):
    # the Path.glob method automatically iterates in subfolders, for
    # all files matching the expressions
    if "MyFolder" not in image_path.parts:
        # Skips processing if "MyFolder" not in the relative path to the image file
        continue
    # Loads the original image a single time:
    img = Image.open(image_path)
    if img.width < full_width:
        if "crop" not in image_path.name:
            # Do not print warnings for slices - just skip then
            print(f"Image at {image_path} has width of only {img.width}. Skipping")
        continue
    for y in range(3):
        for x in range(4):
            print(f"Iteration {y * 4 + x} on file {image_path.name}")
            # Store the cropped image object into a new variable - the original image is kept on "img"
            left = x_step * x
            top = y_step * y
            sliced_img = img.crop((left, top, left + x_step, top + y_step))
            new_path = image_path.with_name(f"{image_path.stem}_crop_{y * 4 + x + 1}{image_path.suffix}")
            sliced_img.save(new_path)

推荐阅读