En lisant l’image sous forme de tableau NumPy 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 , divers traitements d’image peuvent être effectués à l’aide des fonctions NumPy.
En utilisant ndarray, vous pouvez et définir (modifier) les valeurs des pixels, découper des images, concaténer des images, etc. Ceux qui connaissent NumPy peuvent effectuer divers traitements d’images sans utiliser de bibliothèques telles qu’OpenCV.
Même en utilisant OpenCV, OpenCV pour Python traite les données d’image comme des 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 , il est donc utile de savoir comment utiliser NumPy ( 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 ). En plus d’OpenCV, de nombreuses bibliothèques telles que scikit-image traitent les images comme des 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 .
Cet article décrit le contenu suivant.
Lire et écrire des images :
- Comment lire un fichier image sous forme de tableau NumPy
- Comment enregistrer un tableau NumPy 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 en tant que fichier image
Exemples de traitement d’images avec NumPy ( 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 ) :
- Obtenir et définir (modifier)les valeurs des pixels
- Génération d’image monochrome et concaténation
- Inversion négative-positive (inversion de la valeur du pixel)
- Réduction de couleur
- Binarisation
- Correction gamma
- Parage avec tranche
- Fractionner avec une tranche ou une fonction
- Pâté avec tranche
- Mélange alpha et masquage
- Faire pivoter et retourner
Les exemples de codes de cet article utilisent Pillow pour lire et enregistrer des fichiers image. Si vous souhaitez utiliser OpenCV, consultez l’article suivant.
Voir également l’article suivant sur Pillow. Des opérations simples telles que la lecture, l’enregistrement, le redimensionnement et la rotation d’images peuvent être effectuées par Pillow seul.
Comment lire un fichier image en tant que 𝐧𝐝𝐚𝐫𝐫𝐚𝐲
Prenons l’image suivante comme exemple.

La transmission des données d’image se fait par PIL.I𝐦𝐚𝐠𝐞.𝐨𝐩𝐞𝐧() à 𝐧𝐩.𝐚𝐫𝐫𝐚𝐲() retourner un 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 3D dont la forme est (𝐫𝐨𝐰 (𝐡𝐞𝐢𝐠𝐡𝐭), 𝐜𝐨𝐥𝐮𝐦𝐧 (𝐰𝐢𝐝𝐭𝐡), 𝐜𝐨𝐥𝐨𝐫 (𝐜𝐡𝐚𝐧𝐧𝐌𝐞𝐥)) .
from PIL import Image import numpy as np im = np.array(Image.open('data/src/lena.jpg')) print(type(im)) # <class 'numpy.ndarray'> print(im.dtype) # uint8 print(im.shape) # (225, 400, 3)
L’ordre des couleurs (canaux) est RVB (rouge, vert, bleu). Notez qu’il est différent du cas de lecture avec 𝐜𝐯2.𝐢𝐦𝐫𝐞𝐚𝐝() d’OpenCV.
Si vous convertissez l’image en niveaux de gris avec 𝐜𝐨𝐧𝐯𝐞𝐫𝐭(‘L’) puis la transmettez à 𝐧𝐩.𝐚𝐫𝐫𝐚𝐲() , elle renvoie un 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 2D dont la forme est (𝐫𝐨𝐰 (𝐡𝐞𝐢𝐠𝐡𝐭), 𝐜𝐨𝐥𝐮𝐦𝐦𝐞 (𝐰𝐢𝐝𝐭𝐡)) .
im_gray = np.array(Image.open('data/src/lena.jpg').convert('L')) print(im_gray.shape) # (225, 400)
Vous pouvez également obtenir 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 de PIL.I𝐦𝐚𝐠𝐞 avec 𝐧𝐩.𝐚𝐬𝐚𝐫𝐫𝐚𝐲() . 𝐧𝐩.𝐚𝐫𝐫𝐚𝐲() renvoie un 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 réinscriptible , tandis que 𝐧𝐩.𝐚𝐬𝐚𝐫𝐫𝐚𝐲() renvoie un 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 en lecture seule .
Pour 𝐧𝐩.𝐚𝐫𝐫𝐚𝐲() , vous pouvez modifier la valeur de l’élément (pixel).
print(im.flags.writeable) # True print(im[0, 0, 0]) # 109 im[0, 0, 0] = 0 print(im[0, 0, 0]) # 0
Pour 𝐧𝐩.𝐚𝐬𝐚𝐫𝐫𝐚𝐲() , vous ne pouvez pas écrire modifier la valeur car la ré est interdite. Il est possible de créer un nouveau 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 basé sur le 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 lu .
im_as = np.asarray(Image.open('data/src/lena.jpg')) print(type(im_as)) # <class 'numpy.ndarray'> print(im_as.flags.writeable) # False # im_as[0, 0, 0] = 0 # ValueError: assignment destination is read-only
Le type de données 𝐝𝐭𝐲𝐩𝐞 de lecture 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 est 𝐮𝐢𝐧𝐭8 (entier non signé 8 bits).
Si vous souhaitez le traiter comme un nombre à virgule flottante 𝐟𝐥𝐨𝐚𝐭 , vous pouvez le convertir avec 𝐚𝐬𝐭𝐲𝐩𝐞() ou préciser le type de données dans le deuxième argument de 𝐧𝐩.𝐚𝐫𝐫𝐚𝐲() et 𝐧𝐩.𝐚𝐬𝐚𝐫𝐫𝐚𝐲() .
im_f = im.astype(np.float64) print(im_f.dtype) # float64 im_f = np.array(Image.open('data/src/lena.jpg'), np.float64) print(im_f.dtype) # float64
Consultez l’article suivant pour plus d’informations sur le type de données 𝐝𝐭𝐲𝐩𝐞 dans NumPy.
Comment enregistrer un tableau NumPy 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 en tant que fichier image
Passer 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 à I𝐦𝐚𝐠𝐞.𝐟𝐫𝐨𝐦𝐚𝐫𝐫𝐚𝐲()( ) PIL.I𝐦𝐚𝐠𝐞 . Il peut être enregistré sous forme de fichier image avec la méthode 𝐬𝐚𝐯𝐞() . Le format du fichier enregistré est automatiquement déterminé à partir de l’extension du chemin passé dans l’argument de 𝐬𝐚𝐯𝐞() .
pil_img = Image.fromarray(im) print(pil_img.mode) # RGB pil_img.save('data/temp/lena_save_pillow.jpg')
Une image en niveaux de gris (tableau 2D) peut également être transmise à I𝐦𝐚𝐠𝐞.𝐟𝐫𝐨𝐦𝐚𝐫𝐫𝐚𝐲() . 𝐦𝐨𝐝𝐞 devient automatiquement « L » (niveaux de gris). Elle peut être enregistrée avec 𝐬𝐚𝐯𝐞() .
pil_img_gray = Image.fromarray(im_gray) print(pil_img_gray.mode) # L pil_img_gray.save('data/temp/lena_save_pillow_gray.jpg')
Si vous souhaitez simplement le sauvegarder, vous pouvez l’écrire sur une seule ligne.
Image.fromarray(im).save('data/temp/lena_save_pillow.jpg') Image.fromarray(im_gray).save('data/temp/lena_save_pillow_gray.jpg')
Si le type de données 𝐝𝐭𝐲𝐩𝐞 de 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 est 𝐟𝐥𝐨𝐚𝐭 , etc., une erreur se reflète, il est donc nécessaire de convertir en 𝐮𝐢𝐧𝐭8 .
# pil_img = Image.fromarray(im_f) # TypeError: Cannot handle this data type pil_img = Image.fromarray(im_f.astype(np.uint8)) pil_img.save('data/temp/lena_save_pillow.jpg')
Notez que si la valeur du pixel est représentée par 0,0 à 1,0 , il est nécessaire de multiplier par 255 et de convertir en 𝐮𝐢𝐧𝐭8 et d’enregistrer.
Avec 𝐬𝐚𝐯𝐞() , les paramètres selon le format peuvent être passés en tant qu’arguments. Voir Format de fichier image pour plus de détails.
Par exemple, dans le cas d’un fichier JPG, vous pouvez passer la qualité de l’image comme argument 𝐪𝐮𝐚𝐥𝐢𝐭𝐲 . Elle varie de 1 (la plus basse) à 95 (la plus élevée) et sa valeur par défaut est 75 .
Obtenir et définir (modifier)les valeurs des pixels
Vous pouvez obtenir la valeur d’un pixel en spécifiant les coordonnées à l’index [𝐫𝐨𝐰, 𝐜𝐨𝐥𝐮𝐦𝐧𝐬] de 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 . Notez que l’ordre est 𝐲, 𝐱 aux coordonnées xy. L’origine est en haut à gauche.
from PIL import Image import numpy as np im = np.array(Image.open('data/src/lena.jpg')) print(im.shape) # (225, 400, 3) print(im[100, 150]) # [111 81 109] print(type(im[100, 150])) # <class 'numpy.ndarray'>
L’exemple ci-dessus montre la valeur à (𝐲, 𝐱) = (100, 150) , c’est-à-dire la 100e ligne et la 150e colonne de pixels. Comme mentionné ci-dessus, les couleurs du 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 obtenues à l’aide de Pillow sont dans l’ordre RVB, donc le résultat est (R, G, B) = (111, 81, 109) .
Vous pouvez également utiliser le déballage pour affecter des variables distinctes.
R, G, B = im[100, 150] print(R) # 111 print(G) # 81 print(B) # 109
Il est également possible d’obtenir la valeur en spécifiant la couleur.
print(im[100, 150, 0]) # 111 print(im[100, 150, 1]) # 81 print(im[100, 150, 2]) # 109
Vous pouvez également passer à une nouvelle valeur. Vous pouvez modifier la totalité du RVB en une seule fois ou le modifier avec une seule couleur.
im[100, 150] = (0, 50, 100) print(im[100, 150]) # [ 0 50 100] im[100, 150, 0] = 150 print(im[100, 150]) # [150 50 100]
Pour plus d’informations sur l’obtention et la définition des valeurs d’un 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 , consultez l’article suivant.
Génération d’image monochrome et concaténation
Générez des images monochromes en définissant d’autres valeurs de couleur sur 0 et concaténez-les horizontalement avec 𝐧𝐩.𝐜𝐨𝐧𝐜𝐚𝐭𝐞𝐧𝐚𝐭𝐞() .
from PIL import Image import numpy as np im = np.array(Image.open('data/src/lena_square.png')) im_R = im.copy() im_R[:, :, (1, 2)] = 0 im_G = im.copy() im_G[:, :, (0, 2)] = 0 im_B = im.copy() im_B[:, :, (0, 1)] = 0 im_RGB = np.concatenate((im_R, im_G, im_B), axis=1) # im_RGB = np.hstack((im_R, im_G, im_B)) # im_RGB = np.c_['1', im_R, im_G, im_B] pil_img = Image.fromarray(im_RGB) pil_img.save('data/dst/lena_numpy_split_color.jpg')
Inversion négative-positive (inverser la valeur du pixel)
Il est également facile de calculer et de manipuler les valeurs des pixels.
Une image inversée négative-positive peut être générée en soustrayant la valeur du pixel de la valeur maximale ( 255 pour 𝐮𝐢𝐧𝐭8 ).
import numpy as np from PIL import Image im = np.array(Image.open('data/src/lena_square.png').resize((256, 256))) im_i = 255 - im Image.fromarray(im_i).save('data/dst/lena_numpy_inverse.jpg')

La taille d’origine étant trop grande, elle est redimensionnée avec 𝐫𝐞𝐬𝐢𝐳𝐞() pour plus de commodité. Il en va de même pour les exemples suivants.
Réduction de couleur
Coupez le reste de la division en utilisant // et multipliez à nouveau, les valeurs des pixels deviennent discrètes et le nombre de couleurs peut être réduit.
import numpy as np from PIL import Image im = np.array(Image.open('data/src/lena_square.png').resize((256, 256))) im_32 = im // 32 * 32 im_128 = im // 128 * 128 im_dec = np.concatenate((im, im_32, im_128), axis=1) Image.fromarray(im_dec).save('data/dst/lena_numpy_dec_color.png')

Binarisation
Il est également possible d’attribuer du noir et du blanc en fonction du seuil.
Voir les articles suivants pour plus de détails.

Correction gamma
Vous pouvez faire tout ce que vous voulez avec les valeurs des pixels, comme la multiplication, la division, l’exponentiation, etc.
Vous n’avez pas besoin d’utiliser la boucle 𝐟𝐨𝐫 car l’image entière peut être calculée telle quelle.
from PIL import Image import numpy as np im = np.array(Image.open('data/src/lena_square.png')) im_1_22 = 255.0 * (im / 255.0)**(1 / 2.2) im_22 = 255.0 * (im / 255.0)**2.2 im_gamma = np.concatenate((im_1_22, im, im_22), axis=1) pil_img = Image.fromarray(np.uint8(im_gamma)) pil_img.save('data/dst/lena_numpy_gamma.jpg')
À la suite du calcul, le type de données 𝐝𝐭𝐲𝐩𝐞 de 𝐧𝐮𝐦𝐩𝐲.𝐧𝐝𝐚𝐫𝐫𝐚𝐲 est converti en nombre à virgule flottante 𝐟𝐥𝐨𝐚𝐭 . Notez que vous devez le convertir en 𝐮𝐢𝐧𝐭8 lorsque vous l’enregistrez.
Parage avec tranche
En spécifiant une zone avec une tranche, vous pouvez la découper en un rectangle.
from PIL import Image import numpy as np im = np.array(Image.open('data/src/lena_square.png')) print(im.shape) # (512, 512, 3) im_trim1 = im[128:384, 128:384] print(im_trim1.shape) # (256, 256, 3) Image.fromarray(im_trim1).save('data/dst/lena_numpy_trim.jpg')

Consultez l’article suivant pour plus d’informations sur le découpage pour 𝐧𝐮𝐦𝐩𝐲.𝐧𝐝𝐚𝐫𝐫𝐚𝐲 .
Il peut être pratique de définir une fonction qui spécifie les coordonnées supérieures gauche ainsi que la largeur et la hauteur de la zone à découper.
def trim(array, x, y, width, height): return array[y:y + height, x:x+width] im_trim2 = trim(im, 128, 192, 256, 128) print(im_trim2.shape) # (128, 256, 3) Image.fromarray(im_trim2).save('data/dst/lena_numpy_trim2.jpg')

Si vous spécifiez en dehors de la taille de l’image, elle sera ignorée.
im_trim3 = trim(im, 128, 192, 512, 128) print(im_trim3.shape) # (128, 384, 3) Image.fromarray(im_trim3).save('data/dst/lena_numpy_trim3.jpg')

Fractionner avec une tranche ou une fonction
Vous pouvez également diviser l’image en découpant.
from PIL import Image import numpy as np im = np.array(Image.open('data/src/lena_square.png').resize((256, 256))) print(im.shape) # (256, 256, 3) im_0 = im[:, :100] im_1 = im[:, 100:] print(im_0.shape) # (256, 100, 3) print(im_1.shape) # (256, 156, 3) Image.fromarray(im_0).save('data/dst/lena_numpy_split_0.jpg') Image.fromarray(im_1).save('data/dst/lena_numpy_split_1.jpg')


Il est également possible de diviser l’image avec la fonction NumPy.
𝐧𝐩.𝐡𝐬𝐩𝐥𝐢𝐭() divise 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 horizontalement. Si une valeur entière est spécifiée pour le deuxième argument, 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 est divisée de manière égale.
im_0, im_1 = np.hsplit(im, 2) print(im_0.shape) # (256, 128, 3) print(im_1.shape) # (256, 128, 3)
Si une liste est spécifiée comme deuxième argument, 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 est divisée à la position de cette valeur.
im_0, im_1, im_2 = np.hsplit(im, [100, 150]) print(im_0.shape) # (256, 100, 3) print(im_1.shape) # (256, 50, 3) print(im_2.shape) # (256, 106, 3)
𝐧𝐩.𝐯𝐬𝐩𝐥𝐢𝐭() divise 𝐧𝐝𝐚𝐫𝐫𝐚𝐲 verticalement. L’utilisation de 𝐧𝐩.𝐯𝐬𝐩𝐥𝐢𝐭() est la même que 𝐧𝐩.𝐡𝐬𝐩𝐥𝐢𝐭() .
Lorsqu’une valeur entière est spécifiée comme deuxième argument avec 𝐧𝐩.𝐡𝐬𝐩𝐥𝐢𝐭() ou 𝐧𝐩.𝐯𝐬𝐩𝐥𝐢𝐭() , une erreur est découverte si elle ne peut pas être divisée de manière égale. 𝐧𝐩.𝐚𝐫𝐫𝐚𝐲_𝐬𝐩𝐥𝐢𝐭() ajuste la taille de manière appropriée et la divise.
# im_0, im_1, im_2 = np.hsplit(im, 3) # ValueError: array split does not result in an equal division im_0, im_1, im_2 = np.array_split(im, 3, axis=1) print(im_0.shape) # (256, 86, 3) print(im_1.shape) # (256, 85, 3) print(im_2.shape) # (256, 85, 3)
Pâté avec tranche
En utilisant des tranches, un rectangle de tableau peut être remplacé par un autre rectangle de tableau.
En utilisant ceci, une partie de l’image ou l’image entière peut être collée sur une autre image.
import numpy as np from PIL import Image src = np.array(Image.open('data/src/lena_square.png').resize((128, 128))) dst = np.array(Image.open('data/src/lena_square.png').resize((256, 256))) // 4 dst_copy = dst.copy() dst_copy[64:128, 128:192] = src[32:96, 32:96] Image.fromarray(dst_copy).save('data/dst/lena_numpy_paste.jpg')

dst_copy = dst.copy() dst_copy[64:192, 64:192] = src Image.fromarray(dst_copy).save('data/dst/lena_numpy_paste_all.jpg')

Notez qu’une erreur se produit si la taille de la zone spécifiée sur le côté gauche diffère de la taille de la zone spécifiée sur le côté droit.
Mélange alpha et masquage
En effectuant une opération pour chaque élément (= pixel) du tableau, deux images peuvent être fusionnées ou composées en fonction d’une image de masque. Consultez les articles suivants pour plus de détails.


Faire pivoter et retourner
Il existe également des fonctions qui permettent de faire pivoter le tableau et de le retourner vers le haut, le bas, la gauche et la droite.
Image originale :

Image pivotée :

Image inversée :

