Thème 7 : Photographie Numérique

Éléments historiques

1515 Léonard de Vinci

Mise au point de la chambre noire « camera obscura »
Boite noire permettant de projeter le sujet sur un support qu'un peintre peut alors reproduire.

1821 Nicéphore Niepce

Invention du procédé photographique
Utilisation de résine d'origine minérale : l'asphalte ou bitume de Judée.

1826 Nicéphore Niepce

Première photographie noir & blanc
Chambre noire avec bitume. Temps de pose de plusieurs jours

1884 Georges Eastman (Kodak)

Invention de la pellicule
Film photographique transparent en rouleaux sensibles à la lumière grâce au bromure d'argent : Argentique

1906 Auguste et Louis Lumière

Première vraie photographie en couleur
Autochromes à base de fécule de pomme de terre.

1935 Leopold Mannes et Leopold Godowsky Jr

Apparition des premières pellicules couleur
Trois couches sensibles respectivement au rouge, au bleu et au vert.

1969 Willard Boyle et George E. Smith

Invention du capteur CCD
Transfert de charges électriques par effet photoélectrique.

1975 Steven Sasson

Premier appareil photo numérique
Masse de 3,6 kg, image de 100x100 pixels, temps d'enregistrement de 23 secondes.

1999 NIKON D1

Premier réflex numérique
Capable de prendre de photo de 2,7 mégapixels à une vitesse de 4,5 images/seconde.

2005 Nokia N90

Premier téléphone faisant appareil photo numérique
Il embarque un capteur de 2 mégapixels avec optique Carl Zeiss, autofocus et flash LED.

Les images numériques

Les réseaux sociaux tels que Snapchat proposent des filtres permettant entre autre de modifier les visages en y ajoutant des accessoires ou des effets. Comment peut-on faire ce type de traitements en utilisant le langage Python ?

Comprendre les images numériques

(possibilité de visionner : sur Lumni)

Que se passe-t-il si on zoome sur une image ? Les points observés sont appelés pixels. On dit que l'image est matricielle car en mathématiques une matrice désigne un tableau de nombres.

Remarque : On parle aussi de «carte de points» (de l'anglais «bitmap»)

Le principe physique

Voici l'agrandissement de l'image d'un écran LCD (Liquid Cristal Display). On peut distinguer les pixels qui la composent. Si on agrandit encore l'image, on peut voir que chaque pixel est subdivisé en 3 rectangles (un rouge, un vert et un bleu) : ce sont les sous-pixels (ou luminophores).

L'œil et le cerveau réalisent une synthèse additive des couleurs. Toute lumière colorée peut être reproduite en superposant, dans certaines proportions, trois faisceaux lumineux de couleurs rouge, verte et bleue (nos yeux ne sont sensibles qu'à ces trois couleurs).

Codage des couleurs

Chaque pixel est codé par trois valeurs, une pour la composante rouge, une autre pour la composante verte et une troisième pour la composante bleue. On parle de code RVB. Chacune de ces trois couleurs peut prendre 256 valeurs différentes (de 0 à 255).

Pour pouvoir coder un nombre jusqu'à la valeur 255, il faut 8 bits, soit un octet à un ordinateur. L'octet 00000000 correspond à la valeur décimale 0 tandis que l'octet 11111111 correspond à la valeur décimale 255. Ainsi pour coder les trois couleurs d'un pixel, il faut 3 octets soit 24 bits.

Pour chacun des pixels, donner son code RVB.
  1. code RVB d'un pixel rouge :
  2. code RVB d'un pixel magenta :
  3. code RVB d'un pixel cyan :
  4. code RVB d'un pixel jaune :
  5. code RVB d'un pixel gris :
Compléter les phrases suivantes :
La profondeur de couleur est le nombre de bits associés à chaque pixel. Dans le cas présenté ici, la profondeur de couleur est de bits.
Lorsque les trois couleurs ont la même valeur (par exemple 125 125 125), le pixel est .
Avec 256 nuances de rouge, 256 nuances de vert et 256 nuances de bleu, il est possible de produire nuances de couleurs différentes.

Traiter les images avec le langage Python

Télécharger cette archive et la décompresser dans votre répertoire de travail.
Elle contient les images et scripts nécessaires pour traiter les parties suivantes.

Ouverture et définition de l'image

Code 1 :

from PIL import Image
img = Image.open("sernin.jpg")
largeur, hauteur = img.size
print("largeur de l'image :", largeur, "pixels")
print("hauteur de l'image :", hauteur, "pixels")
    
sernin.jpg
Description des lignes du programme ci-dessus :
  1. Lancer le programme code_01.py pour observer le résultat.
  2. Modifier le code pour afficher les dimensions de l'autre image disponible nommée chat.jpg.
  3. La définition de l'image est le nombre de pixels qui la compose. Modifier le code afin de calculer la définition de l'image.
image chat.jpg

from PIL import Image
img = Image.open("chat.jpg")
largeur, hauteur = img.size
definition = largeur*longueur
print("largeur de l'image :", largeur, "pixels")
print("hauteur de l'image :", hauteur, "pixels")
print("définition de l'image :", definition, "pixels")
    

Modification des pixels de l'image

En python, le pixel de coordonnée (0, 0) est en haut à gauche.
Code 2 :

from PIL import Image
img = Image.open("couleur.png")
r, v, b = img.getpixel((0, 0))
print("RVB :", r, v, b)
    
Description des lignes du programme ci-dessus :
  • Ligne 1 : Importe la bibliothèque PIL (ou Pillow) permettant de traiter les images.
  • Ligne 2 : Ouvre le fichier image couleur.png et l'affecte à la variable img.
  • Ligne 3 : Extraction des valeurs des trois composantes du pixel (0, 0) soit le pixel de la 1re colonne et de la 1re ligne.
  • Ligne 4 : Affiche le résultat dans la console.
couleur.png
  1. Vérifier que le pixel de coordonnée (0, 0) est bien en haut à gauche.
  2. Modifier le code afin de retrouver le code RVB des 5 autres zones de couleurs de l'image (l'image est de 30×30 pixels).

from PIL import Image
img = Image.open("couleur.png")
for pixel in [(0,0), (15,0), (30,0), (0,30), (15,30), (30,30)] :
    r, v, b = img.getpixel(pixel)
    print("pixel :", pixel)
    print("RVB :", r, v, b)
Code 3 :

from PIL import Image
largeur = 200
hauteur = 100
img = Image.new('RGB', (largeur, hauteur))

# Balayage de tous les pixels de l'image
for y in range(hauteur): # Balayage des lignes
    for x in range(largeur): # Balayage des colonnes
        img.putpixel((x, y), (255, 0, 0))

img.show()
img.save("rectanglerouge.png", "PNG")
Description des lignes du programme ci-dessus :
  1. Modifier le code pour créer une image bleue.
  2. Modifier le code pour créer une image jaune.
  3. Modifier le code pour colorer en rouge seulement la moitié supérieure de l'image. (Pour obtenir la division entière d'un nombre en python, il faut utiliser le double-slash //)
  1. 
        from PIL import Image
    largeur = 200
    hauteur = 100
    img = Image.new('RGB', (largeur, hauteur))
    
    for y in range(hauteur): 
        for x in range(largeur): 
            img.putpixel((x, y), (0, 0, 255))
    
    img.show()
    img.save("rectanglebleu.png", "PNG")
    
  2. 
    from PIL import Image
    largeur = 200
    hauteur = 100
    img = Image.new('RGB', (largeur, hauteur))
    
    for y in range(hauteur): 
        for x in range(largeur): 
            img.putpixel((x, y), (255, 255, 0))
    
    img.show()
    img.save("rectanglejaune.png", "PNG")
    
  3. 
        from PIL import Image
    largeur = 200
    hauteur = 100
    img = Image.new('RGB', (largeur, hauteur))
    
    for y in range(hauteur//2): 
        for x in range(largeur): 
            img.putpixel((x, y), (255, 0, 0))
    
    img.show()
    img.save("rectanglerougeamoitie.png", "PNG")
    
Compléter les phrases suivantes :
L'origine des axes pour une image en Python est .
La bibliothèque permet de manipuler des images en Python.
Quelle instruction permet de récupérer les dimensions (en pixel) de l'image img ?
Quelle instruction permet de récupérer les composantes colorées du pixel de coordonnée (0, 0) de l'image img ?
Quelle instruction permet de colorer en rouge le pixel de coordonnée (0, 0) de l'image img ?

Application : Conversion en niveaux de gris

Une technique pour convertir une image en niveau de gris est de réaliser la moyenne des trois composantes RVB du pixel et d'affecter cette moyenne aux trois composantes RVB.

Attention : Les composantes doivent être des nombres entiers. Pour cela il y a toujours le double-slash // pour réaliser la division entière ou bien la fonction int() qui renvoie la partie entière de ce qui lui ait donné en argument.

Code 4 :

from PIL import Image
img = Image.open("occitanie.jpg")
largeur, hauteur = img.size
for y in range(hauteur):
    for x in range(largeur):
        r, v, b = img.getpixel((x, y))
        r = r # Ligne à modifier
        v = v # Ligne à modifier
        b = b # Ligne à modifier
        img.putpixel((x, y),(r, v, b))
img.show()
img.save("image_modif.png", "PNG")
    
  • Modifier les lignes 7, 8 et 9 du code ci-dessous afin de convertir l'image colorée en image en niveau de gris.
  • Modifier les lignes 7, 8 et 9 du code ci-dessous pour réaliser d'autres effets sur l'image.

from PIL import Image
img = Image.open("occitanie.jpg")
largeur, hauteur = img.size
for y in range(hauteur):
    for x in range(largeur):
        r, v, b = img.getpixel((x, y))
        g = (r+v+b)//3
        img.putpixel((x, y),(g, g, g))
img.show()
img.save("image_modif.png", "PNG")
    

Application : Presque comme Snapchat !

Pour faire presque comme Snapchat, programmer son propre filtre Snapchat permettant d'ajouter une auréole sur la tête du chat.
Pour cela, il faut :
  • ouvrir les deux images "aureole.jpg" et "chat.jpg". Ces deux images ont la même définition de 300x366 pixels.
  • copier les 91 premières lignes de pixels de l'image aureole.jpg (soit 1/6 de sa hauteur) sur l'image chat.jpg.
chat aureole
chat.jpg et aureole.jpg

Ce qu'il faut retenir :

Attention : Une image de 100 pixels en hauteur est représentée par un tableau de 100 lignes numérotées de 0 à 99.

Bonus : détection de contours

Quand une voiture autonome est en mouvement, il est important qu'elle sache reconnaître les objets qui l'entourent : véhicules, piétons, cyclistes, les panneaux de signalisation. Pour suivre la route, elle doit savoir en particulier reconnaître les bordures, les lignes blanches, les trottoirs, etc.

Pour cela, les photos réalisées par ses capteurs sont traitées par des algorithmes qui permettent de détecter les contours des objets qu'elle rencontre. L'objectif de cette activité est de découvrir le fonctionnement d'un de ces algorithmes de détection de contours.

 
Qu'est-ce qu'un contour ?
  1. Repérer et sélectionner les pixels appartenant au contour de la ligne blanche sur l'agrandissement de l'image en niveaux de gris ci-contre.
  2. Proposer une méthode permettant de déterminer si un pixel appartient à un contour.
Pixels voisins

Pour repérer la position d'un pixel, on repère d'abord la colonne x à laquelle il appartient, puis la ligne y. Notons un tel pixel P(x, y) et NG(x, y) son niveau de gris.

Pour détecter si le pixel P(x, y) appartient ou non à un contour, la méthode consiste à chercher une rupture d'intensité entre 2 pixels symétriques par rapport à P(x, y), appelés pixel voisins. Pour cela on calcule la différence entre les niveaux de gris de 2 pixels voisins. Si cette différence est assez élevée, le pixel fait parti du contour.

Attention : En python le premier pixel en haut à gauche a pour coordonnées (0, 0) et non (1, 1).


Sélectionner les coordonnées des pixels voisins du pixel (x,y).
(x,y)
  1. La différence de niveaux de gris entre les 2 pixels voisins du pixel P(1, 1) vaut tandis que pour le pixel P(1, 3) elle vaut .
  2. Ainsi c'est le pixel qui semble appartenir à un éventuel contour.
Mesure d'une discontinuité avec 4 pixels voisins

Dans l'exemple précédent, seul un couple de pixels voisins est pris en compte dans une unique direction. Pour détecter des contours sur deux directions différentes, il faut prendre en compte deux couples de pixels voisins (voir schéma).
Pour cela, il faut calculer pour chacun la différence des niveaux de gris et afin de pouvoir les ajouter sans que les différences s'annulent du fait des valeurs négatives, il faudra élever leur valeur au carré.

Pour le pixel P(2, 1), la valeur obtenue est tandis que pour le pixel P(1,3), elle est de .
Afin de retenir qu'un seul de ces deux pixels comme appartenant à un contour, il est possible de choisir le seuil de .

Programmation de la détection de contours dans une image
L'algorithme permettant d'afficher les contours des objets sur une image a été partiellement programmé en python dans l'éditeur ci-dessous.


from PIL import Image

def contour(filename): # Crée la fonction contour
    im = Image.open(filename) # Ouvre le fichier image
    im_contour = Image.new(im.mode, im.size) # Crée une image vide de même taille
    colonne, ligne = im.size
    for y in range(1, ligne-1): # Boucles pour balayer les pixels sauf en bordure
        for x in range(1, colonne-1):
            a = im.getpixel((x-1, y-1)) # pixel au dessus à gauche
            b = im.getpixel(( ... , ... )) # pixel en dessous à droite
            c = ... # pixel au dessus à droite
            d = ... # pixel en dessous à gauche
            formule = (a-b)**2 + (c-d)**2
            if formule > 800: # Test de contour Si le résultat est supérieur à 800
                im_contour.putpixel((x, y), 0) # Colore en noir
            else : # Sinon
                im_contour.putpixel((x, y), 255) # Colore en blanc
    return im_contour # Renvoi l'image avec les contours

im = contour("route_01.png") # Appele la fonction pour l'appliquer
im.show() # Affichage de l'image
im.save("contour.png","PNG") # Sauvegarde de l'image
Description des lignes du programme :
  • Ligne 3 : Définition d'une fonction avec le mot clé def. Cette fonction aura besoin d'un argument filename qui correspond au nom du fichier à charger pour ouvrir l'image. Toutes les lignes indentées (décallées par rapport à la marge) sont contenues dans la fonction. Il s'agit des lignes 4 à 18.
  • Ligne 5 : Permet de créer une image vide nommée im_contour ayant les mêmes caractéristiques (niveaux de gris et taille) que l'image im.
  • Lignes 14 à 17 : Test permettant de compléter l'image vide im_contour avec un pixel noir s'il s'agit d'un pixel de contour ou un pixel blanc dans le cas contraire.
  • Ligne 18 : Le mot clé return permet de définir ce que va retourner la fonction comme résultat. Il s'agit de la dernière ligne de la fonction contour. Ici, ce qui est renvoyé est l'image dans laquelle a été tracé les contours : im_contour.
  • Ligne 20 : Une fois que la fonction a été créée, elle n'est pas exécutée. Pour qu'elle s'applique réellement, il faut l'invoquer en lui donnant tous les arguments nécessaire. C'est ce que réalise cette ligne 20. Le résultat de la fonction est affecté à la variable im.

Cliquer sur les images pour les télécharger
Image traitée
  1. Après s'être approprié le code, compléter la ligne 13 afin de réaliser le bon calcul à partir des valeurs en niveaux de gris des pixels voisins.
  2. Modifier le code pour effectuer le même traitement sur la deuxième image disponible.

Comment les images sont-elles capturées ?

Le capteur de l'appareil photo

Les appareils photo numérique sont équipés de capteurs photographiques composés d'une matrice d'éléments qui réagissent à la quantité de lumière qu'ils reçoivent. Ce sont les photosites.

Le filtre de Bayer fait en sorte qu'un photosite ne reçoive qu'une seule couleur primaire. Il y a deux fois plus de photosites exposés au vert pour être adapté à l'œil humain qui est plus sensibles au vert qu'au rouge ou au bleu.

Le capteur est recouvert d'un filtre de Bayer
Les photosites
  • Lorsqu'on photographie un rectangle blanc tous les photosites sont activés.
  • Pour un rectangle vert seuls les photosites sensibles au vert sont activés.
  • Pour un rectangle rouge seuls ceux sensibles au rouge sont activés.
  • Pour un bleu seuls les bleus sont activés.
Photosites

Attention : photosite ≠ pixel
Un photosite ne correspond pas à un pixel car il ne contient l'information que d'une seule couleur (Rouge ou Verte ou Bleue) alors qu'un pixel est codé avec trois valeurs. Le processeur d'image traite les données des photosites pour compléter l'image. En effet, pour chaque photosite, deux canaux colorés sont manquants. Une des techniques utilisées est de calculer la moyenne des valeurs obtenues par les photosites adjacents pour extrapoler les valeurs des deux canaux manquants.

En utilisant les informations des photosites rouges et bleus à proximité d'un photosite vert, le processeur compose un pixel, avec ses trois couleurs. L'image brute ainsi obtenue en sortie du capteur est souvent au format RAW.

En lumière faible, il est souvent nécessaire d'utiliser plus de photosites pour recomposer un seul pixel ce qui conduit à une perte de définition de l'image.

Détermination des valeurs R0, V0 et B0 du pixel central à partir des valeurs adjacentes.
  1. Un capteur photographique contient des .
  2. Afin de correspondre à la vision humaine, il y a deux fois plus de que pour les deux autres couleurs primaires.
  3. Seuls les photosites bleus sont activés quand la lumière incidente est .
  4. Seuls les photosites rouges et verts sont activés quand la lumière incidente est .
  5. Seuls les photosites verts ne sont pas activés. La lumière incidente est .
  6. Tous les photosites sont activés quand la lumière incidente est .
  7. En de lumière ( ), aucun photosite n'est activé.
  8. Pour former un pixel d'une image, il faut .

Découverte :
Voir une des photos les plus définies du monde avec 195 milliards de pixels de Shangai.

Les métadonnées

Pouvez-vous déterminer l'endroit où a été prise chacune de ces photos ?
(Cliquer pour télécharger)

On peut obtenir quelques informations, et en particulier les informations de géolocalisation, d'une photo en accédant à ses métadonnées EXIF. Cela est réalisable grâce au système d'exploitation de votre ordinateur.

Après avoir téléchargé les images sur votre ordinateur :

Remarque : En procédant ainsi, les données affichées se limitent à quelques paramètres de prise de vue et informations sur le matériel. On peut obtenir plus d'information avec un logiciel de retouche photo ou grâce à des solutions en ligne comme : Verexif.

Après avoir trouver les valeurs de latitude et longitude, il est possible d'utiliser un site tel que Coordonnées GPS pour déterminer le lieu de la prise de vue.

Ces métadonnées associées aux images sont facilement manipulables à l'aide de petits logiciels disponibles gratuitement tel que Exif Pilot.

Déterminer le lieu de la prise de vue des quatre photos à partir des données EXIF. Chaque image numérique est accompagnée de structurées dans son .
Ces métadonnées facilement accessibles .
Elles permettent de connaître entre autres, la marque utilisé ainsi que ses réglages.
Toutes ces données sont automatiquement enregistrées au moment de la prise de vue.
L'auteur désactiver la géolocalisation de sa photo en paramétrant correctement son appareil ou application photographique. Les métadonnées EXIF de l'image être facilement manipulées.

bonus : Stéganographie

Mario a perdu la combinaison à 4 lettres du cadenas qui sécurise son coffre. Le code est caché dans une image numérique. C'est de la stéganographie.
Votre objectif : Utiliser le protocole de codage pour retrouver le code à 4 lettres.

Protocole de codage :
  • Chaque lettre du code à cacher est représentée par son code ASCII en binaire. Par exemple, le code ASCII de la lettre "A" est 01000001 (voir tableau de correspondance ASCII ci-joint).
  • La première lettre est codée à partir du pixel de coordonnées (colonne 19 ; ligne 29). Une seule lettre est cachée par ligne. La deuxième lettre est codée à partir du pixel (colonne 19 ; ligne 28) … L'origine est choisie en bas à gauche de l'image.
  • Les caractères « 0 » ou « 1 » sont codé dans les composantes RVB des pixels : si le nombre est pair il code pour un « 0 », s'il est impair, pour un « 1 ».
  • Pour réaliser ce codage, les valeurs des composantes RVB des pixels d'origine ont pu être modifiées, en ajoutant 1, pour s'assurer de leur parité.
Exemple pour coder la lettre V :
pixel 1 pixel 2 pixel 3
Image initiale 138 65 23 234 87 34 126 90 40
Octet à cacher 0 1 0 1 0 1 1 0
Image modifiée 138 65 24 235 88 35 127 90 40

En appliquant ce système de code à l'image de gauche, on obtient l'image de droite. On ne perçoit aucune différente à l'œil nu.

(Cliquer pour télécharger)
Caractère ASCIINombre binaire sur un octet
A1000001
B1000010
C1000011
D1000100
E1000101
F1000110
G1000111
H1001000
I1001001
J1001010
K1001011
L1001100
M1001101
N1001110
O1001111
P1010000
Q1010001
R1010010
S1010011
T1010100
U1010101
V1010110
W1010111
X1011000
Y1011001
Z1011010
En utilisant vos connaissances sur les images et le langage Python, trouver et décoder le message contenu dans la deuxième image.
Pour démarrer, on pourra afficher les pixels dont les valeurs R, V ou B sont différentes dans les deux images.
Expert
Écrire une programme Python permettant de cacher un message saisi par l'utilisateur dans une image.

S'entraîner