Je construis une galerie de photos en Python et je veux pouvoir générer rapidement des vignettes pour les images haute résolution.
Quel est le moyen le plus rapide de générer des vignettes de haute qualité pour diverses sources d’image?
Devrais-je utiliser une bibliothèque externe comme imagemagick ou existe-t-il un moyen interne efficace de le faire?
Les dimensions des images redimensionnées seront (taille maximale):
120x120
720x720
1600x1600
La qualité est un problème, car je souhaite préserver autant de couleurs originales que possible et minimiser les artefacts de compression.
Merci.
Vous voulez PIL il le fait avec facilité
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)))
Si vous avez désespérément besoin de vitesse. Ensuite, enfilez-le, multi-traitez ou obtenez une autre langue.
Un peu tard pour la question (seulement un an!), Mais je vais revenir à la partie "multitraitement" de la réponse de @ JakobBowyer.
C’est un bon exemple de problème avec { parallèle embarrassant }, car le bit de code principal ne mute en aucun état externe à lui-même. Il lit simplement une entrée, effectue son calcul et enregistre le résultat.
Python est en fait assez bon pour ce genre de problèmes grâce à la fonction map fournie par 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))
Le noyau du code est exactement le même que @JakobBowyer, mais au lieu de l'exécuter en boucle dans un seul thread, nous l'avons intégré dans une fonction répartie sur plusieurs cœurs via la fonction de mappage multitraitement.
J'avais envie de m'amuser et j'ai donc fait une analyse comparative des différentes méthodes suggérées ci-dessus et de quelques idées personnelles.
J'ai rassemblé 1000 images haute résolution pour iPhone 6s 12MP, de 4032x3024 pixels chacune, et utilise un iMac à 8 cœurs.
Voici les techniques et les résultats - chacun dans sa propre section.
Méthode 1 - Image séquentielle ImageMagick
C'est un code simpliste et non optimisé. Chaque image est lue et une vignette est produite. Ensuite, il est relu et une miniature de taille différente est produite.
#!/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))
Résultat: 170 secondes
Méthode 2 - ImageMagick séquentielle avec chargement unique et redimensionnement successif
C'est toujours séquentiel mais légèrement plus intelligent. Chaque image n'est lue qu'une fois et l'image chargée est ensuite redimensionnée à trois dimensions et sauvegardée à trois résolutions. L’amélioration réside dans le fait que chaque image est lue une seule fois, pas 3 fois.
#!/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))
Résultat: 76 secondes
Méthode 3 - GNU Parallel + ImageMagick
Ceci s'appuie sur la méthode précédente en utilisant GNU Parallel pour traiter les images N
en parallèle, où N
est le nombre de cœurs de processeur de votre ordinateur.
#!/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))
Résultat: 18 secondes
Méthode 4 - GNU Parallel + vips
Ceci est identique à la méthode précédente, mais utilise vips
sur la ligne de commande au lieu de 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))
Résultat: 8 secondes
Méthode 5 - PIL séquentielle
Ceci est censé correspondre à la réponse de Jakob.
#!/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
Résultat: 38 secondes
Méthode 6 - PIL séquentiel à chargement unique et redimensionnement successif
Ceci est conçu comme une amélioration de la réponse de Jakob, dans laquelle l'image est chargée une seule fois, puis redimensionnée trois fois au lieu d'être rechargée à chaque fois pour produire chaque nouvelle résolution.
#!/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
Résultat: 27 secondes
Méthode 7 - PIL parallèle
Ceci est destiné à correspondre à la réponse d'Audionautics, dans la mesure où elle utilise le multitraitement de Python. Cela évite également de recharger l'image pour chaque taille de vignette.
#!/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)))))
Résultat: 6 secondes
Méthode 8 - Parallel OpenCV
Ceci est censé être une amélioration de la réponse de bcattle, dans la mesure où il utilise OpenCV, mais évite également le besoin de recharger l'image pour générer chaque nouvelle sortie de résolution.
#!/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)))))
Résultat: 5 secondes
Une autre option consiste à utiliser les liaisons python à OpenCV . Cela peut être plus rapide que PIL ou 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)
Il existe une solution plus complète ici .
Si vous voulez l'exécuter en parallèle, utilisez concurrent.futures
sur Py3 ou le futures
package sur 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)
Si vous connaissez déjà imagemagick, pourquoi ne pas vous en tenir aux liaisons python?
En plus de @JobobBowyer & @Audionautics , PIL
est assez ancien et vous pouvez trouver le dépannage et rechercher la bonne version ... à la place, utilisez Pillow
dans ici ( source )
l'extrait mis à jour ressemblera à ceci:
im = Image.open(full_path)
im.thumbnail(thumbnail_size)
im.save(new_path, "JPEG")
script d'énumération complet pour la création de vignettes:
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")
Je suis tombé sur ceci en essayant de trouver quelle bibliothèque je devrais utiliser:
Il semble que OpenCV est clairement plus rapide que PIL .
Cela dit, je travaille avec des tableurs et il s’avère que le module que j’utilisais openpyxl nécessite déjà l’importation de PIL pour insérer des images .