Les images et Python

  • Dernière modification de la publication :26 octobre 2021
  • Temps de lecture :11 min de lecture
  • Commentaires de la publication :3 commentaires

Loading

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 :

  1. nombre de lignes (en pixel)
  2. nombre de colonnes (en pixel)
  3. 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 !

Les abonné.e.s à mathweb.fr trouveront ci-dessous un ZIP contenant les images et deux scripts réunissant tous les filtres.

Image-python-filtresTélécharger
0 0 votes
Évaluation de l'article
S’abonner
Notification pour
guest
3 Commentaires
Le plus ancien
Le plus récent Le plus populaire
Commentaires en ligne
Afficher tous les commentaires
Martoni

Bonjour,

Par exemple, pour afficher le valeur du pixel qui est en ligne 100 et en colonne 200, j’écris:

print(img[200,100])

C’est pas plutôt ça ?

print(img[100,200])

Car sinon la ligne suivante pour changer le pixel est fausse (ou l’inverse).

u160v45

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 )