PIL создавать пустые изображения при кадрировании - PullRequest
0 голосов
/ 18 июня 2019

Я работаю с файлами изображений размером ~ 50 МБ (~ 19000 пикселей x 25500 пикселей) и обрезаю их в изображения размером 4705 пикселей x 8375 пикселей. Я написал цикл for, который просматривает папку из 95 изображений. По большей части обрезка работает нормально, но на случайных изображениях, когда код обрезает изображение, его подизображение получается в виде пустого изображения. Когда это произойдет, первое из 12 изображений получится нормальным (обрезано правильно), а остальные 11 изображений получатся пустыми. Если проблема не возникает, все 12 изображений получаются обрезанными правильно.

Я запускаю код на Spyder 3.3.4 на MBP 10.14.5. 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)

Опять же, я ожидаю, что изображения будут частичными изображениями, а не пустыми изображениями. Не уверен, что это проблема с памятью или что-то еще, не связанное с кодом.

1 Ответ

1 голос
/ 18 июня 2019

Трудно сказать, глядя на программу - это сработало бы, если бы каждое изображение было таким, как вы описали - однако ваш код для именования целевых изображений не используются шаблоны программирования, защищенные от ошибок, поскольку они не используют все преимущества некоторых языковых возможностей. Этот код работает сейчас, но, возможно, были некоторые проб и ошибок, чтобы добраться туда. Итак, моя ставка в том, что в какой-то момент времени неверная версия этот скрипт был запущен, и при генерации целевые файлы срезов. Этот прогон переписал некоторые изображения, которые сейчас размер одного среза.

На самом деле, если вызывать метод crop, превышающий размеры пикселей изображения для объекта изображения PIL, ошибка не возникает: вместо этого создается бесшумное (черное) изображение.

Вы не упомянули, но если вы проверите изображения, для которых нарезка в противном случае, дело в том, что ваши оригиналы, вероятно, уже обрезаны до меньшего размера .

Кроме того, поскольку нет проверок того, какие изображения вы обрезаете, если вы запустите этот код более одного раза, уже сохраненные кадрирования будут обрабатываться, как если бы они снова были большими изображениями.

То есть, при первом запуске этого скрипта "image.jpg" будет сохранен и обрезан до "image1.jpg" через "image12.jpg" - но во втором запуске, каждый из этих "imageN. jpg "станет" imageNM.jpg "- с" M "снова с" 1 "на" 12 ". Кроме того, 11-е и 12-е изображения первого прогона «image11.jpg» и «image12.jpg» будут заменены на первый и второй выходные данные второго прогона.

Таким образом, если вы все еще можете восстановить исходные папки с изображениями размером 25500 x 19000 пикселей и только в тех из них, можно запустить измененную версию этого кода, что позволит избежать повторной обработки уже созданных фрагментов. Одной проверки ширины изображения можно избежать, и более точная схема именования также может быть лучше.

Также, как некоторые советы по кодированию:

  • использовать "f-строки" Python для манипулирования именами,
  • Используйте Python pathlib.Path для манипулирования именами папок и получения файлов изображений (это новинка из Python 3.5, и есть редкие примеры),
  • избегайте жестко закодированных чисел по всему коду - просто поместите их в начало списка, как переменные
  • Используйте явные итерации по 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)
...