
En lisant l’image sous la forme d’un ndarray de tableau NumPy, divers traitements d’image peuvent être effectués à l’aide des fonctions NumPy.
En utilisant ndarray, vous pouvez obtenir et définir (modifier) les valeurs de pixel, découper des images, concaténer des images, etc. Ceux qui connaissent NumPy peuvent effectuer divers traitements d’image sans utiliser de bibliothèques telles que OpenCV.
Même lors de l’utilisation d’OpenCV, OpenCV pour Python traite les données d’image comme ndarray, il est donc utile de savoir comment utiliser NumPy (ndarray). En plus d’OpenCV, de nombreuses bibliothèques telles que scikit-image traitent les images comme ndarray.
Cet article décrit le contenu suivant.
Lire et écrire des images :
- Comment lire le fichier image en tant que tableau NumPy ndarray
- Comment enregistrer le tableau NumPy ndarray en tant que fichier image
Exemples de traitement d’image avec NumPy (ndarray) :
- Obtenir et définir (modifier) les valeurs de pixel
- Génération d’image monochrome et concaténation
- Inversion négatif-positif (inversion de la valeur du pixel)
- Réduction des couleurs
- Binarisation
- Correction gamma
- Découpe avec tranche
- Fractionner avec une tranche ou une fonction
- Coller avec tranche
- Mélange et masquage alpha
- Tourner 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 aussi 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 ndarray
Prenez l’image suivante comme exemple.
Passer les données d’image lues par PIL.Image.open() à np.array() renvoie un ndarray 3D dont la forme est (ligne (hauteur), colonne (largeur), couleur (canal)).
from PIL import Image
import numpy as np
im = np.array(Image.open('data/src/lena.jpg'))
print(type(im))
#
print(im.dtype)
# uint8
print(im.shape)
# (225, 400, 3)
L’ordre des couleurs (canaux) est RVB (rouge, vert, bleu). Notez que c’est différent du cas de la lecture avec cv2.imread() d’OpenCV.
Si vous convertissez l’image en niveaux de gris avec convert(‘L’) puis que vous la transmettez à np.array(), elle renvoie un ndarray 2D dont la forme est (ligne (hauteur), colonne (largeur)).
im_gray = np.array(Image.open('data/src/lena.jpg').convert('L'))
print(im_gray.shape)
# (225, 400)
Vous pouvez également obtenir ndarray à partir de PIL.Image avec np.asarray(). np.array() renvoie un ndarray réinscriptible, tandis que np.asarray() renvoie un non-réinscriptiblendarray.
Pour np.array(), 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 np.asarray(), vous ne pouvez pas modifier la valeur car la réécriture est interdite. Il est possible de créer un nouveau ndarray basé sur le ndarray lu.
im_as = np.asarray(Image.open('data/src/lena.jpg'))
print(type(im_as))
#
print(im_as.flags.writeable)
# False
# im_as[0, 0, 0] = 0
# ValueError: assignment destination is read-only
Le type de données dtype du ndarray lu est uint8 (entier non signé 8 bits).
Si vous souhaitez le traiter comme un nombre à virgule flottante, vous pouvez le convertir avec astype() ou spécifier le type de données dans le deuxième argument de np.array() et np.asarray().
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 dtype dans NumPy.
Comment enregistrer le tableau NumPy ndarray en tant que fichier image
Passer ndarray à Image.fromarray() renvoie PIL.Image. Il peut être enregistré en tant que fichier image avec la méthode save(). Le format du fichier enregistré est automatiquement déterminé à partir de l’extension du chemin passé en argument de save().
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 à Image.fromarray(). devient automatiquement ‘L’ (niveaux de gris). Il peut être sauvegardé avec save().
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 l’enregistrer, 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 dtype de ndarray est float, etc., une erreur se produira, il est donc nécessaire de convertir en uint8.
# 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 uint8 et de sauvegarder.
Avec save(), les paramètres selon le format peuvent être passés en arguments. Voir Format de fichier image pour plus de détails.
Par exemple, dans le cas de JPG, vous pouvez passer la qualité de l’image à l’argument qualité. Il va de 1 (le plus bas) à 95 (le plus élevé) et 75 par défaut.
Obtenir et définir (modifier) les valeurs de pixel
Vous pouvez obtenir la valeur d’un pixel en spécifiant les coordonnées à l’index [ligne, colonnes] de ndarray. Notez que l’ordre est y, x en 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]))
#
L’exemple ci-dessus montre la valeur à (y, x) = (100, 150), c’est-à-dire la 100e ligne et la 150e colonne de pixels. Comme mentionné ci-dessus, les couleurs du ndarray obtenu à l’aide de Pillow sont dans l’ordre RVB, donc le résultat est (R, G, B) = (111, 81, 109).
Vous pouvez également utiliser la décompression pour les 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 changer le RVB en une seule fois ou le changer 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]
Génération d’image monochrome et concaténation
Générez des images monochromes en définissant les autres valeurs de couleur sur 0 et en les concaténant horizontalement avec np.concatenate(). Vous pouvez également concaténer des images en utilisant np.hstack() ou np.c_[]
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 (valeur de pixel inversée)
Il est également facile de calculer et de manipuler les valeurs de pixel.
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 uint8).
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')
Parce que la taille d’origine est trop grande, elle est redimensionnée avec resize() pour plus de commodité. Il en va de même pour les exemples suivants.
Réduction des couleurs
Coupez le reste de la division en utilisant // et multipliez à nouveau, les valeurs de pixel 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 au noir et blanc selon le seuil.
Voir les articles suivants pour plus de détails.
Correction gamma
Vous pouvez faire tout ce que vous voulez avec les valeurs de pixel, telles que la multiplication, la division, l’exponentiation, etc.
Vous n’avez pas besoin d’utiliser la boucle for 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 dtype de numpy.ndarray est converti en nombre à virgule flottante float. Notez que vous devez le convertir en uint8 lorsque vous l’enregistrez.
Découpe 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 en tranches pour numpy.ndarray.
Il peut être pratique de définir une fonction qui spécifie les coordonnées supérieures gauches 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 la 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.
np.hsplit() divise ndarray horizontalement. Si une valeur entière est spécifiée pour le deuxième argument, ndarray est divisé en parts égales.
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, ndarray est divisé à 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)
np.vsplit() divise ndarray verticalement. L’utilisation de np.vsplit() est la même que celle de np.hsplit().
Lorsqu’une valeur entière est spécifiée comme deuxième argument avec np.hsplit() ou np.vsplit(), une erreur est générée si elle ne peut pas être divisée de manière égale. np.array_split() 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)
Coller avec tranche
En utilisant des tranches, un rectangle de tableau peut être remplacé par un autre rectangle de tableau.
En utilisant cela, 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 produira 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 et masquage alpha
Par l’opération pour chaque élément (= pixel) du tableau, deux images peuvent être mélangées en alpha ou composées sur la base d’une image de masque. Voir les articles suivants pour plus de détails.
Tourner et retourner
Il existe également des fonctions qui font pivoter le tableau et le retournent vers le haut, le bas, la gauche et la droite.
Image originale:
Image pivotée :
Image inversée :