Каков самый быстрый способ создания эскизов изображений в Python?
Я создаю фотогалерею на Python и хочу иметь возможность быстро создавать эскизы для изображений с высоким разрешением.
Какой самый быстрый способ создания высококачественных миниатюр для различных источников изображений?
Должен ли я использовать внешнюю библиотеку, например imagemagick, или есть эффективный внутренний способ сделать это?
Размеры измененных изображений будут (максимальный размер):
120x120
720x720
1600x1600
Качество - это проблема, так как я хочу сохранить как можно больше оригинальных цветов и свести к минимуму артефакты сжатия.
Спасибо.
Ответы
Ответ 1
Вы хотите PIL, он делает это с легкостью
from PIL import Image
sizes = [(120,120), (720,720), (1600,1600)]
files = ['a.jpg','b.jpg','c.jpg']
for image in files:
for size in sizes:
im = Image.open(image)
im.thumbnail(size)
im.save("thumbnail_%s_%s" % (image, "_".join(size)))
Если вам отчаянно нужна скорость. Тогда проденьте это, многопроцессорный или получите другой язык.
Ответ 2
Мне показалось, что я повеселился, поэтому я провел несколько сравнительных тестов на различные методы, предложенные выше, и несколько собственных идей.
Я собрал вместе 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 секунд
Ответ 3
Немного опоздал на вопрос (всего год!), Но я вернусь к ответу "многопроцессорность" в ответе @JakobBowyer.
Это хороший пример смущающей параллельной проблемы, так как основной бит кода не изменяет никакое внешнее по отношению к себе состояние. Он просто читает входные данные, выполняет их вычисления и сохраняет результат.
Python действительно неплохо справляется с такими проблемами благодаря функции map, предоставляемой multiprocessing.Pool
.
from PIL import Image
from multiprocessing import Pool
def thumbnail(image_details):
size, filename = image_details
try:
im = Image.open(filename)
im.thumbnail(size)
im.save("thumbnail_%s" % filename)
return 'OK'
except Exception as e:
return e
sizes = [(120,120), (720,720), (1600,1600)]
files = ['a.jpg','b.jpg','c.jpg']
pool = Pool(number_of_cores_to_use)
results = pool.map(thumbnail, zip(sizes, files))
Ядро кода точно такое же, как @JakobBowyer, но вместо того, чтобы запускать его в цикле в одном потоке, мы обернули его в функцию, распределив ее по нескольким ядрам с помощью функции многопроцессорной карты.
Ответ 4
Другим вариантом является использование привязок python к OpenCV. Это может быть быстрее, чем PIL или Imagemagick.
import cv2
sizes = [(120, 120), (720, 720), (1600, 1600)]
image = cv2.imread("input.jpg")
for size in sizes:
resized_image = cv2.resize(image, size)
cv2.imwrite("thumbnail_%d.jpg" % size[0], resized_image)
Здесь более полное пошаговое руководство здесь.
Если вы хотите запустить его параллельно, используйте concurrent.futures
в Py3 или futures
на Py2.7:
import concurrent.futures
import cv2
def resize(input_filename, size):
image = cv2.imread(input_filename)
resized_image = cv2.resize(image, size)
cv2.imwrite("thumbnail_%s%d.jpg" % (input_filename.split('.')[0], size[0]), resized_image)
executor = concurrent.futures.ThreadPoolExecutor(max_workers=3)
sizes = [(120, 120), (720, 720), (1600, 1600)]
for size in sizes:
executor.submit(resize, "input.jpg", size)
Ответ 5
Если вы уже знакомы с imagemagick, почему бы не придерживаться привязок python?
PythonMagick
Ответ 6
Пользователи Python 2.7, Windows, x64
В дополнение к @JakobBowyer и @Audionautics, PIL
довольно старый, и вы можете найти самостоятельно устраните неполадки и ищите правильную версию... вместо этого используйте Pillow
из здесь (источник)
обновленный фрагмент будет выглядеть так:
im = Image.open(full_path)
im.thumbnail(thumbnail_size)
im.save(new_path, "JPEG")
полное перечисление script для создания миниатюр:
import os
from PIL import Image
output_dir = '.\\output'
thumbnail_size = (200,200)
if not os.path.exists(output_dir):
os.makedirs(output_dir)
for dirpath, dnames, fnames in os.walk(".\\input"):
for f in fnames:
full_path = os.path.join(dirpath, f)
if f.endswith(".jpg"):
filename = 'thubmnail_{0}'.format(f)
new_path = os.path.join(output_dir, filename)
if os.path.exists(new_path):
os.remove(new_path)
im = Image.open(full_path)
im.thumbnail(thumbnail_size)
im.save(new_path, "JPEG")
Ответ 7
Еще один ответ, так как (я думаю?) Никто не упомянул качество.
Вот фотография, которую я сделал с iPhone 6S в Олимпийском парке в Восточном Лондоне:
![roof of olympic swimming pool]()
Крыша сделана из набора деревянных реек, и если вы не будете достаточно аккуратно уменьшать размеры, вы получите очень неприятные муаровые эффекты. Мне пришлось довольно сильно сжать изображение, чтобы загрузить его в stackoverflow - если вам интересно, оригинал находится здесь.
Здесь cv2 изменить размер:
$ python3
Python 3.7.3 (default, Apr 3 2019, 05:39:12)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> x = cv2.imread("IMG_1869.JPG")
>>> y = cv2.resize(x, (120, 90))
>>> cv2.imwrite("cv2.png", y)
True
Здесь vipsthumbnail:
$ vipsthumbnail IMG_1869.JPG -s 120 -o vips.png
А вот два уменьшенных изображения рядом друг с другом, увеличенные в 2 раза, с vipsthumbnail слева:
![results of downsize to 120 pixels across]()
(ImageMagick дает те же результаты, что и vipsthumbnail)
По умолчанию cv2 имеет значение BILINEAR, поэтому он имеет фиксированную маску 2x2. Для каждой точки на выходном изображении он вычисляет соответствующую точку на входе и получает среднее значение 2x2. Это означает, что на самом деле только выборка не более 240 точек в каждой строке и просто игнорирование остальных 3750! Это приводит к появлению уродливых алиасов.
vipsthumbnail делает более сложное сокращение в три этапа.
- Он использует функцию сжатия при загрузке libjpeg, чтобы уменьшить изображение в 8 раз по каждой оси с помощью блочного фильтра, чтобы превратить 4032 пикселя по всему изображению в 504 x 378 пикселей.
- Он сжимает еще один фильтр размером 2 x 2, чтобы получить 252 x 189 пикселей.
- Он заканчивается ядром Lanczos3 5 x 5, чтобы получить изображение размером 120 x 90 пикселей.
Предполагается, что это даст эквивалентное качество полноценному ядру Lanczos3, но будет быстрее, поскольку большую часть времени он может фильтровать по блокам.
Ответ 8
Я наткнулся на это, пытаясь выяснить, какую библиотеку мне следует использовать:
Похоже, OpenCV явно быстрее PIL.
Тем не менее, я работаю с электронными таблицами, и получается, что модуль, который я использовал openpyxl, уже требует от меня импорта PIL для вставки изображений.