Les images et Python

Les images et Python

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.

Cette partie est réservée aux abonné.e.s de ce site.
Si vous êtes abonné·e·s, merci de vous identifier pour visualiser le contenu dans son intégralité.
Stéphane Pasquet
Stéphane Pasquet

1 réflexion au sujet de « Les images et Python »

MartoniPublié le  10:37 - Sep 10, 2021

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).

    Stéphane Pasquet

    Stéphane PasquetPublié le  11:10 - Sep 10, 2021

    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:

    print(img[788,210])

    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 !

Laissez votre message