Traitement d'images

Couleurs

Principe

Actuellement les écrans d’ordinateurs savent afficher 16 millions de couleurs. D’après une étude menée en 1998, l’œil humain ne pourrait distinguer « que » 2 millions de couleurs en moyenne.

Il existe différentes solutions pour décrire une couleur, celle qui va nous intéresser est le système RGB (ou RVB en français) pour les trois couleurs primaires Rouge, Vert, Bleu . En utilisant le principe de synthèse additive des couleurs, on va pouvoir à partir de ces trois couleurs recréer toutes les couleurs dont nous avons besoin. En voici quelques exemples :

exemple2
RougeVertBleuRésultat
100%100%0%Jaune
100%0%100%Violet
0%100%100%Cyan
100%50%0%Orange
100%100%100%Blanc
50%50%50%Gris
0%0%0%Noir
... ... ... ...

Interface de modification de couleurs du logiciel libre GIMP (www.gimp.org)

exemple2 Vous pouvez utiliser le logiciel libre GIMP pour créer une couleur ou plus simplement utiliser un outil en ligne comme le suivant : www.code-couleur.com

Binaire et octet

Nous avons déjà vu le système de numération d’un ordinateur au début d’année. Les êtres humains comptent en base décimale (base 10), sûrement car ils ont dix doigts. Un ordinateur ne peut stocker de l’information qu’à l’aide de signaux électriques qui sont soit su ON soit sur OFF. Il n’existe donc que 2 états pour une machine : 0 ou 1 que l’on appelle bit.

Un ordinateur compte donc en base 2, on dit qu’il compte en binaire. Nous avons déjà vu comment convertir un décimal en binaire et inversement.

Hexadécimal

L’écriture binaire d’un nombre, bien que naturelle pour la machine présente un inconvénient de générer des nombres dont l’écriture est très longue et peu lisible pour l’être humain. Pour cela on se propose pour un octet (emplacement nécessaire pour coder un nombre en 8 bits)de regrouper les bits 4 par 4, on peut écrire 16 valeurs de 0000 à 1111 en binaire ou plus clairement de 0 à 15 en décimal. Il nous faudrait donc 16 symboles pour représenter ces valeurs et ainsi travailler en base 16 que l’on appelle base hexadécimale. On a déjà les 10 chiffres de 0 à 9 que l’on va compléter par 6 lettres : A, B, C, D, E et F.

En hexadécimal, 2 symboles suffisent donc pour écrire un nombre de 0 à 255. Le principe est toujours le même, on commence à numéroter : 0, 1, …, 8, 9, A, B, C, D, E, F, puis 10, 11, 12, …, 1F, 20… La conversion d’une base à l’autre se fait aussi selon les mêmes règles.

Conversion d’un nombre décimal en hexadécimal :

Comme pour le binaire, on multiplie par des puissances de 16, par exemple,

Le nombre en hexadécimal 1A3C est égal en décimal : C×160 + 3×161 + A×162 + 1×163 = 6 716.

Conversion d’un décimal en hexadécimal : la conversion se fait à l’aide de divisions successives par 16. Par exemple, pour le nombre 6 716

NombreQuotientResteSymbole
671641912C
4192633
26110A
1011

Le nombre 6 716 s’écrit donc en hexadécimal sous la forme 1A3C.

Exercice 1

Compléter le tableau suivant :

BinaireDécimalHexadécimal
101010 ... ...
... 723 ...
... ... BAC

Pour revenir aux couleurs et Python

En Python, on peut donner un nombre en binaire ou en hexadécimal en utilisant une chaîne de caractère commençant respectivement par 0b ou 0x qu’il faut alors transformer en valeur numérique à l’aide de la fonction eval . Depuis Python 2.6 des fonctions ont été ajoutées pour passer d’un nombre en écriture décimale à l’écriture binaire ou hexadécimale, comme le résume le tableau d’exemples qui suit :

SaisieRésultat
eval('0b110') 6
eval('0xCA0') 3 232
bin(37) 0b100101
hex(243) 0xf3

Pour revenir à nos couleurs, en Python, avec le module tkinter, un certain nombre de couleurs existe déjà sous forme de chaînes de caractères parmi lesquelles :

black, white, red, purple, cyan, maroon, green, blue, orange, yellow, grey ….

Si l’on souhaite une couleur personnalisée, on peut alors préciser la couleur que l’on souhaite en indiquant le code HTML de la couleur désirée. Le code HTML d’une couleur est une chaîne de caractère composée de :

  • un symbole # pour commencer.
  • 2 symboles correspondant au niveau de rouge codé en hexadécimal.
  • 2 symboles correspondant au niveau de vert codé en hexadécimal.
  • 2 symboles correspondant au niveau de bleu codé en hexadécimal.

Exercice 2

Trouver la proportion de rouge, vert et bleu des couleurs suivantes données en code HTML :

exemple3

#B9121B

#E70739

#375D81

#FFF168

Les listes 2 D

Ce paragraphe vous intéressera particulièrement si vous souhaitez réaliser un jeu de plateau. Nous avons déjà vu comment transformer un fichier texte en plateau de jeu :

exemple4

Mais jusque là nous n’avons pas conservé ces informations. Or si notre personnage a vocation à se déplacer sur le plateau, il faudra être en mesure de savoir sa position et ses déplacements dans le plateau par exemple.

Une première solution est de charger notre fichier dans une liste de chaînes de caractères :

fichier = open('ExempleISN.txt','r') 	# pour ouvrir le fichier exempleISN.txt, pour la lecture
cases = []								# pour initialiser la liste cases
for ligne in fichier:
        cases.append(ligne)       		# pour parcourir le fichier ExemplesISN.txt et stocker 
										# le contenu dans la liste cases
fichier.close()							# fermeture du fichier ExempleISN.txt

chaque élément de la liste correspond à une ligne du plateau, par exemple si on saisit print(cases[8]) (l’avant dernière ligne), on obtient :

I S     S N       NN

Ainsi on peut savoir ce qu’il y a à la ligne 9 et à la colonne 3 (la numérotation commence à zéro) :

ligne = cases[8]
print(ligne[2])				# ce qui affiche  >>> S

mieux encore, en une seule ligne :

print(cases[8][2])				
# ce qui affiche  >>> S

Cette technique nous permet donc de savoir quel élément de décor se cache sous le personnage. Cependant les chaînes de caractères ne permettent pas de modifier un caractère de celles-ci, ce qui peut-être nécessaire (par exemple éliminer un élément du plateau ou faire un trou dans le plateau …). A la place d’une chaîne par ligne, il serait donc préférable d’avoir une liste par ligne.

Voici comment modifier le chargement du fichier ExempleISN.txt pour obtenir une liste de listes :

fichier = open('ExempleISN.txt','r') 	# pour ouvrir le fichier exempleISN.txt, pour la lecture
cases = []								# pour initialiser la liste cases
for ligne in fichier:
        cases.append(list(ligne))       # pour parcourir le fichier ExemplesISN.txt 
										# et stocker le contenu dans la liste des listes cases
fichier.close()							# fermeture du fichier ExempleISN.txt

Si on veut lire le contenu de la ligne 8 et de la colonne 3 :

print(cases[7][2])

Si on veut modifier le contenu de la ligne 8 et de la colonne 3 :

cases[7][2] = ...

Remarque

Notez que l’accès au contenu se fait donc sous la forme : cases[ligne][colonne]

Exemple 1

Voici deux programmes :

Programme 1
cases = [ ]
for i in range(20) :
   cases.append([0]*20)
Programme 2
cases = [[0 for i in range(20)] for j in range(20)]

Quel est le rôle de chacun de ces deux programmes ?

Traitement d'images

Changement de format d'une image

la fonction :

img = Image.open('image.format')
permet d'ouvrir une image de tout format (compatible avec la bibliothèque Pil).

Pour afficher par exemple le format, les dimensions et le mode de l’image, on écrit :

print ('image.format', im.format, "%dx%d" % im.size, im.mode)

La fonction

img.save('image.format', "FORMAT" ) 
permet de transformer le format d’une image img en un autre format :

Exemple 2

Quel est le rôle du programme suivant ?

from PIL import Image
im = Image.open('image.png')
im.save('image.jpg', "JPEG")
im.save('image.bmp', "BMP")
im.save('image.gif', "GIF") 

Pour réduire les dimensions d’une image, on utilise la fonction :

img.resize((newl,newh))

Exemple 3

Quel est le rôle du programme suivant ?

from PIL import Image
from PIL import Image
im = Image.open('image.png')
out = im.resize((100,120))           
out.save('resize.png')

Déplacer ou recopier plusieurs images

Exemple 4

Recopier et tester le programme suivant :
from PIL import Image
from PIL import ImageDraw
img = Image.new("RGB", (500,400), "white")
draw = ImageDraw.Draw(img)
into = Image.open("explication.png")
w,h=into.size
img.paste(into, (0,0,w,h))
img.paste(into, (300,0,300+w,h))
img.paste(into, (200,200,200+w,200+h))
img.paste(into, (300,300,300+w,300+h))
del draw
img.save("dessin.jpg", "JPEG")

Quel est le rôle de ce programme ?

On peut effectuer une rotation d’un angle donné à une image. Pour incliner une image de α degrés, on utilise la fonction

img.rotate(α)

Exemple 5

Recopier et tester le programme suivant :
from PIL import Image
img = Image.open('image.png')
out = img.rotate(45)
out.save('NewImage.png')

Quel est le rôle de ce programme ?

On peut aussi effectuer une symétrie axiale à une image. Voici un exemple qui montre comment construire l’image symétrique par rapport à un axe vertical :

Exemple 6

Recopier et tester le programme suivant :
from PIL import Image
img = Image.open('image.png')
w,h=img.size
box = (0, 0, w, h)
src = img.crop(box)
out = img.resize((w*2,h))
out.paste(src,(0,0,w,h))
src=img.transpose(Image.FLIP_LEFT_RIGHT)
out.paste(src,(w,0,2*w,h))
out.save('NewImage.png')

Quel est le rôle de ce programme ?

Ecrire du texte sur une image

Il est aussi possible d’écrire du texte sur une image :

Exemple 7

Recopier et tester le programme suivant :
#-*- coding:utf8 -*-
import sys
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

txt = 'Salle d'informatique'
txt2 = 'Photo de synthèse'

font = ImageFont.truetype('verdanai.ttf',200)
font2 = ImageFont.truetype('simsun.ttc',200)
im = Image.open('image.png')
w,h = im.size
draw = ImageDraw.Draw(im)

draw.text( (0,50), txt, font=font)
draw.text( (0,300), txt2, font=font2)
del draw
im.save('font.png', "PNG")

Quel est le rôle de ce programme ?

Remarque

Attention si vous importez la librairie tkinter de la manière from kinter import *, alors son module Image écrasera celui du module PIL (ou inversement, en fonction de la position des imports). C'est une des raisons qui font qu'il est déconseillé d'utilisez ce schéma pour l'importation. C'est pour cela qu'il est préférable pour tkinter d'importer de la manière import tkinter Tk ou import tkinter Canvas .

Décomposition d'une image en RVB

la fonction :

img = Image.open('image.format')
permet d'ouvrir une image de tout format (compatible avec la bibliothèque Pil).

la fonction :

img.getpixel()
retourne un t-uples à 3 composantes contenant les valeurs des pixels de l'image si l’image est en couleur.

Par exemple pour un pixel de coordonnées (x ; y) d’une image img l’instruction :


rouge[x][y],vert[x][y], bleu[x][y] = img.getpixel(x ; y)
permet de récupérer les trois composantes RVB du pixel de coordonnés (x ; y).

Exemple 8

Recopier et tester le programme suivant :
from PIL import Image
# Charger l’image qu’on nomme img
img = Image.open('Joconde.jpg')
#Définir les dimensions de l’image
w,h = img.size
#Initialiser les 3 composantes RVB de l’image
rouge = [[0 for j in range(h)] for i in range(w)]
vert = [[0 for j in range(h)] for i in range(w)]
bleu = [[0 for j in range(h)] for i in range(w)]

for j in range(0,h):
    for i in range(0,w):
        p = img.getpixel((i,j))
        
rouge[i][j] = p[0]
vert[i][j] = p[1]
bleu[i][j] = p[2]

Quel est le rôle de ce programme ?

Dessiner dans une fenêtre

Pour dessiner dans une fenêtre, on ouvre une fenêtre graphique, dans laquelle on crée un Canvas nommé Fond. Ensuite on exécute l’instruction

Fond.create_rectangle(x,y,x,y,outline=(rouge, vert, bleu))
qui a pour effet de dessiner un pixel dans la x-ème colonne et la y-ème ligne de cette fenêtre, dont la couleur est décrite dans le système rouge, vert, bleu par les nombres rouge, vert et bleu. La coordonnée x varie entre 0 et largeur – 1 et la coordonnée y entre 0 et hauteur – 1. Les nombres rouge, vert et bleu varient entre 0 et 255.

Attention axe vértical

De même que les Anglais roulent à gauche, l’axe vertical, en géométrie algorithmique, est orienté vers le bas.

Savoir-faire : créer une image

Méthode

  1. Établir une condition sur les coordonnées d’un pixel qui permet de décider s’il appartient à la figure à tracer ou non.
  2. Écrire une instruction qui balaye la fenêtre graphique, au moyen de deux boucles imbriquées, l’une sur les abscisses et l’autre sur les ordonnées.
  3. Dans le corps de la boucle la plus interne, affecter la couleur appropriée à chaque pixel, selon qu’il appartient à la figure ou non.

Exemple 9

Recopier et tester le programme suivant :
from tkinter import Tk
from tkinter import Canvas

fen = Tk()
fen.geometry("400x400")
fen.title("Carré Rouge")


Fond=Canvas(fen,width=400,height=400,bg="white")
Fond.place(x=0,y=0)
couleur = "#%02x%02x%02x" % (255,0,0)
for y in range(0,400):
    for x in range(0,400):
            if 100<=x and x <= 250 and 50 <= y and y <= 200 :
                     Fond.create_rectangle(x,y,x,y,outline=couleur)
    
fen.mainloop()

Quel est le rôle de ce programme ?

Produire un fichier au format PPM

Si au lieu de dessiner cette image dans une fenêtre graphique, on veut l’enregistrer dans un fichier, par exemple au format PPM, afin de pouvoir l’inclure dans une page web ou l’attacher à un courrier, on doit d’abord représenter cette image dans un tableau, au sens que l’on a donné à ce mot au paragraphe II., puis produire un fichier au format PPM, à partir de ce tableau. Pour représenter une image en niveaux de gris, il suffit d’utiliser un tableau bidimensionnel t, dont la case t[i][j] contient la valeur du pixel de coordonnées (i ; j). Pour représenter une image en couleurs, on utilise trois tableaux bidimensionnels rouge, vert et bleu, les cases rouge[i][j], vert[i][j] et bleu[i][j] contenant les trois composantes de la couleur du pixel de coordonnées (i ; j). On peut alors transformer le programme ci-avant en remplaçant toutes les instructions

Fond.create_rectangle(x,y,x,y,outline=couleur)

par :

rouge[x][y] = r
vert[x][y] = v
bleu[x][y] = b

Par exemple, le dessin du carré rouge :

Fond.create_rectangle(x,y,x,y,outline=couleur)

par :

couleur = "#%02x%02x%02x" % (255,0,0)
for y in range(0,400):
    for x in range(0,400):
            if 100<=x and x <= 250 and 50 <= y and y <= 200 :
              Fond.create_rectangle(x,y,x,y,outline=couleur)

Une fois le dessin terminé, on peut l’enregistrer au format PPM qui est un fichier texte qui doit avoir le format suivant :

P3
#
400
400
255
 rouge[0][0] 
vert[0][0] 
bleu[0][0] 
rouge[1][0] 
vert[1][0] 
bleu[1][0]
...

La première ligne contient les caractères P3 pour indiquer que c’est un fichier au format PPM, la deuxième est un commentaire, la troisième la largeur de l’image, la quatrième sa hauteur, la cinquième la valeur 255 pour indiquer que les valeurs des pixels vont de 0 à 255, puis sur les lignes suivantes les trois valeurs rouge, vert et bleu en énumérant les pixels de gauche à droite et de haut en bas.

Recopier et tester ce programme

fichier = open("Carre.ppm","w")
print("P3",file=fichier)
print("#",file=fichier)
print(400,file=fichier)
print(400,file=fichier)
print(255,file=fichier)
for y in range(0,400):
    for x in range(0,400):
        print(rouge[x][y], file=fichier)
        print(vert[x][y], file=fichier)
        print(bleu[x][y], file=fichier)
fichier.close()

Quel le rôle de ce programme ?

Lire un fichier au format PPM

Inversement, on peut écrire un programme qui lit un fichier au format PGM ou PPM dans un tableau ou dans trois, selon que l’image est en niveaux de gris ou en couleurs.

La seule difficulté, pour lire un tel fichier, est due au fait qu’il est possible d’insérer dans un fichier au format PGM ou PPM des commentaires, c’est-à-dire des lignes qui commencent par le caractère # et qui doivent être ignorées. En pratique cependant, la plupart des fichiers au format PGM et PPM ont un seul commentaire, à la deuxième ligne :

P3
# format PPM, cette photo a été prise ...
279
400
255
113
89
72
...

On se limite donc à des fichiers de cette forme si bien que les fichiers PPM que l’on lit sont de la forme suivante :

  • deux lignes qui peuvent être ignorées,
  • la largeur de l’image, suivie d’un retour à la ligne ou d’un espace,
  • la hauteur de l’image, suivie d’un retour à la ligne ou d’un espace,
  • la valeur maximale utilisée pour exprimer les niveaux de rouge, vert et bleu, suivie d’un retour à la ligne ou d’un espace,
  • la liste des pixels, ligne par ligne, de haut en bas et de gauche à droite, séparés par des retours à la ligne ou des espaces.

On peut lire un tel fichier avec le programme suivant :

fichier = open("Joconde.ppm","r")
s = fichier.readline()
s = fichier.readline()
largeur = eval(fichier.readline())
hauteur = eval(fichier.readline())
maximum = eval(fichier.readline())

rouge = [[0 for j in range(0,hauteur)] for i in range(0,largeur)]
vert = [[0 for j in range(0,hauteur)] for i in range(0,largeur)]
bleu = [[0 for j in range(0,hauteur)] for i in range(0,largeur)]

for j in range(hauteur):
        for i in range(largeur):
            rouge[i][j] = eval(fichier.readline())
            vert[i][j] = eval(fichier.readline())
            bleu[i][j] = eval(fichier.readline())
fichier.close()

et ensuite afficher cette image dans une fenêtre :

fen = Tk()
fen.geometry("500x600")
fen.title("Pgm")

Fond=Canvas(fen,width=largeur,height=hauteur,bg="white")
Fond.place(x=0,y=0)

for y in range(0,hauteur):
    for x in range(0,largeur):
        couleur = "#%02x%02x%02x" % (rouge[x][y],vert[x][y],bleu[x][y])
        Fond.create_rectangle(x,y,x,y,outline=couleur)
    
fen.mainloop()

La lecture et l’affichage d’une image en couleurs, au format PGM sont similaires, sauf qu’il faut lire un seul nombre pour chaque pixel et le stocker dans un seul tableau.

Transformer des images

Une fois une image est représentée dans un tableau, il est facile de la transformer. Par exemple, d’inverser la quantité de chaque couleur :

for j in range(0,hauteur):
    for i in range(0,largeur):
rougebis[i][j] = maximum - rouge[i][j]
vertbis[i][j] = vert[i][j]
bleubis[i][j] = bleu[i][j]

ce qui transforme l’image (1) en l’image (2).

exemple4

Savoir-faire : transformer une image en couleurs en une image en niveaux de gris.

Méthode

On remplace chaque pixel de couleur r, v, b par un pixel dont le niveau de gris est la moyenne des nombres r, v et b.

Savoir-faire : augmenter le contraste d’une image en niveaux de gris.

Méthode

On fixe un seuil et on remplace tous les pixels plus clairs que ce seuil par un pixel blanc et tous les pixels plus sombres que ce seuil par un pixel noir.

Savoir-faire : changer la taille d’une image

Méthode :

On calcule la nouvelle image pixel par pixel.

Voir l'exercice 3