Rotation d'une image bitmap d'un quart de tour



L’objectif de cette activité est l’écriture d’une fonction qui effectue la rotation d’une image bitmap de 90 degrés en utilisant le principe « Diviser pour régner ».

On peut manipuler des images en Python à l’aide du module PIL (Python Image Library). Une première partie de l’activité est consacrée à la prise en main de ce module. Dans un second temps, la fonction de manipulation des bits est développée.

Images numériques

Définition

L’image matricielle

Une image matricielle, ou « carte de points » (de l’anglais « bitmap »), est une image constituée d’une matrice de points colorés, c’est-à-dire, constituée d’un tableau, d’une grille, où chaque case possède une couleur qui lui est propre et est considérée comme un point. Il s’agit donc d’une juxtaposition de points de couleurs formant, dans leur ensemble, une image.

Caractéristiques

Numérisation

Le codage ou la représentation informatique d’une image implique sa numérisation. Cette numérisation se fait dans deux espaces :

  • l’espace spatial dans lequel l’image est numérisée suivant l’axe des abscisses et l’axe des ordonnées : on parle d’échantillonnage. Les échantillons dans cet espace sont nommés pixelspicture element ») et leur nombre va constituer la définition de l’image.

  • l’espace des couleurs dans lequel les différentes valeurs de luminosité que peut prendre un pixel sont numérisées pour représenter sa couleur et son intensité ; on parle de quantification. La précision dans cet espace dépend du nombre de bits sur lesquels on code la luminosité et est appelée profondeur de l’image.

À retenir

La qualité d’une image matricielle est déterminée par le nombre total de pixels et la quantité d’information contenue dans chaque pixel (souvent appelée profondeur de numérisation des couleurs).

Définition d’une image

On appelle définition le nombre de points (pixels) constituant une image: c’est le nombre de colonnes de l’image que multiplie son nombre de lignes.

La définition d’une image indique donc le niveau de détails qui seront visibles dans l’image. Pour une taille donnée, plus il y aura de pixels, plus il y aura de détails fins visibles.

Exemple. Une image numérisée avec une définition de $640 \times 480$ pixels (donc contenant 307 200 pixels) apparaîtra très approximative et sous forme d’un pavage de petits carrés de couleur, par comparaison à une image de $1280 \times 1024$ pixels (soit 1 310 720 pixels).

Résolution d’une image

On appelle résolution le nombre de points (pixels) contenus dans une longueur donnée, le pouce (un pouce mesure $\pu{2,54 cm}$, c’est une unité de mesure britannique). Elle est exprimée en « points par pouce » (PPP) en français et « Dots Per Inch » (DPI) en anglais.

La résolution permet ainsi d’établir le rapport entre la définition d’une image et la dimension réelle de sa représentation sur un support physique (écran, papier, etc.)

> Source : http://www.raphaelisdant.fr/paris8/

Codage des couleurs

En plus de sa définition, une image numérique utilise plus ou moins de mémoire selon le codage des informations de couleur qu’elle possède. C’est ce que l’on nomme le codage de couleurs ou profondeur des couleurs, exprimé en bits par pixel (bpp) : 1, 4, 8, 16, 24, … bits.

Le codage des informations de couleur est donc le nombre de bits utilisés pour coder une couleur.

Remarque

Les écrans utilisent la synthèse additive pour restituer les couleurs : chaque pixel est composé de trois sous-éléments munis respectivement d’un filtre : rouge, vert, bleu.

Quelques modes de représentation des couleurs

  • Mode bitmap (noir et blanc) : Avec ce mode, il est possible d’afficher uniquement des images en deux couleurs: noir et blanc. Il présente une profondeur de 1 bpp.
  • Mode niveau de gris : Avec ce mode, il est possible d’afficher des images utilisant 254 ou 65534 nuances de gris entre le blanc et le noir. Le niveau de gris représente la luminosité d’un pixel, lorsque les valeurs de ses composantes de couleur sont identiques. Pour convertir une image couleur en niveau de gris il faut remplacer, pour chaque pixel les trois valeurs représentant les niveaux de rouge, de vert et de bleu, en une seule valeur représentant la luminosité. Il présente une profondeur de 8 bpp ou 16 bpp.
  • Mode RVB : Dans ce mode, la couleur de chaque pixel de l’image est codée à l’aide d’un triplet de valeurs pour le rouge, le vert et le bleu. Chaque canal peut être codé sur 8 bits (le plus fréquent) ou 16 bits (appareils de photo modernes).

Exploitation des documents

  1. Déterminer la résolution d’une image de $300 \times 300$ pixels mesurant 2 pouces par coté.
  2. Quelle serait la définition en pixels d’une feuille scannée d’une largeur de 8,5 pouces sur une hauteur de 11 pouces en 300 dpi ?
  3. Quel est le poids, en octets, d’une image d’une définition de $640 \times 480$, codée sur 1 bit (image en « noir et blanc ») ? codée sur 8 bits (« niveau de gris ») ? codée sur 24 bits (« RVB ») ?
  4. Pourquoi le mode « niveau de gris » divise-t-il par trois le nombre d’octets nécessaire au codage ?
  5. Quelle particularité doivent présenter les sous-éléments de chaque pixel lors de l’affichage d’une image en « niveau de gris » ?
  6. Déterminer le nombre de couleurs que l’on peut coder en mode RVB (8 bpp).

Manipulation d’une image en niveaux de gris avec Python

  1. Étudier le code suivant, essayer de déterminer quel traitement effectue la fonction mystere et le tester.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import PIL.Image as pi


def mystere(im):
    """
    Cette fonction réalise un traitement qu'il faut deviner, sur
    l'image dont l'objet qui la représente est passé en argument.

    Paramètre
    ---------
    im: PIL.Image.Image
        Objet image

    Retour
    ------
    None
    """
    (largeur, hauteur) = (im.width, im.height)

    for y in range(hauteur):
        for x in range(largeur):
            niveau = im.getpixel((x, y))
            niveau = 255 - niveau
            im.putpixel((x, y), niveau)

def main():
    """ Fonction principale """

    # Chemin de l'image
    image = "tigrenb.png"
    # Création de l'objet image
    im_nb = pi.open(image)
    # Visualisation du fichier
    im_nb.show()

    # Modification du fichier
    mystere(im_nb)
    # Visualisation des modifications
    im_nb.show()

main()
  1. À partir de la fonction précédente, écrire une fonction nommée noir_et_blanc qui, en fonction d’un seuil s, transforme tous les pixels de valeur inférieure à s en 0 (noir) et tous les autres en 255 (blanc).

  2. En modifiant les couleurs des pixels, il est possible de « dessiner » dans une image. Par exemple, on peut tracer une ligne horizontale blanche dans l’image. Écrire la fonction ligne_blanche qui trace une ligne blanche horizontale au niveau de l’ordonnée $y$ de l’image et le tester.

  3. Écrire la fonction ligne, évolution de la fonction précédente, qui permet de tracer une ligne horizontale d’une « couleur », codée en niveau de gris, dont le niveau est passé en argument.

  4. On veut maintenant pouvoir assombrir ou éclaircir des images. Pour assombrir une image, on décide de diviser par 2 le niveau de tous les pixels. À l’opposé, pour éclaircir une image, on décide de multiplier par 2 le niveau de tous les pixels, en prenant soin de ne pas dépasser la valeur maximale de 255 (on limite donc les valeurs supérieures à 255). Écrire la fonction traitement qui réalise ces opérations (la fonction doit être capable d’éclaircir ou d’assombrir l’image).

  5. Écrire la fonction grille qui trace une grille à l’écran. Cette fonction doit recevoir en argument le pas de la grille, la couleur des traits horizontaux et verticaux (en niveaux de gris) et l’objet image.

Manipulation d’une image en couleur avec Python

  1. Étudier le code suivant.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
from PIL import Image


def recuperation_niveaux(im: Image, pt: tuple) -> None:
    """
    Récupère la couleur du pixel pt.
    """
    # On vérifie que le point est dans l'image
    (largeur, hauteur) = (im.width, im.height)
    if pt[0] > (largeur - 1) or pt[0] < 0 or pt[1] < 0 or pt[1] > (hauteur - 1):
        raise Exception("Le point indiqué n'est pas dans l'image.")

    # Récupération des niveaux de couleur
    triplet = im.getpixel(pt)

    return triplet


def modification_couleur(im: Image, pt: tuple, triplet: tuple) -> None:
    """ 
    Attribue la couleur (triplet) au pixel pt.
    """

    # On vérifie que le point est dans l'image
    (largeur, hauteur) = (im.width, im.height)
    if pt[0] > (largeur - 1) or pt[0] < 0 or pt[1] < 0 or pt[1] > (hauteur - 1):
        raise Exception("Le point indiqué n'est pas dans l'image.")

    # Modification de la couleur
    im.putpixel(pt, triplet)


def grille(im: Image, pas: int, triplet: tuple) -> None:
    """
    Trace une grille à l'écran, de pas 'pas' dont la couleur 
    est donnée par le triplet triplet.
    """
    pass


def augmentation_contraste(im: Image, offset: int) -> None:
    """
    Augmente le contraste de l'image d'une valeur offset.
    """
    pass


def teinte_plus_chaude(im: Image, offset: int) -> None:
    pass


def main():
    """ Fonction principale. """
    nom_image = "tigre.jpg"

    # Création de l'objet image
    im = Image.open(nom_image)
    im.show()

    # Récupération des niveaux de couleur
    pt = (600, 250)
    (rouge, vert, bleu) = recuperation_niveaux(im, pt)

    # Affichage du résultat
    print("Au point {} : R : {}, V : {}, B : {}".format(pt, rouge, vert, bleu))


main()
  1. Quelle est la couleur du pixel de coordonnées $(600, 250)$ ?

  2. Adapter la fonction grille de la partie précédente de façon à ce qu’on puisse choisir la couleur de la grille.

  3. Écrire le corps de la fonction augmentation_contraste.

  4. Écrire le corps de la fonction teinte_plus_chaude.

Rotation d’une image d’un quart de tour

Dans la suite de cette activité on suppose que l’image est carrée et que sa dimension est une puissance de 2, par exemple $256 \times 256$.

Une solution se trouve à cette adresse

L’idée de l’algorithme consiste à découper l’image en quatre, à effectuer la rotation de 90 degrés de chacun des quatre morceaux, puis de les déplacer vers leur position finale.

Afin de pouvoir procéder récursivement, définir la fonction (et sa spécification !) rotation_aux(px, x, y, t) qui effectue la rotation de la portion carrée de l’image comprise entre les pixels $(x,y)$ et $(x+t, y+t)$. Cette fonction ne renvoie rien, elle modifie le tableau px pour effectuer la rotation de cette portion de l’image, au même endroit. On suppose que t est une puissance de 2. Le code de cette fonction est :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
def rotation_aux(im, x: int, y: int, t: int) -> None:
    if t == 1:
        return

    t = t // 2
    rotation_aux(im, x, y, t)
    rotation_aux(im, x + t, y, t)
    rotation_aux(im, x, y + t, t)
    rotation_aux(im, x + t, y + t, t)

    for i in range(x, x + t):
        for j in range(y, y + t):
            #print(x, y)
            t_1 = im.getpixel((i, j))
            t_2 = im.getpixel((i, j + t))
            t_3 = im.getpixel((i + t, j + t))
            t_4 = im.getpixel((i + t, j))

            im.putpixel((i, j), t_4)
            im.putpixel((i + t, j), t_3)
            im.putpixel((i + t, j + t), t_2)
            im.putpixel((i, j + t), t_1)
  1. Étudier ce code afin de bien le comprendre. En particulier, faire fonctionner à la main la fonction pour une image de définition $8 \times 8$.

  2. En déduire la fonction rotation(px) qui effectue une rotation de l’image toute entière, sa dimension étant donnée par le paramètre taille. Une fois la rotation effectuée, on pourra sauvegarder le résultat dans un autre fichier avec l’instruction im.save("Apres_rotation.png").


Voir également