Самый быстрый способ создать несколько миниатюр из одного большого изображения в Python

У меня есть библиотека больших изображений (8000x6000 пикселей ~ 13 МБ), для которых я хотел бы создать несколько миниатюр меньшего размера с шириной 3000 пикселей, 2000 пикселей, 1000 пикселей, 500 пикселей, 250 пикселей и 100 пикселей.

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

Я думал об оптимальном способе сделать это в Python, и вот возможные проблемы, которые сразу приходят на ум:

  • Имеет ли смысл генерировать каждую миниатюру из исходного изображения, или я могу создавать миниатюры меньшего размера из любой миниатюры, которая немного больше? Например, 8000 пикселей -> 3000 пикселей, 3000 пикселей -> 2000 пикселей, 1000 пикселей -> 500 пикселей и т. д. Не будет ли это работать намного быстрее?
  • Имеет ли смысл загружать исходное изображение в память перед созданием эскизов?
  • Должен ли я использовать ImageMagick? Из командной строки или через API?
  • Есть ли способ подключиться к GPU?
  • Имеет ли смысл несколько потоков в этом случае?

Есть ли другие вещи, о которых следует помнить при оптимизации создания эскизов? Пример кода приветствуется для начала работы. Спасибо.


person ensnare    schedule 19.05.2014    source источник
comment
Что вы подразумеваете под изображение хранится в плоском файле? Вы имеете в виду JPEG или TIFF или что-то в этом роде? А какой будет неровный файл?   -  person Mark Setchell    schedule 22.08.2015
comment
Поздно отвечать на этот вопрос, но «плоский файл» обычно относится к файлу файловой системы ОС, а не к хранению в структурированном хранилище, таком как база данных и т. Д.   -  person Webreaper    schedule 20.06.2018


Ответы (1)


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

Я сделал изображения, содержащие случайные, трудно сжимаемые данные с размерами и размерами файлов, которые соответствуют вашим, т.е.

convert -size 8000x6000 xc:gray +noise random -quality 35 image.jpg

затем ls дает 13 МБ следующим образом:

-rw-r--r--  1 mark  staff    13M 23 Aug 17:55 image.jpg

Я сделал 128 таких случайных изображений, потому что это хорошо кратно 8 ядрам процессора на моей машине — см. параллельные тесты позже.

Теперь о методах...

Способ 1

Это наивный метод — вы просто создаете все файлы, которые вы просили, один за другим.

#!/bin/bash
for f in image*jpg; do
   for w in 3000 2000 1000 500 250 100; do
      convert $f -resize ${w}x res_${f}_${w}.jpg
   done 
done

Время: 26 минут 46 секунд

Метод 2

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

#!/bin/bash
for f in image*jpg; do
   convert $f -resize 3000x -write res_${f}_3000.jpg \
              -resize 2000x -write res_${f}_2000.jpg \
              -resize 1000x -write res_${f}_1000.jpg \
              -resize 500x  -write res_${f}_500.jpg  \
              -resize 250x  -write res_${f}_250.jpg  \
              -resize 100x  res_${f}_100.jpg
done

Время: 6 мин. 17 сек.

Способ 3

Здесь мы заранее сообщаем ImageMagick, что самое большое изображение, которое нам понадобится, составляет всего 3000x2250 пикселей, поэтому оно может использовать меньше памяти, считывать меньше уровней DCT и выполнять меньше операций ввода-вывода. Это называется «усадка при загрузке».

#!/bin/bash
for f in image*jpg; do
   convert -define jpeg:size=3000x2250 $f            \
              -resize 3000x -write res_${f}_3000.jpg \
              -resize 2000x -write res_${f}_2000.jpg \
              -resize 1000x -write res_${f}_1000.jpg \
              -resize 500x  -write res_${f}_500.jpg  \
              -resize 250x  -write res_${f}_250.jpg  \
              -resize 100x  res_${f}_100.jpg
done

Время: 3 мин. 37 сек.

Кроме того, чтобы продемонстрировать сокращение времени, ввода-вывода и памяти, необходимых, когда вы заранее сообщаете ImageMagick, насколько большой вам понадобится образ, сравните эти две команды, обе из которых считывают один из ваших 8000x6000, 13 МБ. изображения, и оба генерируют одну и ту же миниатюру:

/usr/bin/time -l convert image.jpg -resize 500x result.jpg 2>&1 | egrep "resident|real"        
1.92 real         1.77 user         0.14 sys
415727616  maximum resident set size

т.е. 415 МБ и 2 секунды

/usr/bin/time -l convert -define jpeg:size=500x500 image.jpg -resize 500x result.jpg 2>&1 | egrep "resident|real"

0.24 real         0.23 user         0.01 sys
23592960  maximum resident set size

т.е. 23 МБ и 0,2 секунды - и выходное изображение имеет такое же содержание и качество.

Метод 4

Здесь мы делаем все возможное и используем GNU Parallel, а также все вышеперечисленные методы, чтобы свести ваш процессор, вентиляторы и энергопотребление с ума!!!

#!/bin/bash
for f in image*jpg; do
   cat<<EOF
convert -define jpeg:size=3000x2250 $f          \
              -resize 3000x -write res_${f}_3000.jpg \
              -resize 2000x -write res_${f}_2000.jpg \
              -resize 1000x -write res_${f}_1000.jpg \
              -resize 500x  -write res_${f}_500.jpg  \
              -resize 250x  -write res_${f}_250.jpg  \
              -resize 100x  res_${f}_100.jpg
EOF
done | parallel

Время: 56 секунд

Таким образом, мы можем сократить время обработки с 27 минут до 56 секунд, избегая ненужного чтения изображения и выполняя как можно больше выходных данных для каждого ввода, заранее сообщая ImageMagick, какую часть входного изображения ему нужно прочитать, и используя GNU. Параллельно, чтобы все ваши любимые ядра процессора были заняты. ХТН.

person Mark Setchell    schedule 23.08.2015
comment
Спасибо за это. Совершенно потрясающий ответ и отличная детализация. - person Webreaper; 20.06.2018