Aller au contenu

Une photo abîmée

On travaille ici avec cette photo.

Cette photo a subi une dégradation: une série de points noirs sur le visage.

Note

La dégradation choisie ici est "artificielle" car il s'agit de mettre en oeuvre des principes beaucoup plus simples que les principes utilisés aujourd'hui par les logiciels dans ce domaine.

L'objectif est que vous ayez une première idée des principes que l'on peut mettre en oeuvre.

Utilisation de la bibliothèque pillow.

Testez le script suivant. Il utilise pillow. Les instructions utilisées sont celles dont vous aurez besoin dans la suite.

Placez l'image dans le même dossier que votre fichier python.

from PIL import Image
from random import randint

# ouverture de l'image initiale:
source = Image.open('photodegradee.png')

# récupération de ses dimensions:
largeur, hauteur = source.size
print(f"L'image a {largeur} colonnes et {hauteur} lignes.")


print(f"Le pixel du coin haut gauche a pour couleur {source.getpixel((0,0))}.")
print(f"Le pixel du coin bas droit a pour couleur {source.getpixel((largeur-1, hauteur-1))}.")


# création d'un carré rouge dans l'image:
for ligne in range(50, 100):
    for colonne in range(70,120):
        source.putpixel((colonne,ligne), (255,0,0))


source.save('degradationsSupp.png')
Rappel: format pour python version < 3.6

Rappelons que les f-string ne sont disponibles qu'à partir de python 3.6.
Dans une version antérieure, on utilisera format:

from PIL import Image
from random import randint

# ouverture de l'image initiale:
source = Image.open('photodegradee.png')

# récupération de ses dimensions:
largeur, hauteur = source.size
print(f"L'image a {largeur} colonnes et {hauteur} lignes.")


print("Le pixel du coin haut gauche a pour couleur {}.".format(source.getpixel((0,0))))
print("Le pixel du coin bas droit a pour couleur {}.".format(source.getpixel((largeur-1, hauteur-1))))


# création d'un carré rouge dans l'image:
for ligne in range(50, 100):
    for colonne in range(70,120):
        source.putpixel((colonne,ligne), (255,0,0))


source.save('degradationsSupp.png')

Chaque pixel est repéré dans l'image par ses coordonnées:

  • le pixel (0,0) est le coin supérieur gauche,
  • et le pixel (largeur-1, hauteur-1) est le coin inférieur droit.

Les colonnes sont numérotées dans l'ordre croissant de gauche à droite, les lignes sont numérotées dans l'ordre croissant de haut en bas.

Chaque pixel a une couleur de la forme (composante rouge, composante vert, composante bleu). Chaque composante est un entier entre 0 et 255.

Exercice 1

Écrire une fonction python qui prend en paramètres les couples de coordonnées de deux pixels et renvoie la distance euclidienne entre ces deux pixels.

Un code possible
from math import sqrt 
def distance(pixelA, pixelB):
    """
    pixelA -- couple  de coordonnées d'un pixel  de l'image.
    pixelB -- couple  de coordonnées d'un pixel  de l'image.

    Renvoie la distance euclidienne entre ces pixels.
    """
    XA, YA = pixelA
    XB, YB = pixelB
    dx, dy = XB - XA, YB - YA
    return sqrt(dx**2 + dy**2)

Deux listes de pixels

Les pixels noirs qui dégradent la photo ont pour couleur (0, 0, 0). Il n'y aucun autre point de la photo ayant cette couleur.

À l'aide d'un logiciel de traitement de l'image, comme GIMP, on a délimité la zone des pixels dégradés:

  • lignes entre 930 et 1000,
  • colonnes entre 1200 et 1500.
# liste des coordonnées de la zone comportant des pixels dégradés: 
LISTE000 = [(col,lig)  for col in range(1200, 1501) for lig in range(930,1001)]

Notre objectif va être de réparer les points noirs par des points ayant la couleur initiale.

On définit pour cela une seconde liste couvrant une partie du visage, cette seconde liste sera notre liste de couleurs de référence pour les réparations.

# une liste des coordonnées des points d'une zone plus large que la dégradée
# mais privée des pixels dégradés :
LISTENon000 = [(col,lig) for col in range(1100, 1600) for lig in range(900,1050)
if SOURCE.getpixel((col,lig)) != (0,0,0)]

Exercice 2

Proposer un code pour le corps de la fonction suivante:

def kPlusProchesVoisins(pixel_a_corriger, k):
    """
    k -- entier naturel  (non nul et au plus longueur de la liste LISTENon000)
    pixel_a_corriger -- couple des coordonnées d'un pixel (pixel dégradé de l'image)


    Renvoie la liste des k pixels  les plus proches de pixel_a_corriger
    (sous la forme de couples de coordonnées).
    parmi ceux de la liste LISTENon000.
    """  
Un code possible
def kPlusProchesVoisins(pixel_a_corriger, k):
    """
    k -- entier naturel  (non nul et au plus longueur de la liste LISTENon000)
    pixel_a_corriger -- couple des coordonnées d'un pixel (pixel dégradé de l'image)


    Renvoie la liste des k pixels  les plus proches de pixel_a_corriger
    (sous la forme de couples de coordonnées).
    parmi ceux de la liste LISTENon000.
    """
    # liste des couples (distance, pixel)
    # où distance est la distance 
    # entre pixel_a_corriger et   un pixel pris dans LISTENon000
    L = [(distance(pixel_a_corriger, pixel), pixel) for pixel in LISTENon000]
    # tri de cette liste (ordre croissant des distances):
    L.sort(key= lambda x: x[0])
    # on renvoie les k premiers pixels (k plus proches voisins):
    return [L[j][1] for j in range(k)]

Exercice 3

Plutôt que d'utiliser ce qui précède pour "classer" les pixels détériorés (une classe étant caractérisée par une couleur), on décide, pour reconstituer la photo, de remplacer la couleur (0,0,0) d'un pixel dégradé par la moyenne des couleurs de ses k voisins les plus proches.

Écrire une fonction spécifiée comme suit:

def couleurMoyenne(pixel_a_corriger, k):
    """
    k -- entier naturel  (non nul et au plus longueur de la liste LISTENon000)
    pixel -- couple des coordonnées d'un pixel de l'image

    Renvoie la  moyenne des couleurs des k pixels non dégradés plus proches de pixel.
    """  
Un code possible
def couleurMoyenne(pixel_a_corriger, k):
    """
    k -- entier naturel  (non nul et au plus longueur de la liste LISTENon000)
    pixel -- couple des coordonnées d'un pixel de l'image

    Renvoie la  moyenne des couleurs des k pixels non dégradés plus proches de pixel.
    """  
    voisinspp = kPlusProchesVoisins(pixel_a_corriger, k)
    couleursVoisins = [SOURCE.getpixel(voisin) for voisin in voisinspp]

    rouge = sum([couleur[0] for couleur in couleursVoisins])//k
    vert = sum([couleur[1] for couleur in couleursVoisins])//k
    bleu = sum([couleur[2] for couleur in couleursVoisins])//k
    return (rouge, vert, bleu))

Exercice 4

Il vous reste à écrire une fonction qui remplace effectivement les couleurs des points dégradés par la moyenne calculée précédemment. Et enfin de tester et visualiser si le résultat est satisfaisant.

Un code possible

Ci-dessous un code possible.
Vous constaterez, sur le fichier image obtenu, l'absence de défaut apparent.

Attention

Le programme demande du temps pour son exécution... Lancez le programme et commencez à travailler la suite...

from PIL import Image
from math import sqrt, inf


SOURCE = Image.open('photodegradee.png')



# liste des coordonnées de la zone comportant des pixels dégradés: 
LISTE000 = [(col,lig)  for col in range(1200, 1501) for lig in range(930,1001)]




# une liste des coordonnées des points d'une zone plus large que la dégradée
# mais privée des pixels dégradés :
LISTENon000 = [(col,lig) for col in range(1100, 1600) for lig in range(900,1050)
if SOURCE.getpixel((col,lig)) != (0,0,0)]






def distance(pixelA, pixelB):
    """
    pixelA -- couple  de coordonnées d'un pixel  de l'image.
    pixelB -- couple  de coordonnées d'un pixel  de l'image.

    Renvoie la distance euclidienne entre ces pixels.
    """
    XA, YA = pixelA
    XB, YB = pixelB
    dx, dy = XB - XA, YB - YA
    return sqrt(dx**2 + dy**2)



def kPlusProchesVoisins(pixel_a_corriger, k):
    """
    k -- entier naturel  (non nul et au plus longueur de la liste LISTENon000)
    pixel_a_corriger -- couple des coordonnées d'un pixel (pixel dégradé de l'image)


    Renvoie la liste des k pixels  les plus proches de pixel_a_corriger
    (sous la forme de couples de coordonnées).
    parmi ceux de la liste LISTENon000.
    """
    # liste des couples (distance, pixel)
    # où distance est la distance 
    # entre pixel_a_corriger et   un pixel pris dans LISTENon000
    L = [ (distance(pixel_a_corriger, pixel), pixel) for pixel in LISTENon000]
    # tri de cette liste (ordre croissant des distances):
    L.sort(key= lambda x: x[0])
    # on renvoie les k premiers pixels (k plus proches voisins):
    return [L[j][1] for j in range(k)]





def couleurMoyenne(pixel_a_corriger, k):
    """
    k -- entier naturel  (non nul et au plus longueur de la liste LISTENon000)
    pixel -- couple des coordonnées d'un pixel de l'image

    Renvoie la  moyenne des couleurs des k pixels non dégradés plus proches de pixel.
    """  
    voisinspp = kPlusProchesVoisins(pixel_a_corriger, k)
    couleursVoisins = [SOURCE.getpixel(voisin) for voisin in voisinspp]

    rouge = sum([couleur[0] for couleur in couleursVoisins])//k
    vert = sum([couleur[1] for couleur in couleursVoisins])//k
    bleu = sum([couleur[2] for couleur in couleursVoisins])//k
    return (rouge, vert, bleu)


def panseLesPlaies(k):
    """
    k -- entier (notre paramètre de la méthode k-NN)

    Parcourt les pixels dégradés et remplace leur couleur
    par la couleur moyenne des k voisins les plus proches pris dans LISTENon000.
    """   
    for pix in LISTE000:
        couleur = SOURCE.getpixel(pix)
        if couleur == (0,0,0):
            SOURCE.putpixel(pix, couleurMoyenne(pix, k))
    SOURCE.save('photoreparee.png')



panseLesPlaies(3)