• 10 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 17/12/2019

Maîtrisez la construction d’histogrammes et réalisez des modifications simples

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Ce chapitre propose un certain nombre d'algorithmes de traitements d’image classiques ou amusants sous formes d’exercices pratiques, afin de vous plonger dans la marmite de pixels !

Convertissez une image couleur en noir & blanc (niveaux de gris)

Pour écrire un algorithme qui convertit une image couleur en niveaux de gris, exploitez la formule de luminance Y utilisée en télévision pour (cf. YUV, YIQ et YDbDr vu antérieurement) : Y=0,299.R+0,587.V+0,114.B

import cv2 as cv
import numpy as np
image = cv.imread("Lena.png")
b,v,r = cv.split(image) # récupère 3 matrices d'octets séparées pour R et V et B
y = 0.299*r + 0.587*v + 0.114*b # opération matricielle
y = y.astype(np.uint8) # convertit les réels en octets
cv.imshow("Luminance Y", y)
cv.waitKey(0)
cv.destroyAllWindows()

Remarques :

Calculez des histogrammes d’images

L’histogramme d’une image est le graphique qui représente le nombre de pixels existant pour chaque valeur. Calculer l’histogramme de l’image en niveaux de gris, c’est en d’autres termes compter combien il y a de pixels pour chaque nuance de gris.

Écrivez un algorithme pour calculer, dans un vecteur de taille 256, l'histogramme d'une image en niveaux de gris.

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt # utile pour les graphiques
# Convertit l'image en niveaux de gris Y
image = cv.imread("Lena.png")
b,v,r = cv.split(image) # récupère 3 matrices d'octets séparées
y = 0.299*r + 0.587*v + 0.114*b # opération matricielle
y = y.astype(np.uint8) # convertit les réels en octets
# Calcule l'histogramme de l'image
hist = np.zeros(256, int) # prépare un vecteur de 256 zéros (pour chaque gris)
for i in range(0,image.shape[0]): # énumère les lignes
for j in range(0,image.shape[1]): # énumère les colonnes
hist[y[i,j]] = hist[y[i,j]] + 1
print(hist)
plt.plot(hist)
plt.show()

Remarque :

Égalisez un histogramme

L’égalisation d’histogramme consiste à corriger une image qui manque de contraste : ses couleurs, ou ses niveaux de gris, se concentrent sur seulement quelques valeurs.

Cette transformation va faire une conversion de couleurs :

  • Afin d’utiliser toute l’étendue des niveaux ;

  • Afin d’avoir (à peu près) autant de pixels de chaque niveau.

L’astuce consiste à d’abord calculer l’histogramme cumulé (le principe est le même que pour l’histogramme, si ce n’est que pour toute valeur  i  on calcule non pas le nombre de pixels de cette valeur dans l’image, mais on cumule le nombre de pixels de valeur égale ou inférieure à  i  dans l’image).

Hawkes bay : image peu contrastée [source : Wikipédia]
Hawke's Bay : image peu contrastée [source : Wikipédia]
Hawkes bay : histogramme correspondant (en rouge) ainsi que l’histogramme cumulé (en noir) normalisé en échelle pour rester dans les limites du graphique [source : Wikipédia]
Hawke's Bay : histogramme correspondant (en rouge) ainsi que l’histogramme cumulé (en noir) normalisé en échelle pour rester dans les limites du graphique [source : Wikipédia].

Ensuite, on utilise cet histogramme cumulé directement comme une table de conversion des niveaux de gris (pour cela, on normalise les valeurs de l’histogramme cumulé pour les ramener entre 0 et 255). L’idée de cette conversion étant la suivante :

  • Tous les faibles niveaux inutilisés dans l’image (ici de 0 à 120 environ) seront ramenés à 0 ;

  • Tous les hauts niveaux inutilisés (ici d’environ 200 à 255) seront ramenés à 255 ;

  • Les seuls niveaux utilisés seront étalés entre 0 et 255 suivant la progression de l’histogramme cumulé (tout un niveau étant déplacé vers un nouveau niveau, ce qui intercalera des « trous » de niveaux non utilisés dans l’image finale).

Hawkes bay : image mieux contrastée grâce à l’égalisation de son histogramme [source : Wikipédia]
Hawke's Bay : image mieux contrastée grâce à l’égalisation de son histogramme [source : Wikipédia]
Le nouvel histogramme correspondant (en rouge, démontrant l’utilisation de toute l’étendue de la dynamique des niveaux du noir au blanc) ainsi que l’histogramme cumulé (en noir, démontrant la répartition régulière approximant une contribution
Le nouvel histogramme correspondant (en rouge, démontrant l’utilisation de toute l’étendue de la dynamique des niveaux du noir au blanc), ainsi que l’histogramme cumulé (en noir, démontrant la répartition régulière approximant une contributio

Écrivez un algorithme mettant en œuvre le principe précédent pour égaliser l'histogramme d'une image en niveaux de gris, afin d'en améliorer le contraste.

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
# Convertit l'image en niveaux de gris Y
image = cv.imread("Lena.png")
b,v,r = cv.split(image) # récupère 3 matrices d'octets séparées
y = 0.299*r + 0.587*v + 0.114*b # opération matricielle
y = y.astype(np.uint8) # convertit les réels en octets
cv.imshow("Luminance Y", y)
# Calcule l'histogramme de l'image
histo = np.zeros(256, int) # prépare un vecteur de 256 zéros
for i in range(0,image.shape[0]): # énumère les lignes
for j in range(0,image.shape[1]): # énumère les colonnes
histo[y[i,j]] = histo[y[i,j]] + 1
print(histo)
plt.plot(histo)
plt.show()
# Calcule l'histogramme cumulé hc
hc = np.zeros(256, int) # prépare un vecteur de 256 zéros
hc[0] = histo[0]
for i in range(1,256):
hc[i] = histo[i] + hc[i-1]
# Normalise l'histogramme cumulé
nbpixels = y.size
hc = hc / nbpixels * 255
print(hc)
plt.plot(hc)
plt.show()
# Utilise hc comme table de conversion des niveaux de gris
for i in range(0,y.shape[0]): # énumère les lignes
for j in range(0,y.shape[1]): # énumère les colonnes
y[i,j] = hc[y[i,j]]
cv.imshow("Luminance Y après égalisation", y)
cv.waitKey(0)
cv.destroyAllWindows()

Remarques pour les images couleur :

Cachez une image dans une autre

L’idée va être d’exploiter le fait qu’une modification d’un niveau (de gris, ou de n’importe quelle autre couleur) de seulement 1 unité n’est pas visible à l’œil.

Ainsi, nous pourrons remplacer arbitrairement le bit de poids faible (celui des unités) de chaque pixel de l’image originale par 1 bit du pixel correspondant à une autre image (qui sera donc binaire, monochrome) sans que cela n'affecte l’œil. Ainsi, le pixel résultat portera 2 informations :

  • Une « approximation » du pixel original, au pire éloignée de 1 unité ;

  • Le bit du pixel de l’image binaire à dissimuler.

Parmi les 3 plans RVB, on choisira ici de modifier les bits du plan B, puisque c’est plutôt dans cette gamme que notre œil est le moins sensible.

Écrivez l’algorithme d’encodage pour cacher l’image ci-dessous dans la photo de Lena

(les fonctions  split  et  merge  d’OpenCV sont une piste possible).

Image du message à cacher dans une autre image
Image du message à cacher dans une autre image 

Algorithme d’encodage

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
# Cache un message d'information (en Noir & Blanc) dans une image
image = cv.imread("Lena.png")
message = cv.imread("message.png",0)
b,v,r = cv.split(image) # récupère 3 matrices d'octets séparées
b = b & 0b11111110 # efface le bit de poids faible des octets de B
b = b | (message > 0) # ajoute le bit de de poids faible en fonction du message
cache = cv.merge((b,v,r)) # reconstruit une image à partir des 3 plans RVB
cv.imwrite("cache.png", cache)
cv.imshow("Image avec contenu caché", cache)
cv.waitKey(0)
cv.destroyAllWindows()

Écrivez l’algorithme de décodage pour afficher l’image cachée à partir de votre image issue de l’algorithme précédent.

Algorithme de décodage

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
image = cv.imread("cache.png")
# Extrait le contenu caché en bit de poids faible de la composante B
b,v,r = cv.split(image) # récupère 3 matrices d'octets séparées
cache = b & 1 # extrait le 1er bit (de poids faible)
cache = cache * 255 # multiplie par 255 pour visualiser du noir ou du blanc
cv.imshow("Contenu caché", cache)
cv.waitKey(0)
cv.destroyAllWindows()

Nous pouvons essayer de rendre le message caché encore plus discret : on peut « brouiller » un peu plus ce bit qui a été modifié, tout simplement en le « mélangeant » avec un bit voisin (par exemple celui de la composante verte).

Le principe va être le suivant :

  • On recopie le bit de poids faible de la composante verte à la place du bit de poids faible de la composante b si le bit à cacher est 0 ;

  • On recopie l’inverse du bit vert si le bit à cacher est 1.

Ce résultat final est un « ou exclusif » (XOR en anglais) entre le bit à cacher et celui du vert.

Écrivez le nouvel algorithme d’encodage pour cacher l’image selon ce procédé.

En fait, il suffit de modifier :

b = b | (message > 0) # ajoute le bit de de poids faible en fonction du message

en

b = b | (message > 0)^(v&1) # bit de de poids faible fonction du message et de V

Comment décoder ? Car si on ne modifie rien, vous obtiendrez l’image suivante ne montrant rien de reconnaissable (appelée « neige » en télévision, quand on ne capte rien).

Image de « neige » lorsque rien d'intelligible n'est décodé
Image de « neige » lorsque rien d'intelligible n'est décodé

Écrivez l’algorithme modifié pour découvrir le véritable message que nous avons caché dans l’image PNG de Lena ci-dessous : 

Image de Lena transformée pour contenir un message caché à retrouver !
Image de Lena transformée pour contenir un message caché à retrouver !

Là encore il suffit de changer 1 ligne, dans la formule de décodage de la version précédente plus simple :

cache = b & 1 # extrait le 1er bit (de poids faible)

devient

cache = (b & 1)^(v & 1) # extrait le 1er bit de B modulé par le 1er bit de V

Alors, avez-vous découvert le message caché dans la dernière image ?

Exemple de certificat de réussite
Exemple de certificat de réussite