Face à la nécessité de redimensionner et de convertir une multitude d’images, un problème récurrent se pose : la plupart des applications ne permettent que le traitement séquentiel des fichiers en file d’attente. Malgré la puissance de calcul de mon processeur, cette tâche s’annonce fastidieuse. Dans mon cas, près de 4000 fichiers, chacun dépassant les 20 Mo, nécessitent une transformation. En explorant les possibilités d’optimisation, j’ai découvert la piste du multiprocessing.
Un cœur de processeur est limité à exécuter une instruction à la fois. Le multiprocessing offre la possibilité de paralléliser le travail sur différents cœurs, permettant ainsi à chaque cœur de traiter une image simultanément. A une époque où les processeurs peuvent proposer plus de 10 cœurs, le gain de temps devient rapidement conséquent.
Python – Pillow – Multiprocessing
Contexte du code ci-dessous :
- Réduction de la taille des images à une largeur ne dépassant pas les 3000px – resolution=3000
- Conversion des images au format TIF une fois redimensionnées au format PNG
- Traitement parallélisé de la file d’attente sur au maximum 7 cœurs – Pool(min(cpu_count(), 7)).
- Par défaut il y a une limitation sur la taille maximum des images traitées. Les images étant de « confiance » pour une exécution locale, j’ai supprimé la limite en utilisant : Image.MAX_IMAGE_PIXELS = None
- En bonus : mise en place de la barre de progression de la librairie alive_progress qui permet de suivre l’avancée du travail et d’avoir une estimation du temps restant jusqu’à la fin (temps estimé sur la base du travail accompli)
C’est partie pour le code
import os from PIL import Image from alive_progress import alive_bar from multiprocessing import Pool, cpu_count source_dir = "chemin du répertoire source" destination_dir = "chemin du répertoire destination" def process_file(args): root, file, destination_dir, resolution = args try: Image.MAX_IMAGE_PIXELS = None source_file_path = os.path.join(root, file) relative_path = os.path.relpath(root, source_dir) destination_path = os.path.join(destination_dir, relative_path) if not os.path.exists(destination_path): os.makedirs(destination_path) filename, file_extension = os.path.splitext(file) destination_file_path = os.path.join(destination_path, f"{filename}.png") with Image.open(source_file_path) as img: img = img.convert('RGB') img.thumbnail((resolution, resolution)) img.save(destination_file_path, dpi=(resolution, resolution)) return ("👉 Copie de " + filename + " █ dans █ " + destination_path) except Exception as e: print(f"Erreur lors du traitement du fichier {filename}: {e}") def reduire_resolution_et_sauvegarder_en_png(source_dir, destination_dir, resolution): try: files_to_process = [] for root, dirs, files in os.walk(source_dir): for file in files: if file.lower().endswith('.tif'): files_to_process.append((root, file)) with Pool(min(cpu_count(), 7)) as p: args_list = [(root, file, destination_dir, resolution) for root, file in files_to_process] with alive_bar(len(files_to_process), title="⏳ Traitement des fichiers") as bar: for _ in p.imap_unordered(process_file, args_list): bar() if _ is not None: print (_) except Exception as e: print(f"Erreur lors de la copie des fichiers .tif de {source_dir} vers {destination_dir}: {e}") if __name__ == '__main__': # Exemple d'utilisation reduire_resolution_et_sauvegarder_en_png(source_dir, destination_dir, resolution=3000)
Laisser un commentaire