Dans cet article, j’ai envie de vous parler d’images, et plus particulièrement de manipulation d’images avec Python.
Le module PIL
Pour manipuler des images, j’ai l’habitude d’utiliser PIL. On peut l’installer en ligne de commande en tapant par exemple (si on utilise pip):
pip install pillow
En plus de PIL, j’utilise le module numpy… et vous allez vite savoir pourquoi!
Chargement d’une image
Pour la suite, je vais prendre l’image suivante, nommée tout simplement “image.jpg”:
C’est une magnifique photo du Pont de pierre, à Bordeaux, sur lequel passe le tramway.
Pour la charger, j’utilise le code:
from PIL import Image import numpy as np imgpil = Image.open("image.jpg") img = np.array(imgpil) # Transformation de l'image en tableau numpy
Pour en déterminer les dimensions, je tape:
print(img.shape)
La méthode shape appliquée à une image renvoie un triplet (ou un quadruplet) correspondant :
- nombre de lignes (en pixel)
- nombre de colonnes (en pixel)
- nombre de plans
En général, il n’y a que trois plans (correspondant aux trois couleurs primaires: rouge, vert et bleu), mais quelques fois, il y en a quatre. Ce dernier correspond à la transparence.
Sur mon image, Python me renvoie:
(509, 2000, 3)
Cela signifie que les dimensions de mon image sont 509 × 2000 [hauteur×largeur], et qu’il n’y a que trois plans.
La valeur d’un pixel
Pour connaître la valeur d’un pixel, on utilise le tableau représentant l’image et on demande tout simplement d’afficher la valeur du pixel en informant les coordonnées de ce dernier. Par exemple, pour afficher le valeur du pixel qui est en ligne 100 et en colonne 200, j’écris:
print(img[200,100])
qui me renvoie:
[63 55 42]
qui correspond au triplet (R, V, B), où R, V et B sont compris entre 0 et 255.
Pour modifier la valeur de ce pixel, il suffit donc de changer sa valeur:
img[200,100] = (125,30,255)
On peut ainsi créer des fonctions “filtres” :
def filtre_rouge(img_orig): im = np.copy(img_orig) # On fait une copie de l'original for i in range(im.shape[0]): for j in range(im.shape[1]): r, v, b = im[i, j] im[i, j] = (r, 0,0) return im def filtre_bleu(img_orig): im = np.copy(img_orig) # On fait une copie de l'original for i in range(im.shape[0]): for j in range(im.shape[1]): r, v, b = im[i, j] im[i, j] = (0, 0,b) return im def filtre_vert(img_orig): im = np.copy(img_orig) # On fait une copie de l'original for i in range(im.shape[0]): for j in range(im.shape[1]): r, v, b = im[i, j] im[i, j] = (0, v,0) return im imgpil = Image.fromarray(filtre_rouge(img)) # Transformation du tableau en image PIL imgpil.save("resultat_rouge.jpg") imgpil = Image.fromarray(filtre_bleu(img)) # Transformation du tableau en image PIL imgpil.save("resultat_bleu.jpg")
Cela crée et enregistre les trois images suivantes:
Avec ce filtre:
def filtre(img_orig): im = np.copy(img_orig) # On fait une copie de l'original for i in range(im.shape[0]): for j in range(im.shape[1]): r, v, b = im[i, j] im[i, j] = (99-r, 99-v,99-b) return im
on obtient:
Flou gaussien
Le flou gaussien est un peu compliqué à obtenir, et quand bien même on dispose d’un script, il est très (très très très) long à donner un résultat. Voici le script que j’utilise:
def gaussKernel(r,sigma): kernel = [[0] * (2*r+1) for _ in range(2*r + 1)] if sigma > 0: gaussianKernelFactor, e = 0, 0 for ky in range(-r,r): for kx in range(-r,r): e = exp(-(kx**2+ky**2)/(2*pi*sigma**2)) gaussianKernelFactor += e kernel[kx+r][ky+r] = e for ky in range(-r,r): for kx in range(-r,r): kernel[kx+r][ky+r] = kernel[kx+r][ky+r] / gaussianKernelFactor return kernel def convolution(img,M): px = int( (len(M)-1) / 2 ) im = img.copy() imax = img.shape[1] - px for i in range(px,imax): for j in range(px,img.shape[0]-px): somme = 0.0 for k in range(-px,px+1): for l in range(-px,px+1): somme += img[j+l][i+k] * M[l+px][k+px] im[j][i] = somme return im imgpil = Image.open("image.jpg") img = np.array(imgpil) H = gaussKernel(3,10) imgpil = Image.fromarray(convolution(img,H)) imgpil.save("resultat_flou.jpg")
On définit d’abord le noyau de convolution (gaussKernel) en fonction du rayon de flou que l’on souhaite et d’une valeur sigma, qui intervient dans les calculs. C’est une matrice carrée. Ensuite, on définit la convolution basée sur cette matrice qui donnera le flou gaussien.
J’ai voulu mettre le paquet et ai pris un rayon égal à 7 (je suis un dingue moi! Faut pas me chercher !…), et un sigma de 0.8 (pourquoi pas ?)… mais je n’aurais jamais dû faire ça ! En effet, le programme met… 20 minutes à créer l’image pas très floutée au final! Je change donc mon rayon en 3 et sigma en 10, je lance le script à 19:47 et à 19:53, le résultat tombe:
6 minutes pour un flou très léger… Ce n’est pas très rentable!
Il existe cependant une autre méthode, bien plus rapide ! Utiliser un filtre de PIL :
from PIL import Image from PIL import ImageFilter from PIL.ImageFilter import ( GaussianBlur ) simg = Image.open('image.jpg') # defaut: radius=2 dimg = simg.filter(GaussianBlur(radius=10)) dimg.save("image_floue.jpg")
qui produit l’image suivante:
Et tout ceci en… moins de 2 secondes !
Vous trouverez ci-dessous un ZIP contenant les images et deux scripts réunissant tous les filtres.
Bonjour,
C’est pas plutôt ça ?
Car sinon la ligne suivante pour changer le pixel est fausse (ou l’inverse).
Non, nulle erreur, c’est juste que le deuxième exemple n’est pas en rapport direct avec le premier (mais c’est vrai que cela porte à confusion donc je vais changer ça!). J’aurais très bien pu prendre comme second exemple:
pour afficher la couleur de la ligne 788 et de la colonne 210.
Avec Python, il faut penser que les array de dimensions 2 sont de la forme A[y][x] (assez contre-intuitif pour les matheux !).
Cela dit, il y avait tout de même une erreur… Le triplet renvoyé n’est pas un triplet de pourcentages (comme indiqué initialement), mais un triplet (x y z), où x, y et z sont compris entre 0 et 255. Je vais rectifier cela !
bonjour , oui c’est très lent mais la double boucle for n’est pas recommandée . Tu peux jouer directement sur les canal de couleur. Ceci est beaucoup plus rapide exemple pour le filtre bleu :
image = Image.open("image-1024x261.jpg")
source = image.split()
R, G, B = 0, 1, 2
canalRougeZero = source[R].point(lambda i: 0 )
canalVertZero = source[G].point(lambda i: 0 )
source[R].paste(canalRougeZero) # On colle le canal rouge à zero
source[G].paste(canalVertZero) # On colle le canal vert à zero
image = Image.merge(image.mode, source)
image.save("resultat_bleu.jpg")
Tu peux aussi:
Rouge = source[R].point(lambda i: i - 99 )