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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def transformation_noir_et_blanc(source, destination):
    """Convertir l'image en noir et blanc."""
    source = Image.open(source).convert("RGB")
    dest = Image.new("RGB", source.size)

    for x in range(source.width):
        for y in range(source.height):
            couleur = source.getpixel((x, y))
            if sum(couleur) > 3 * 127:
                nouvelle = (255, 255, 255)
            else:
                nouvelle = (0, 0, 0)
            dest.putpixel((x, y), nouvelle)

    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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def transformation_niveaux_de_gris(source, destination):
    """Convertir l'image en niveaux de gris."""
    source = Image.open(source).convert("RGB")
    dest = Image.new("RGB", source.size)

    for x in range(source.width):
        for y in range(source.height):
            couleur = source.getpixel((x, y))
            moyenne = (couleur[0] + couleur[1] + couleur[2]) // 3
            dest.putpixel((x, y), (moyenne, moyenne, moyenne))

    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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def transformation_extrait_rouge(source, destination):
    """Extraire la couleur rouge de l'image"""
    source = Image.open(source).convert("RGB")
    dest = Image.new("RGB", source.size)

    for x in range(source.width):
        for y in range(source.height):
            original = source.getpixel((x, y))
            nouvelle = (original[0], 0, 0)
            dest.putpixel((x, y), nouvelle)

    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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def transformation_extrait_vert(source, destination):
    """Extraire la couleur vert de l'image"""
    source = Image.open(source).convert("RGB")
    dest = Image.new("RGB", source.size)

    for x in range(source.width):
        for y in range(source.height):
            original = source.getpixel((x, y))
            nouvelle = (0, original[1], 0)
            dest.putpixel((x, y), nouvelle)

    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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def transformation_extrait_bleu(source, destination):
    """Extraire la couleur bleu de l'image"""
    source = Image.open(source).convert("RGB")
    dest = Image.new("RGB", source.size)

    for x in range(source.width):
        for y in range(source.height):
            original = source.getpixel((x, y))
            nouvelle = (0, 0, original[2])
            dest.putpixel((x, y), nouvelle)

    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).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def transformation_eclaircir(source, destination):
    """Éclaircir l'image"""
    source = Image.open(source).convert("RGB")
    dest = Image.new("RGB", source.size)

    for x in range(source.width):
        for y in range(source.height):
            couleur = source.getpixel((x, y))
            dest.putpixel(
                (x, y),
                (couleur[0] // 2 + 128, couleur[1] // 2 + 128, couleur[2] // 2 + 128),
            )

    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).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def transformation_assombrir(source, destination):
    """Assombrir l'image"""
    source = Image.open(source).convert("RGB")
    dest = Image.new("RGB", source.size)

    for x in range(source.width):
        for y in range(source.height):
            couleur = source.getpixel((x, y))
            dest.putpixel((x, y), (couleur[0] // 2, couleur[1] // 2, couleur[2] // 2))

    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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def transformation_permuter(source, destination):
    """Permuter les couleurs"""
    source = Image.open(source).convert("RGB")
    dest = Image.new("RGB", source.size)

    for x in range(source.width):
        for y in range(source.height):
            couleur = source.getpixel((x, y))
            dest.putpixel((x, y), (couleur[1], couleur[2], couleur[0]))

    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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def transformation_contraste(source, destination):
    """Augmenter le contraste"""
    source = Image.open(source).convert("RGB")
    dest = Image.new("RGB", source.size)

    for x in range(source.width):
        for y in range(source.height):
            couleur = list(source.getpixel((x, y)))
            for compteur in range(3):
                if couleur[compteur] < 128:
                    couleur[compteur] = couleur[compteur] // 2
                else:
                    couleur[compteur] = couleur[compteur] // 2 + 128
            dest.putpixel((x, y), tuple(couleur))

    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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def transformation_psychedelique(source, destination):
    """Produire une version psychédélique de l'image"""
    source = Image.open(source).convert("RGB")
    dest = Image.new("RGB", source.size)

    for x in range(source.width):
        for y in range(source.height):
            couleur = list(source.getpixel((x, y)))
            for compteur in range(3):
                couleur[compteur] = 16 * (couleur[compteur] % 16)
            dest.putpixel((x, y), tuple(couleur))

    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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def transformation_reduit_couleurs(source, destination):
    """Réduire le nombre de couleurs"""
    source = Image.open(source).convert("RGB")
    dest = Image.new("RGB", source.size)

    for x in range(source.width):
        for y in range(source.height):
            couleur = list(source.getpixel((x, y)))
            for compteur in range(3):
                couleur[compteur] = 128 * round(couleur[compteur] / 128)
            dest.putpixel((x, y), tuple(couleur))

    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\).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def transformation_inverse(source, destination):
    """Inverser les couleurs"""
    source = Image.open(source).convert("RGB")
    dest = Image.new("RGB", source.size)

    for x in range(source.width):
        for y in range(source.height):
            couleur = source.getpixel((x, y))
            dest.putpixel(
                (x, y), (255 - couleur[0], 255 - couleur[1], 255 - couleur[2])
            )

    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).

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

    for x in range(source.width):
        for y in range(source.height):
            dest.putpixel((source.width - x - 1, y), source.getpixel((x, y)))

    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).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def transformation_symetrie_hautbas(source, destination):
    """Effectuer la symérie haut-bas"""
    source = Image.open(source).convert("RGB")
    dest = Image.new("RGB", source.size)

    for x in range(source.width):
        for y in range(source.height):
            dest.putpixel((x, source.height - y - 1), source.getpixel((x, y)))

    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).

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

    for x in range(source.height):
        for y in range(source.width):
            dest.putpixel((x, y), source.getpixel((source.width - 1 - y, x)))

    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.

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

    for x in range(source.width):
        for y in range(source.height):
            dest.putpixel((x + bord, y + bord), source.getpixel((x, y)))
    for x in range(source.width + 2 * bord):
        for y in range(source.height + 2 * bord):
            if (
                x <= bord
                or x >= source.width + bord
                or y <= bord
                or y >= source.height + bord
            ):
                dest.putpixel((x, y), couleur)

    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.

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

    for x in range(source.width // 2):
        for y in range(source.height // 2):
            dest.putpixel((x, y), source.getpixel((2 * x, 2 * y)))

    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.

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

    for x in range(source.width // 2):
        for y in range(source.height // 2):
            dest.putpixel(
                (x, y),
                tuple(
                    sum(l) // 4
                    for l in zip(
                        source.getpixel((2 * x, 2 * y)),
                        source.getpixel((2 * x, 2 * y + 1)),
                        source.getpixel((2 * x + 1, 2 * y)),
                        source.getpixel((2 * x + 1, 2 * y + 1)),
                    )
                ),
            )

    dest.save(destination)