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]

ce qui signifie qu’il y a 63% de rouge, 55% de vert et 42% de bleu.

Pour modifier la valeur d’un pixel, il suffit donc de changer sa valeur:

img[100,200] = (20,30,80)

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 sur cette page un ZIP contenant les images et deux scripts réunissant tous les filtres.

Stéphane Pasquet
Stéphane Pasquet

Laissez votre message

Supportscreen tag