Images numériques: Introduction

Introduction

Un image numérique est stocké dans l'ordinateur sous forme d'un tableau (matrice), les éléments de ce tableau sont appelés pixel. Un pixel stocke une couleur.
Si $T$ est le tableau représentant l'image numérique, on note $Nl$ le nombre de lignes dans ce tableau et $Nc$ le nombre de colonnes, alors le tableau contient $Nl\times Nc$ pixels. On parle alors de la résolution de l'image.

Image en niveau de gris

Pour les image en niveau de gris, chaque pixel de l'image est un entier entre $0$ et $255$, ce qui fait $256$ possibilités pour un pixel.
La valeur 0 correspond au noir, et la valeur 255 correspond au blanc. Les valeurs intermédiaires correspondent à des niveaux de gris allant du noir au blanc.

image en gris
Exemple d'une image en gris.
Exercice

Calculer l'espace nécessaire sur le disque dure de l'ordinateur pour stocker une image de taille $Nc\times Nl$ pixels.

Image en couleur

Une image couleur est en réalité composée de trois images, afin de représenter le rouge, le vert, et le bleu.
CS

Chacune de ces trois images s'appelle un canal. Cette représentation en rouge, vert et bleu mime le fonctionnement du système visuel humain.
Chaque pixel de l'image couleur contient ainsi trois nombres (r,v,b), chacun étant un nombre entier entre 0 et 255. Si le pixel est égal à (255,0,0), il ne contient que de l'information rouge, et est affiché comme du rouge. De façon similaire, les pixels valant (0,255,0) et (0,0,255) sont respectivement affichés vert et bleu.
On peut afficher à l'écran une image couleur à partir de ses trois canaux en utilisant les règles de la synthèse additive des couleurs. La figure ci-contre montre les règles de composition cette synthèse additive des couleurs. Un pixel avec les valeurs (255,0,255) est un mélange de rouge et de vert, il est ainsi affiché comme jaune.

La figure ci-après montre la décomposition d'une image couleur en ses trois canaux constitutifs.

$\quad$
Image en couleur
Image en couleur.
$\quad$
Canal rouge
Canal rouge.
$\quad$
Canal bleu.
Canal bleu.
Canal vert
Canal vert.

Traitement d'image avec Python

On peut faire de traitement d'image avec Python, pour cela il faut charger la bibliothèque PIL (Python Imaging Library), qui permet d'avoir des fonctions supplémentaire et rendre le travail plus facile.

Création d'une image

Pour créer une image avec Python on utilise la fonction Image.new(mode,size), avec

  • mode = "L" ou "RGB", définit si l'image sera en niveau de gris ou couleur.
  • size= (NbColonnes,NbLignes) définit la taille de l'image

La fonction Image.putpixel($(i,j)$,Valeur) permet de changer la couleur de pixel $(i,j)$ avec ( $0\leq i< $ NbColonnes et $0\leq j< $ NbLignes) et Valeur est

  • entier entre 0 et 255 dans le cas d'une image en niveau de gris
  • triplet d'entiers $(a,b,c)$ tels que $ 0\leq a,b,c\leq 255$ dans le cas d'une image couleur

L'exemple suivant montre comment on crée une image en niveau de gris,

				from math import *
from PIL import Image
im = Image.new("L",(40,40))  # dimension de l'image
im.putpixel((20,20),255)     # changer la couleur d'un pixel
im.show()
for i in range(10,31):
	im.putpixel((i,10),255)  # travailler sur une ligne
im.show()                    # visualiser l'image
im.save("im1.jpg")           # sauvegarder la nouvelle image
				
				

Et voici le résultat ce premier essais


1ère essais

Le code suivant permet de créer une image en couleur, puis changer les couleur de l'image pixel par pixel

				from PIL import Image
im = Image.new("RGB",(100,100))  # image en couleur
for j in range(100):            # j parcourt les colonnes
	for i in range(100):        # i parcourt les lignes
		im.putpixel((j,i),(255-i,255+j,i+j))  # mettre des couleurs
im.show()
im.save("im2.jpg")            # sauvegarder la nouvelle image
				
				

Et voici le résultat ce deuxième essais


2eme essais
Exercice

CS
En s'inspirant des exemples ci-dessus, créer une image en couleur qui contient un rectangle et un triangle comme ci-après.

Ouvrir une image

La fonction Image.open(chemin) permet d'ouvrir une image, la fonction Image.size(image) donne les dimension de l'image (donc deux entiers).
La fonction get.pixel($i,j$) donne la valeur (ou les valeurs) du pixel $(i,j)$.

				from PIL import Image
im = Image.open("ImC4.png")
# L'image doit être dans le même répertoire que le fichier python
NbC,NbL = im.size
print("Les dimension de l'image sont %d %d"%(NbC,NbL))
p = im.getpixel((1,1))
print(p[0],p[1],p[2])
im2 = Image.new('RGB',(NbC,NbL))
# nouveau image de même dimension que l'image ouverte
for j in range(NbC):
    for i in range(NbL):
        p = im.getpixel((j,i))
        im2.putpixel((j,i),(255-p[0],255-p[1],255-p[2]))
im2.show()
im2.save("ImC4modifie.png")
				
				

Voici le résultat,

$\quad$
Image initiale
Image initiale.
$\quad$
Image modifiée
Image modifiée.
$\quad$
Exercice

Copier l'image ImC4.png dans votre répertoire. A l'aide de la fonction Image.resize((NC,NL)) créer des nouvelles images à partir de l'image initiales avec des dimensions différentes.

Exercice

Couper l'image ImC4.png en quatre images de dimensions identiques.

Exercice

En utilisant la fonction Image.rotate($\theta$) qui permet de faire une rotation de l'image, créer des nouvelles images de ImC4.png en faisant différents rotations.

Opérations sur une image

Créer une image en niveau de gris

A partir d'une image en couleur (chaque pixel est représenté par 3 valeurs [r,g,b]), on peut créer une image en niveau de gris.
Pour cela, chaque pixel dans la nouvelle image est calculé en moyennant les trois valeurs de même pixel dans l'image couleur.

				from PIL import Image
im= Image.open("Grenade02.jpg")
L,H = im.size
# Création d'une nouvelle image 
#(de même dimension)
imG = Image.new("L",(L,H))
for y in range(H):
    for x in range(L):
        p = im.getpixel((x,y))  
        # triplet pour chaque pixel
        a = (p[0]+p[1]+p[2])//3 
        # moyenner les trois valeurs
        imG.putpixel((x,y),a)
imG.save("GR_GRIS_G.png")
imG.show()
				
				
$\quad$
Image en couleur
Image en couleur.
$\quad$
En niveau de gris
En niveau de gris.
$\quad$

Seuillage

Le seuillage pour une image en niveau de gris consiste à mettre en 0 (donc noir) tout les pixels ayant un niveau de gris inférieur à une certaine valeur. Le seuillage permet de mettre en évidence des formes ou des objets dans une image.


$\quad$
Image initiale
Image initiale (gris).
$\quad$
Seuil 100
Seuil 100.
$\quad$
Seuil 100
Seuil 170.

Assombrissement, éclaircissement

Assombrissement: Il s'agit de rendre une image (en niveau de gris) très claire plus sombre, pour cela, il faut agir sur les niveaux de gris des pixels de sorte que,

  1. le noir (resp. blanc) reste noir (resp. blanc)
  2. le gris finale doit être plus sombre que le gris initial
  3. respect des contrastes
Autrement dit, il faut chercher une fonction $f:[0,1]\longmapsto [0,1]$ telle que $f(0)=0,\,f(1)=1$, $f$ est croissante et $f(x)\leq x$ pour tout $x\in [0,1]$.
L' éclaircissement d'une image sombre est l'opération inverse de l'assombrissement. Je vous laisse le soin de trouver les caractéristique de la fonction $f$ à appliquer!


$\quad$
Image initiale
Image initiale (gris).
$\quad$
Éclaircissement.
Éclaircissement.
$\quad$
Assombrissement
Assombrissement.
Exercice (Accentuer les contraste)

On cherche à modifier une image en niveau de gris, de sorte que:

  1. Le blanc (resp. noir ) doit rester blanc (resp. noir).
  2. Les nuances proches du noir (resp. du blanc) doivent être assombries (resp. éclaircies).

Chercher une fonction $f$ répondant à cette demande puis écrire le programme.

Contour

La détection de contours permet de repérer les pixels de l'image qui correspondent à une variation importante de l'intensité lumineuse.
Le but alors est de transformer une image en niveau de gris en une nouvelle image dans laquelle les contours apparaissent (par convention en blanc sur fond noir). Ceci est très utile car il permet de détecter les formes, objets,.. dans une image.

				from tkinter import *
from tkinter.messagebox import *
from tkinter.colorchooser import *
from tkinter.filedialog import *
from PIL import Image
from os import path, system

result = askopenfilename(title="Ouvrir une image  :",
                        filetypes=(("Image", "*.png"),("Tout fichier", "*")))
im1= Image.open(result)
im1.show()
L,H =im1.size

im2 = Image.new("L",(L,H))
im3 = Image.new("L",(L,H))

for y in range(1,H-1):
    for x in range(1,L-1):
        pix0 = im1.getpixel((x,y))         # stockage du pixel courant
        pix1 = im1.getpixel((x-1,y-1))     # de son voisin en haut à gauche
        pix2 = im1.getpixel((x,y-1))       # de celui du dessus
        pix3 = im1.getpixel((x+1,y-1))     # d'en haut à droite
        pix4 = im1.getpixel((x-1,y))       # de gauche
        pix5 = im1.getpixel((x+1,y))       # de droite
        pix6 = im1.getpixel((x-1,y+1))     # d'en bas à gauche
        pix7 = im1.getpixel((x,y+1))       # d'en-dessous
        pix8 = im1.getpixel((x+1,y+1))     # d'en bas à droite
        r = 8*pix0-pix1-pix2-pix3-pix4-pix5-pix6-pix7-pix8
        # on applique le filtre sur la composante rouge du pixel courant,
        s = r
        #méthode 1 : 
        #On décale de 128 et on réalise une inversion ("négatif")
        r = r+128
        r = int(255-r)
        im2.putpixel((x,y),r)
        #méthode 2 : 
        #On s'assure que s entre 0 et 255 et on réalise une inversion ("négatif")
        if s < 0: s = 0
        if s > 255: s = 255
        s = 255-s
        im3.putpixel((x,y),s)
im2.save("CE_Contour1.png")
im3.save("CE_Contour2.png")

$\quad$
Image initiale
Image initiale (gris).
$\quad$
Contour (méthode 1).
Contour (méthode 1).
$\quad$
Contour (méthode 2)
Contour (méthode 2).

Stéganographie

Ainsi, la stéganographie consiste à dissimuler une image, en particulier au format .PNG (Portable Network Graphics), ou un texte dans une deuxième image.
Cependant il est nécessaire que cette dissimulation s'effectue sans trop dégrader la qualité de cette deuxième image afin de ne pas éveiller de soupçons.
Dès lors en considérant les composantes rouge, verte et bleue des pixels d'une image au format .PNG, on remarque que chacune de ces composantes est décrite à l'aide de huit bits. Par exemple, les huit bits de la composante verte d'un pixel d'une image peut être 10101110; ce nombre est en fait le code binaire du nombre 174 ; en effet : $$ 1\times 2^7+0\times 2^6+1 \times 2^5+0 \times 2^4+1 \times 2^3+1 \times 2^2+1 \times 2^1+0 \times 2^0= 174.$$ Afin de dissimuler une information dans une image, il est possible de vérifier qu'en ignorant ou modifiant les quatre bits de poids faible des composantes verte, rouge et bleue de chaque pixel, c'est-à-dire ceux associés aux puissances de deux les plus faibles, la qualité de l'image n'est pas sévèrement altérée.
Ainsi, nous pouvons réduire sans perte majeur d'informations, les composantes verte, rouge et bleue de chaque pixel d'une image respectivement à leurs quatre premiers bits, appelés les quatre bits de poids fort.
Donc il est possible de dissimuler une première image dans une deuxième image, de même taille, en remplaçant les quatre bits de poids faible des composantes verte, rouge et bleue de chaque pixel de la deuxième image par, respectivement, les quatre bits de poids fort des composantes verte, rouge et bleue de chaque pixel de la première image.
Enfin pour dissimuler un texte dans une image, il suffit de coder un caractère de ce texte en code ASCII (American Standard Code for Information Interchange). En effet, le code ASCII d'un caractère est un nombre compris entre 0 et 255. Or le code 255 est associé au code binaire 11111111, et tous les autres nombres compris entre 0 et 254 peuvent aussi s'écrire en base deux à l'aide de huit chiffres. Donc il est possible de dissimuler le caractère d'un texte dans une image, en remplaçant les quatre bits de poids faible de la composante verte par les quatre bits de poids fort du code binaire du code ASCII de ce caractère, et en remplaçant les quatre bits de poids faible de la composante rouge par les quatre bits de poids faible du code binaire du code ASCII de ce caractère. Sachant cacher le caractère d'un texte dans un pixel, il est dès lors possible de dissimuler un texte dans une image dont le nombre de pixel est strictement plus grand que la longueur de ce texte.
Ainsi pour cacher une image $A$ dans une image $B$ on travaille pixel par pixel. Pour chaque pixel, on procède de la façon suivante :

  1. $\mathbf{1.\,}$ Pour chaque composante (R,V,B) de ce pixel de l'image $B$, les 4 bits de poids faible seront mis à $0$.
  2. $\mathbf{2.\,}$ Pour chaque composante (R,V,B) de ce pixel de l'image $A$, les 4 bits de poids fort seront décalés à droite.
  3. $\mathbf{3.\,}$ Chaque composante (R,V,B) de ce pixel de l'image résultat sera la somme des nouvelles composantes (R,V,B) de l'image $A$ et $B$.

i.e. Les 4 bits de poids fort de chaque composante (R,V,B) de l'image résultat sont les 4 bits de poids fort de l'image $B$ et les 4 bits de poids faible sont les 4 bits de poids fort de l'image $A$.
Inversement, pour trouver une image cachée dans une image donnée $C$, la démarche est très simple. On travaille également pixel par pixel. Pour chaque pixel de l'image $C$, et pour chaque composante (R,V,B) de ce pixel: on récupère les bits de poids faible puis on les décale à gauche, i.e. les 4 bits de poids faible de l'image $C$ deviennent les 4 bits de poids fort de l'image résultat.

Exemple 1
$\quad$
L'image qui cache
L'image qui cache.
$\quad$
L'image à cachée.
L'image à cachée.

Et voici le résultat obtenu.

L'image à cachée.
Résultat final
Exemple 2
$\quad$
L'image qui cache
L'image qui cache.
$\quad$
L'image à cachée.
L'image à cachée.

Et voici le résultat obtenu.

L'image à cachée.
Résultat final
Exercice

Écrire un programme en Python permettant de cacher une image dans une deuxième image (de même taille) et de chercher une image cachée dans une image.