Какой самый быстрый способ создания миниатюр изображений в Python? - PullRequest
23 голосов
/ 25 декабря 2011

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

Какой самый быстрый способ создания высококачественных миниатюр для различных источников изображений?

Должен ли я использовать внешнюю библиотеку, например, imagemagick, или есть эффективный внутренний способ сделать это?

Размеры изображений с измененным размером будут (максимальный размер):

120x120
720x720
1600x1600

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

Спасибо.

Ответы [ 7 ]

23 голосов
/ 26 декабря 2011

Вы хотите 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)))

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

15 голосов
/ 13 августа 2018

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

Я собрал 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 секунд

12 голосов
/ 01 ноября 2013

Немного опоздал на вопрос (всего год!), Но я прокомментирую "многопроцессорную работу" в ответе @ 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 голосов
/ 23 ноября 2014

Другим вариантом является использование привязок 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)
3 голосов
/ 25 декабря 2011

Если вы уже знакомы с imagemagick, почему бы не придерживаться привязок python?

PythonMagick

2 голосов
/ 27 сентября 2014

Python 2.7, Windows, пользователи x64

В дополнение к @ JakobBowyer & @ Audionautics , PIL довольно стар, и вы можете найти решение проблем и поиск подходящей версии ... вместо этого используйте Pillow с здесь ( источник )

обновленный фрагмент будет выглядеть следующим образом:

im = Image.open(full_path)
im.thumbnail(thumbnail_size)
im.save(new_path, "JPEG")

скрипт полного перечисления для создания миниатюр:

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")
0 голосов
/ 12 августа 2018

Я наткнулся на на , когда пытался выяснить, какую библиотеку мне следует использовать:

Кажется, что OpenCV явно быстрее, чем PIL .

Тем не менее, я работаю с электронными таблицами, и получается, что модуль, который я использовал openpyxl, уже требует от меня импорта PIL для вставки изображений .

...