Coder une image numérique
Qu’est ce qu’une image numériques?
Rappels de SNT: Une image numérique est constituée de pixels colorés (ou noirs et blanc). Le fichier comportant l’image numérique contient alors une succession de valeurs qui codent la couleur de chaque pixels: lire le cours de SNT, paragraphe codage de l'image
Librairie PIL
Pour manipuler les images, nous pouvons utiliser le module PIL
de Python. Celui-ci permet de traiter les pixels d’une image, un par un. Souvent, il faudra parcourir l’image pixel par pixel, sur toute la largeur et toute la hauteur. Cela est possible avec deux boucles imbriquées, à condition de connaitre ses dimensions largeur, hauteur
:
from PIL import Image
imageSource=Image.open("crabePortrait.bmp")
largeur,hauteur=imageSource.size
for x in range(largeur): # x varie de 0 à largeur - 1
for y in range(hauteur): # x varie de 0 à hauteur - 1
# traitement pixel (x,y)
planPixels.save("crabe2.jpg")
planPixels.show()
imageSource est une instance de la classe Image
et possède une methode getPixel((x,y))
qui renvoie la valeur du pixel pour l’argument (x,y)
Nous allons travailler sur l’image suivante: crabePortrait.bmp
Dans un premier temps, explorons cette structure de données:
A vous de jouer: une fois l’image téléchargée, ouvrir un notebook dans le même dossier que l’image, puis, à partir du script précédent:
- créer une double boucle sur x (position en largeur qui varie de 0 à largeur//10) et sur y (position en hauteur qui varie de 0 à hauteur//10). On ne parcourt qu’une petite partie de l’image. Le pixel (0,0) est en haut sur le coin gauche.
- dans la boucle: stocker dans p la valeur RGB du pixel de coordonnée (x,y) de imageSource:
p=imageSource.getpixel((x,y))
- afficher la valeur de p…
On souhaite maintenant diminuer le contraste, en divisant par 2 chacune de composantes RGB de chacun des pixels de l’image. L’image sera egalement plus sombre. On va créer cette fois une nouvelle instance de la classe Image
, mais pour former une image completement NOIRE:
planPixels=Image.new("RGB",(largeur,hauteur))
Debut du programme:
from PIL import Image
imageSource=Image.open("crabePortrait.bmp")
largeur,hauteur=imageSource.size
planPixels=Image.new("RGB",(largeur,hauteur))
for ...
On remplira cette image avec des pixels, un par un.
A vous de jouer: Compléter le script pour…
- parcourir tous les pixels de l’image, largeur * hauteur.
- lire la valeur RGB de chaque pixel (
getpixel
) et modifier cette valeur, en diminuant le contraste de couleur: $p = (p[0]//2,\ p[1]//2,\ p[2]//2)$ - puis placer ce pixel sur la nouvelle image en construction planPixels. Utiliser pour cela la methode putpixel((x,y),p) de l’objet
planPixels
où x et y sont les nouvelles coordonnées du pixel p que l’on veut dessiner.
Rotation d’une image
Premier algorithme: permutation de pixels
On va cherche à écrire un script Python qui réalise la rotation d’un quart de tour dans le sens horaire, comme sur l’image ci-dessous:
Questions:
- Quelles sont les coordonnées des points A et B avant transformation?
- Quelles sont les nouvelles coordonnées des points A' et B' après transformation?
- pour un pixel situé initialement en (x,y), quelles sont ses coodonnées (x',y') après transformation? Verifier que l’on a: $x'=-y+hauteur-1$, $y'=x$.
- Ecrire le script du programme qui devra réaliser la transformation de crabePortrait.bmp, d’un quart de tour, dans le sens horaire.
- Donner la complexité de cet algorithme.
Méthode diviser pour régner
On cherche maintenant à effectuer cette transformation, SANS utiliser de nouvelle image planPixels comme précédemment. Ce sera une méthode dite en O(1) du point de vue de la complexité spatiale.
On utilisera l’image suivante (carrée) pour cette méthode: woody.jpg
Compléter la fonction echange_pix
suivante
def echange_pix(image,x0,y0,x1,y1):
"""procedure qui echange les pixels d'une image entre une position
de depart start et d'arrivée end
Params:
------
image : objet de la classe Image
x0,y0: int, int: coordonnées du pixel de depart
x1,y1: int, int: coordonnées du pixel d'arrivée
Example: echange du pixel (0,0) avec celui (120,120)
--------
>>> echange_pix(imageSource,0,0,120,120)
"""
start = image.getpixel((x0,y0))
end = image.getpixel((x1,y1))
image.putpixel((x0,y0),...)
image.putpixel((x1,y1),...)
Compléter la fonction echange_quadrant
suivante
Cette fonction permet d’echanger les pixels de 2 zones carrées de même dimensions.
def echange_quadrant(image,x0,y0,x1,y1,n):
"""procedure qui echange tous les pixels du bloc de pixels A
avec ceux du bloc B, de même dimension n*n.
L'image doit être carrée, de largeur et hauteur égaux à n
A et B occupent une position quelconque parmi les 4 quarts de l'image
Params:
-------
image : objet de la classe Image
x0,y0: int, int: coordonnées du pixel du coin superieur gauche de A
x1,y1: int, int: coordonnées du pixel du coin superieur gauche de B
n : int : largeur ou hauteur de l'image, en nombre de pixels
Example: echange du quart d'image en haut à gauche (A) avec celui
------------ en haut à droite (B) sur une image de largeur 120
>>> echange_quadrant(imageSource,0,0,120,0,120)
"""
for x in range(n):
for y in range(n):
echange_pix(image, # à compléter
Questions:
- On veut echanger les blocs A et D, qui font chacun 120*120 pixels. Quelle instruction faut-il écrire, utilisant la procedure
echange_quadrant
.
- Même question pour echanger les blocs A et C.
analyser la fonction rotate
La fonction (ou plutôt méthode, vue qu’elle ne retourne rien), permet de faire tourner l’image d’un quart de tour par une méthode de type diviser pour régner.
Une fois la partie divisée executée (appels recursifs), lorsque les subdivisions de l’image sont constituées d’un seul pixel, les pixels sont déplacés (règne) à l’aide de 3 permutations successives, selon le schéma suivant:
Il sont alors recombinés pour reformer l’image, tout en suivant les mêmes permutations, mais avec des blocs de pixels plus gros (fusion).
def rotate(image,x0,y0,n):
"""procedure recursive qui tourne d'un quart de tour un carré
de l'image de dimension n.
à chaque appel recursif, la taille de l'image est divisée par 2.
Si l'image fait plus d'un seul pixel, la rotation se fait par
permutation des (zones de) pixels A<=>B, B<=>D, D<=>C
Params:
-------
image
x0,y0: int, int: coordonnées du pixel du coin superieur gauche du carré
n: dimansion du carré
Example:
--------
rotate(imageSource,0,0,420)
"""
if n>=2:
m = n//2
rotate(image,x0,y0,m)
rotate(image,x0,y0+m,m)
rotate(image,x0+m,y0,m)
rotate(image,x0+m,y0+m,m)
echange_quadrant(image,x0,y0,x0+m,y0,m)
echange_quadrant(image,x0,y0,x0+m,y0+m,m)
echange_quadrant(image,x0,y0,x0,y0+m,m)
Analysez la procedure: A l’aide de l’image suivante, que vous découperez, montrer pas à pas ce qui est réalisé par la fonction rotate.
efficacité de la méthode
Pour executer cette méthode, vous écrirez le script suivant dans une nouvelle cellule:
def quart_tour(image):
largeur,hauteur=image.size
assert largeur == hauteur
rotate(image,0,0,largeur)
img = Image.open("woody.jpg")
quart_tour(img)
img.save("woody_endroit.jpg")
img.show()
Tester l’efficacité de cet algorithme
- Est-il plus rapide ou plus lent que votre premier algorithme?
- Le nombre d’opérations significatives suit une loi de recurence: $T(n) = 4 \times T(\tfrac{n}{2}) + C \times n^2$ Cela confirme t-il votre reponse à la question pécédente?
On rappelle que l’intérêt de la méthode est surtout de ne PAS utiliser de nouvelle image planPixels comme précédemment. Il s’agit d’une transformation en place.
Liens et aides
- En cas de difficulté, consulter la page et les videos du site: monlyceenumerique.fr