Мне показалось, что я повеселился, поэтому провел несколько сравнительных тестов по различным методам, предложенным выше, и несколько собственных идей.
Я собрал 1000 изображений iPhone 6S с высоким разрешением 12MP, каждое размером 4032x3024 пикселей, и использую 8-ядерный iMac.
Вот методы и результаты - каждый в своем собственном разделе.
Метод 1 - Последовательный ImageMagick
Это упрощенный, неоптимизированный код. Каждое изображение читается и создается миниатюра. Затем он снова читается и создается миниатюра другого размера.
#!/bin/bash
start=$SECONDS
# Loop over all files
for f in image*.jpg; do
# Loop over all sizes
for s in 1600 720 120; do
echo Reducing $f to ${s}x${s}
convert "$f" -resize ${s}x${s} t-$f-$s.jpg
done
done
echo Time: $((SECONDS-start))
Результат: 170 секунд
Метод 2 - Последовательный ImageMagick с одной загрузкой и последовательным изменением размера
Это все еще последовательно, но немного умнее. Каждое изображение читается только один раз, а затем загруженное изображение уменьшается в три раза и сохраняется с тремя разрешениями. Улучшение в том, что каждое изображение читается только один раз, а не 3 раза.
#!/bin/bash
start=$SECONDS
# Loop over all files
N=1
for f in image*.jpg; do
echo Resizing $f
# Load once and successively scale down
convert "$f" \
-resize 1600x1600 -write t-$N-1600.jpg \
-resize 720x720 -write t-$N-720.jpg \
-resize 120x120 t-$N-120.jpg
((N=N+1))
done
echo Time: $((SECONDS-start))
Результат: 76 секунд
Метод 3 - GNU Parallel + ImageMagick
Это основано на предыдущем методе, используя GNU Parallel для параллельной обработки N
изображений, где N
- количество ядер ЦП на вашем компьютере.
#!/bin/bash
start=$SECONDS
doit() {
file=$1
index=$2
convert "$file" \
-resize 1600x1600 -write t-$index-1600.jpg \
-resize 720x720 -write t-$index-720.jpg \
-resize 120x120 t-$index-120.jpg
}
# Export doit() to subshells for GNU Parallel
export -f doit
# Use GNU Parallel to do them all in parallel
parallel doit {} {#} ::: *.jpg
echo Time: $((SECONDS-start))
Результат: 18 секунд
Метод 4 - GNU Parallel + vips
Это то же самое, что и предыдущий метод, но он использует vips
в командной строке вместо ImageMagick .
#!/bin/bash
start=$SECONDS
doit() {
file=$1
index=$2
r0=t-$index-1600.jpg
r1=t-$index-720.jpg
r2=t-$index-120.jpg
vipsthumbnail "$file" -s 1600 -o "$r0"
vipsthumbnail "$r0" -s 720 -o "$r1"
vipsthumbnail "$r1" -s 120 -o "$r2"
}
# Export doit() to subshells for GNU Parallel
export -f doit
# Use GNU Parallel to do them all in parallel
parallel doit {} {#} ::: *.jpg
echo Time: $((SECONDS-start))
Результат: 8 секунд
Метод 5 - Последовательный PIL
Это должно соответствовать ответу Якоба.
#!/usr/local/bin/python3
import glob
from PIL import Image
sizes = [(120,120), (720,720), (1600,1600)]
files = glob.glob('image*.jpg')
N=0
for image in files:
for size in sizes:
im=Image.open(image)
im.thumbnail(size)
im.save("t-%d-%s.jpg" % (N,size[0]))
N=N+1
Результат: 38 секунд
Метод 6 - Последовательный PIL с одиночной нагрузкой и последовательным изменением размера
Это является улучшением ответа Якоба, в котором изображение загружается только один раз, а затем уменьшается в размере три раза вместо повторной загрузки каждый раз для получения каждого нового разрешения.
#!/usr/local/bin/python3
import glob
from PIL import Image
sizes = [(120,120), (720,720), (1600,1600)]
files = glob.glob('image*.jpg')
N=0
for image in files:
# Load just once, then successively scale down
im=Image.open(image)
im.thumbnail((1600,1600))
im.save("t-%d-1600.jpg" % (N))
im.thumbnail((720,720))
im.save("t-%d-720.jpg" % (N))
im.thumbnail((120,120))
im.save("t-%d-120.jpg" % (N))
N=N+1
Результат: 27 секунд
Метод 7 - Параллельный PIL
Это должно соответствовать ответу Audionautics, поскольку он использует многопроцессорность Python. Это также устраняет необходимость повторной загрузки изображения для каждого размера миниатюры.
#!/usr/local/bin/python3
import glob
from PIL import Image
from multiprocessing import Pool
def thumbnail(params):
filename, N = params
try:
# Load just once, then successively scale down
im=Image.open(filename)
im.thumbnail((1600,1600))
im.save("t-%d-1600.jpg" % (N))
im.thumbnail((720,720))
im.save("t-%d-720.jpg" % (N))
im.thumbnail((120,120))
im.save("t-%d-120.jpg" % (N))
return 'OK'
except Exception as e:
return e
files = glob.glob('image*.jpg')
pool = Pool(8)
results = pool.map(thumbnail, zip(files,range((len(files)))))
Результат: 6 секунд
Метод 8 - Параллельное OpenCV
Это должно улучшить ответ bcattle, поскольку он использует OpenCV, но также устраняет необходимость перезагрузки изображения для генерации каждого нового выходного разрешения.
#!/usr/local/bin/python3
import cv2
import glob
from multiprocessing import Pool
def thumbnail(params):
filename, N = params
try:
# Load just once, then successively scale down
im = cv2.imread(filename)
im = cv2.resize(im, (1600,1600))
cv2.imwrite("t-%d-1600.jpg" % N, im)
im = cv2.resize(im, (720,720))
cv2.imwrite("t-%d-720.jpg" % N, im)
im = cv2.resize(im, (120,120))
cv2.imwrite("t-%d-120.jpg" % N, im)
return 'OK'
except Exception as e:
return e
files = glob.glob('image*.jpg')
pool = Pool(8)
results = pool.map(thumbnail, zip(files,range((len(files)))))
Результат: 5 секунд