Transformations

Cette page présente l’ensemble des transformations d’images qui sont mise en œuvre par traitementimage. Chaque transformation est accompagnée d’une image d’exemple, du code qui l’implémente, et d’une rapide explication.

L’ensemble des transformations prend la forme d’un traitement pixel par pixel. Selon la documentation de PIL (ma traduction) :

L’accès individuel à chaque pixel est plutôt lent. Si vous faites une boucle sur l’ensemble des pixels d’une image, il existe probablement une méthode plus rapide en utilisant d’autres fonctions du module.

Donc les implémentations présentées ici ne sont pas optimales. Mais le but n’est pas d’écrire du code optimal, mais d’illustrer simplement quelques transformations d’image.

L’image de départ est PNG transparency demonstration 24bit PNG with 8bit alpha layer, de Ed_g2s.

La liste des transformations est listée dans le menu à gauche de cette page.

Couleurs de pixels

Noir et blanc

../../_images/exemple-transformation_noir_et_blanc.jpg

Chaque pixel prend la couleur la plus proche parmi les couleurs blanche et noire.

 1def transformation_noir_et_blanc(source, destination):
 2    """Convertir l'image en noir et blanc."""
 3    source = Image.open(source).convert("RGB")
 4    dest = Image.new("RGB", source.size)
 5
 6    for x in range(source.width):
 7        for y in range(source.height):
 8            couleur = source.getpixel((x, y))
 9            if sum(couleur) > 3 * 127:
10                nouvelle = (255, 255, 255)
11            else:
12                nouvelle = (0, 0, 0)
13            dest.putpixel((x, y), nouvelle)
14
15    dest.save(destination)

Niveaux de gris

../../_images/exemple-transformation_niveaux_de_gris.jpg

La moyenne des trois trames de chaque pixel est calculée, et ce pixel se voit affecter une couleur dont la valeur de chaque trame est égale à cette moyenne.

Puisque qu’une couleur dont les trois trames sont identiques est grise (au sens large : du blanc au noir), cela donne une image en niveaux de gris.

 1def transformation_niveaux_de_gris(source, destination):
 2    """Convertir l'image en niveaux de gris."""
 3    source = Image.open(source).convert("RGB")
 4    dest = Image.new("RGB", source.size)
 5
 6    for x in range(source.width):
 7        for y in range(source.height):
 8            couleur = source.getpixel((x, y))
 9            moyenne = (couleur[0] + couleur[1] + couleur[2]) // 3
10            dest.putpixel((x, y), (moyenne, moyenne, moyenne))
11
12    dest.save(destination)

Extraction de la trame rouge

../../_images/exemple-transformation_extrait_rouge.jpg

Les trames bleue et verte sont réduite à zéro ; la trame rouge est conservée.

 1def transformation_extrait_rouge(source, destination):
 2    """Extraire la couleur rouge de l'image"""
 3    source = Image.open(source).convert("RGB")
 4    dest = Image.new("RGB", source.size)
 5
 6    for x in range(source.width):
 7        for y in range(source.height):
 8            original = source.getpixel((x, y))
 9            nouvelle = (original[0], 0, 0)
10            dest.putpixel((x, y), nouvelle)
11
12    dest.save(destination)

Extraction de la trame verte

../../_images/exemple-transformation_extrait_vert.jpg

Les trames rouge et bleue sont réduite à zéro ; la trame verte est conservée.

 1def transformation_extrait_vert(source, destination):
 2    """Extraire la couleur vert de l'image"""
 3    source = Image.open(source).convert("RGB")
 4    dest = Image.new("RGB", source.size)
 5
 6    for x in range(source.width):
 7        for y in range(source.height):
 8            original = source.getpixel((x, y))
 9            nouvelle = (0, original[1], 0)
10            dest.putpixel((x, y), nouvelle)
11
12    dest.save(destination)

Extraction de la trame bleue

../../_images/exemple-transformation_extrait_bleu.jpg

Les trames rouge et verte sont réduite à zéro ; la trame bleue est conservée.

 1def transformation_extrait_bleu(source, destination):
 2    """Extraire la couleur bleu de l'image"""
 3    source = Image.open(source).convert("RGB")
 4    dest = Image.new("RGB", source.size)
 5
 6    for x in range(source.width):
 7        for y in range(source.height):
 8            original = source.getpixel((x, y))
 9            nouvelle = (0, 0, original[2])
10            dest.putpixel((x, y), nouvelle)
11
12    dest.save(destination)

Éclaircir

../../_images/exemple-transformation_eclaircir.jpg

Chaque pixel est rapproché de la couleur blanche (à mi-chemin entre la couleur initiale et la couleur blanche).

 1def transformation_eclaircir(source, destination):
 2    """Éclaircir l'image"""
 3    source = Image.open(source).convert("RGB")
 4    dest = Image.new("RGB", source.size)
 5
 6    for x in range(source.width):
 7        for y in range(source.height):
 8            couleur = source.getpixel((x, y))
 9            dest.putpixel(
10                (x, y),
11                (couleur[0] // 2 + 128, couleur[1] // 2 + 128, couleur[2] // 2 + 128),
12            )
13
14    dest.save(destination)

Assombrir

../../_images/exemple-transformation_assombrir.jpg

Chaque pixel est rapproché de la couleur noire (à mi-chemin entre la couleur initiale et la couleur noire).

 1def transformation_assombrir(source, destination):
 2    """Assombrir l'image"""
 3    source = Image.open(source).convert("RGB")
 4    dest = Image.new("RGB", source.size)
 5
 6    for x in range(source.width):
 7        for y in range(source.height):
 8            couleur = source.getpixel((x, y))
 9            dest.putpixel((x, y), (couleur[0] // 2, couleur[1] // 2, couleur[2] // 2))
10
11    dest.save(destination)

Permuter les couleurs

../../_images/exemple-transformation_permuter.jpg

Les trames de chaque pixel sont permutées. Ceci est facilement visible sur cet exemple puisque les couleurs des trois dés sont (à peu près) les couleurs primaires rouge, vert, bleu. Les couleurs de ces trois dés sont donc permutées.

 1def transformation_permuter(source, destination):
 2    """Permuter les couleurs"""
 3    source = Image.open(source).convert("RGB")
 4    dest = Image.new("RGB", source.size)
 5
 6    for x in range(source.width):
 7        for y in range(source.height):
 8            couleur = source.getpixel((x, y))
 9            dest.putpixel((x, y), (couleur[1], couleur[2], couleur[0]))
10
11    dest.save(destination)

Augmenter le contraste

../../_images/exemple-transformation_contraste.jpg

Les couleurs claires (plus proches du blanc que du noir) sont éclaircies ; les couleurs sombres (plus proches du noir) sont assombries.

 1def transformation_contraste(source, destination):
 2    """Augmenter le contraste"""
 3    source = Image.open(source).convert("RGB")
 4    dest = Image.new("RGB", source.size)
 5
 6    for x in range(source.width):
 7        for y in range(source.height):
 8            couleur = list(source.getpixel((x, y)))
 9            for compteur in range(3):
10                if couleur[compteur] < 128:
11                    couleur[compteur] = couleur[compteur] // 2
12                else:
13                    couleur[compteur] = couleur[compteur] // 2 + 128
14            dest.putpixel((x, y), tuple(couleur))
15
16    dest.save(destination)

Couleurs psychédéliques

../../_images/exemple-transformation_psychedelique.jpg

A priori, n’importe quelle bijection (ou pas) de l’intervalle des entiers \([0; 255]\) dans lui-même convient, à condition qu’il y ait peu de points de discontinuité, pour que l’image de départ puisse tout de même être devinée.

 1def transformation_psychedelique(source, destination):
 2    """Produire une version psychédélique de l'image"""
 3    source = Image.open(source).convert("RGB")
 4    dest = Image.new("RGB", source.size)
 5
 6    for x in range(source.width):
 7        for y in range(source.height):
 8            couleur = list(source.getpixel((x, y)))
 9            for compteur in range(3):
10                couleur[compteur] = 16 * (couleur[compteur] % 16)
11            dest.putpixel((x, y), tuple(couleur))
12
13    dest.save(destination)

Réduire le nombre de couleurs

../../_images/exemple-transformation_reduit_couleurs.jpg

L’image utilise moins de couleur : cela se voit à la perte de détails.

Plutôt qu’autoriser chacun des entiers de 0 à 255 pour représenter chaque trame, seules trois valeurs sont autorisées : 0, 128, 255. Pour arriver à cela, chaque valeur est divisée par 128 (arrondie à l’entier le plus proche), ce qui produit les valeurs 0, 1, 2, puis le résultat est multiplié par 128 pour obtenir les valeurs souhaitées.

 1def transformation_reduit_couleurs(source, destination):
 2    """Réduire le nombre de couleurs"""
 3    source = Image.open(source).convert("RGB")
 4    dest = Image.new("RGB", source.size)
 5
 6    for x in range(source.width):
 7        for y in range(source.height):
 8            couleur = list(source.getpixel((x, y)))
 9            for compteur in range(3):
10                couleur[compteur] = 128 * round(couleur[compteur] / 128)
11            dest.putpixel((x, y), tuple(couleur))
12
13    dest.save(destination)

Inverser les couleurs

../../_images/exemple-transformation_inverse.jpg

La valeur de chaque trame se voit appliquer une symétrie par rapport au nombre 128 (ce qui correspond à la transformation affine \(x\mapsto 255-x\).

 1def transformation_inverse(source, destination):
 2    """Inverser les couleurs"""
 3    source = Image.open(source).convert("RGB")
 4    dest = Image.new("RGB", source.size)
 5
 6    for x in range(source.width):
 7        for y in range(source.height):
 8            couleur = source.getpixel((x, y))
 9            dest.putpixel(
10                (x, y), (255 - couleur[0], 255 - couleur[1], 255 - couleur[2])
11            )
12
13    dest.save(destination)

Déplacement de pixels

Symétrie gauche-droite

../../_images/exemple-transformation_symetrie_gauchedroite.jpg

Symétrie par rapport à l’axe des ordonnées.

L’ordonnée de chaque pixel est conservée ; à l’abscisse on applique une symétrie par rapport à la moitié de la largeur de l’image (ce qui correspond à la transformation affine \(x\mapsto L-x-1\) (où \(L\) est la largeur).

 1def transformation_symetrie_gauchedroite(
 2    source, destination
 3):  # pylint: disable=invalid-name
 4    """Effectuer la symérie gauche-droite"""
 5    source = Image.open(source).convert("RGB")
 6    dest = Image.new("RGB", source.size)
 7
 8    for x in range(source.width):
 9        for y in range(source.height):
10            dest.putpixel((source.width - x - 1, y), source.getpixel((x, y)))
11
12    dest.save(destination)

Symétrie haut-bas

../../_images/exemple-transformation_symetrie_hautbas.jpg

Symétrie par rapport à l’axe des abscisses.

L’abscisse de chaque pixel est conservée ; à l’ordonnée on applique une symétrie par rapport à la moitié de la hauteur de l’image (ce qui correspond à la transformation affine \(x\mapsto h-x-1\) (où \(h\) est la hauteur).

 1def transformation_symetrie_hautbas(source, destination):
 2    """Effectuer la symérie haut-bas"""
 3    source = Image.open(source).convert("RGB")
 4    dest = Image.new("RGB", source.size)
 5
 6    for x in range(source.width):
 7        for y in range(source.height):
 8            dest.putpixel((x, source.height - y - 1), source.getpixel((x, y)))
 9
10    dest.save(destination)

Rotation

../../_images/exemple-transformation_rotation90.jpg

Rotation d’angle \(\frac{\pi}{2}\) (90°).

Chaque pixel de coordonnées \((x; y)\) est déplacé aux coordonnées \((L-1-y; x)\) (où \(L\) est la largeur de l’image).

 1def transformation_rotation90(source, destination):
 2    """Pivoter la photo de 90° vers la gauche"""
 3    source = Image.open(source).convert("RGB")
 4    dest = Image.new("RGB", (source.height, source.width))
 5
 6    for x in range(source.height):
 7        for y in range(source.width):
 8            dest.putpixel((x, y), source.getpixel((source.width - 1 - y, x)))
 9
10    dest.save(destination)

Autre

Ajouteur un cadre

../../_images/exemple-transformation_ajouter_cadre.jpg

Les dimensions de l’image sont agrandies, et une couleur est affectée aux pixels des bords de l’image.

 1def transformation_ajouter_cadre(source, destination):
 2    """Ajouter un cadre"""
 3    source = Image.open(source).convert("RGB")
 4    bord = 5  # Épaisseur du cadre
 5    couleur = (122, 119, 184)
 6    dest = Image.new("RGB", (source.width + 2 * bord, source.height + 2 * bord))
 7
 8    for x in range(source.width):
 9        for y in range(source.height):
10            dest.putpixel((x + bord, y + bord), source.getpixel((x, y)))
11    for x in range(source.width + 2 * bord):
12        for y in range(source.height + 2 * bord):
13            if (
14                x <= bord
15                or x >= source.width + bord
16                or y <= bord
17                or y >= source.height + bord
18            ):
19                dest.putpixel((x, y), couleur)
20
21    dest.save(destination)

Réduire l’image (version rapide)

../../_images/exemple-transformation_reduire1.jpg

Chaque carrée de quatre pixels est réduit à un unique pixel égal au coin supérieur gauche du carré. Les autres pixels sont ignorés.

 1def transformation_reduire1(source, destination):
 2    """Réduire l'image de moitié (version rapide)"""
 3    source = Image.open(source).convert("RGB")
 4    dest = Image.new("RGB", (source.width // 2, source.height // 2))
 5
 6    for x in range(source.width // 2):
 7        for y in range(source.height // 2):
 8            dest.putpixel((x, y), source.getpixel((2 * x, 2 * y)))
 9
10    dest.save(destination)

Réduire l’image (version précise)

../../_images/exemple-transformation_reduire2.jpg

Chaque carrée de quatre pixels est réduit à un unique pixel égal à la moyenne des quatre pixels.

 1def transformation_reduire2(source, destination):
 2    """Réduire l'image de moitié (version plus précise)"""
 3    source = Image.open(source).convert("RGB")
 4    dest = Image.new("RGB", (source.width // 2, source.height // 2))
 5
 6    for x in range(source.width // 2):
 7        for y in range(source.height // 2):
 8            dest.putpixel(
 9                (x, y),
10                tuple(
11                    sum(l) // 4
12                    for l in zip(
13                        source.getpixel((2 * x, 2 * y)),
14                        source.getpixel((2 * x, 2 * y + 1)),
15                        source.getpixel((2 * x + 1, 2 * y)),
16                        source.getpixel((2 * x + 1, 2 * y + 1)),
17                    )
18                ),
19            )
20
21    dest.save(destination)