HOWTO · Matplotlib

Convertir un tableau numérique en image PIL en Python

Ce tutoriel explique comment convertir un tableau NumPy en une image PIL en utilisant le fichier Image.fromarray() du paquet PIL.

Sur cette page

Utilisez Image.fromarray() de Pillow pour créer une image PIL à partir d’un tableau NumPy. Un tableau à deux dimensions représente des pixels en niveaux de gris ; les tableaux de forme (height, width, 3) ou (height, width, 4) représentent des pixels RGB ou RGBA. Le dtype et les valeurs du tableau comptent également : vérifiez-les avant la conversion. PIL est le nom d’importation courant, tandis que Pillow est la bibliothèque maintenue.

Pour une image RGB, créez un tableau uint8 à trois canaux et transmettez-le à Image.fromarray(). La sortie indique le mode de l’image, ses dimensions (width, height) et le pixel situé dans le coin supérieur gauche.

Le code et les libellés ou sorties affichés dans cette section restent en anglais.

import numpy as np
from PIL import Image

pixels = np.array(
    [[[255, 0, 0], [0, 255, 0]], [[0, 0, 255], [255, 255, 255]]],
    dtype=np.uint8,
)
image = Image.fromarray(pixels)
print(image.mode, image.size, image.getpixel((0, 0)))
RGB (2, 2) (255, 0, 0)

L’image obtenue est un objet Pillow en mémoire. Vous pouvez vérifier son mode et ses dimensions, puis l’afficher dans un environnement qui prend en charge l’aperçu des images ou l’enregistrer dans un fichier.

Convertir un tableau NumPy en image PIL avec Image.fromarray()

Image.fromarray(array) déduit le mode de l’image à partir de la forme et du dtype du tableau. Pour des données en niveaux de gris, utilisez un tableau à deux dimensions de forme (height, width). Pour des données RGB, utilisez (height, width, 3) ; un quatrième canal facultatif donne le format RGBA. Les deux premières dimensions correspondent aux lignes et aux colonnes : un tableau de 40 lignes sur 60 colonnes crée une image de 60 pixels de large et 40 pixels de haut.

Utilisez np.uint8 pour des échantillons d’image ordinaires sur 8 bits lorsque les valeurs sont déjà des entiers compris entre 0 et 255. Évitez de convertir le type sans vérifier : la conversion de nombres à virgule flottante tronque leur partie fractionnaire, et les valeurs hors de la plage représentable peuvent revenir au début de cette plage. Si les données sont exprimées sur une autre échelle, déterminez comment les faire correspondre à l’intensité d’affichage avant la conversion. Par exemple, les données comprises entre 0 et 1 peuvent être multipliées par 255, puis arrondies ; pour toute autre plage, une normalisation explicite adaptée à leur signification est nécessaire.

Préparer la forme, le dtype et la plage de valeurs du tableau

Un tableau de forme (height, width) correspond à une image en niveaux de gris. Les tableaux RGB et RGBA ajoutent une dernière dimension pour les canaux, avec trois ou quatre valeurs par pixel. Une forme telle que (height, width, 1) n’est pas la forme bidimensionnelle habituelle pour les niveaux de gris ; si cet axe final de taille un représente bien les niveaux de gris, supprimez-le avec array[..., 0]. Avant la conversion, vérifiez array.shape, array.dtype ainsi que les valeurs minimale et maximale. Le tableau doit contenir une grille rectangulaire régulière : des listes imbriquées de longueurs différentes ne constituent pas des données d’image.

Voici une conversion autonome en niveaux de gris. Les valeurs sont directement choisies dans la plage 8 bits : aucune mise à l’échelle n’est donc nécessaire. L’image obtenue utilise le mode niveaux de gris L et mesure (3, 2).

Le code et les libellés ou sorties affichés dans cette section restent en anglais.

import numpy as np
from PIL import Image

gray = np.array([[0, 64, 128], [32, 160, 255]], dtype=np.uint8)
image = Image.fromarray(gray)
print(image.mode, image.size)
L (3, 2)

Si votre source est un tableau à virgule flottante dont les valeurs vont de 0 à 1, convertissez-le délibérément avec np.round(array * 255).astype(np.uint8). Cette transformation convient uniquement si ces valeurs représentent des intensités normalisées. Pour des mesures, comme des températures, ou des scores arbitraires, choisissez des bornes inférieure et supérieure pertinentes, écrêtez ou normalisez les données dans cet intervalle, puis documentez cette correspondance ; sinon, le contraste et les valeurs des pixels peuvent être trompeurs.

Appliquer une palette de couleurs Matplotlib avant de créer une image PIL

Utilisez une palette de couleurs Matplotlib lorsque la source est un champ scalaire bidimensionnel dont vous souhaitez représenter les valeurs par des couleurs. Normalisez les données dans l’intervalle attendu par la palette choisie, puis convertissez le résultat en un tableau RGB ou RGBA sur 8 bits. La palette plasma renvoie des canaux rouge, vert, bleu et alpha normalisés ; supprimer le canal alpha avec [..., :3] produit donc un tableau RGB. Cette étape est distincte de Image.fromarray() : la palette crée les couleurs, puis Pillow enveloppe ces pixels dans une image.

Le code et les libellés ou sorties affichés dans cette section restent en anglais.

import numpy as np
import matplotlib
from PIL import Image

values = np.array([[0.0, 0.25], [0.75, 1.0]])
colors = matplotlib.colormaps["plasma"](values)[..., :3]
rgb = np.round(colors * 255).astype(np.uint8)
image = Image.fromarray(rgb)
print(image.mode, image.size)
RGB (2, 2)

Cet exemple suppose que values est déjà normalisé entre 0 et 1. Si la plage réelle des valeurs scalaires est différente, normalisez-les à partir de bornes adaptées avant d’appliquer la palette. Une palette est utile pour la visualisation, mais elle transforme les valeurs scalaires en couleurs d’affichage : ne remplacez pas les mesures d’origine si vous en avez besoin pour l’analyse. L’image obtenue est RGB et mesure (2, 2).

Redimensionner et enregistrer l’image PIL

Après la conversion, redimensionnez l’image avec image.resize((width, height)) ; Pillow attend la nouvelle taille dans l’ordre largeur puis hauteur. L’enregistrement est distinct de l’affichage : show() ouvre un aperçu dans l’environnement local, tandis que save() écrit un fichier image. Choisissez une extension correspondant au format souhaité, par exemple .png pour une sortie sans perte ou .jpg pour le format JPEG.

Le code et les libellés ou sorties affichés dans cette section restent en anglais.

import numpy as np
from PIL import Image

pixels = np.zeros((20, 30, 3), dtype=np.uint8)
pixels[:, :15] = [255, 80, 0]
pixels[:, 15:] = [20, 90, 220]
image = Image.fromarray(pixels)
resized = image.resize((60, 40))
resized.save("resized-image.png")
print(resized.size)

L’exemple crée ses données d’entrée en mémoire et écrit resized-image.png dans le répertoire de travail courant. Dans une application, choisissez un chemin adapté à l’emplacement de sortie. L’enregistrement ne modifie pas le tableau NumPy, et le redimensionnement change les dimensions en pixels de l’image Pillow.

Problèmes fréquents de conversion

  • Mode inattendu ou forme refusée : vérifiez array.shape. Utilisez (height, width) pour les niveaux de gris et trois ou quatre canaux dans la dernière dimension pour RGB ou RGBA. Supprimez un canal de taille un ajouté par inadvertance ou réordonnez les canaux si votre source suit une autre convention.
  • Luminosité ou couleurs incorrectes : vérifiez la plage des valeurs avant la conversion. Les valeurs flottantes comprises entre 0 et 1 nécessitent une mise à l’échelle réfléchie pour obtenir une image sur 8 bits ; ne multipliez pas de nouveau celles qui sont déjà comprises entre 0 et 255. Pour les autres plages, normalisez les données selon des bornes documentées.
  • dtype inattendu : vérifiez array.dtype et déterminez comment le convertir. Une simple conversion de type ne remet pas les données à l’échelle ; conservez une copie de la source si sa précision d’origine est importante.
  • Résultat de la palette incorrect : vérifiez que les données scalaires ont été normalisées comme prévu et que le résultat contient trois canaux RGB ou quatre canaux RGBA. Convertissez les canaux de couleur normalisés en uint8 avant de construire l’image.
  • Fichier image absent : appelez save() avec un chemin accessible en écriture et une extension prise en charge. Utilisez show() uniquement pour afficher un aperçu : cette méthode n’enregistre pas l’image.