Formation I.S.N.

Module PIL - Exercices supplémentaires

Plus tard ! Les programmes ci-dessous ne sont pas à réaliser le jour de la formation (sauf s'il vous reste du temps bien évidemment). Ils sont là pour vous donner d'autres idées d'exercices (ou vous entraîner si besoin).

Négatif en couleurs

Le programme à concevoir doit faire en sorte que l'image obtenue soit l'image d'origine en «négatif», comme le présentent les deux images ci-dessous. L'image du logo ISN-IREM est téléchargeable en cliquant dessus.

Image d'origine au format .png Négatif de l'image
  • Une solution

##----- Importation des Modules -----##
from PIL import Image                             # Ne pas oublier de l'importer pour un programme indépendant

##----- Informations sur l'image d'origine -----##
im = Image.open('Logo_ISN-IREM.jpg')
couleur = im.mode
l, h = im.size

##----- Conception de la nouvelle image -----##
im_nega = Image.new(couleur, (l, h))              # Symetrie horizontale

for x in range(l):
    for y in range(h):                            # On parcourt chaque pixel de l'image analysée plus haut
        r, v, b = im.getpixel((x, y))             # On récupère le triplet de couleur
        
        im_nega.putpixel((x, y), (255-r, 255-v, 255-b))

im_nega.save('Logo_ISN-IREM_Negatif.jpg')
im_nega.show()

Empilement de carrés

L'image ci-contre est constituée de sept carrés de couleur noir, rouge, vert, jaune, bleu, magenta et cyan.

  1. Rappeler les triplets RGB permettant de représenter les couleurs précédentes.
  2. Concevoir une fonction carre() qui permet d'ajouter à une image un carré de 100 pixels de côté et de couleur unie.
  3. Utiliser cette fonction pour obtenir l'image ci-contre.
  • La fonction
  • Une solution

def carre(im, h, r, v, b):
    """ Entrees : im est une image, h est une ordonnée, (r, v, b) est un triplet d'entiers entre 0 et 255
		Sorties : Ajoute à une image im (à partir de l'ordonnée h) un carré de 100 pixels de côté et de
			couleur ayant pour composantes (r, v, b)."""
    for x in range(100) :
        for y in range(h*100, (h+1)*100) :
            im.putpixel((x, y), (r, v, b))

Voici une méthode qui « joue » sur les composantes RVB des couleurs primaires et secondaires :


##----- Importation des Modules -----##
from PIL import Image                               # Ne pas oublier de l'importer pour un programme indépendant

##------ Définition des Fonctions ------##
def carre(im, h, r, v, b):
    """Cette fonction ajoute à une image im un carré de 100 pixels
    de côté et de couleur unie."""
    for x in range(100) :
        for y in range(h*100, (h+1)*100) :
            im.putpixel((x, y), (r, v, b))

##------ Programme principal ------##
##----- Informations sur l'image -----##
largeur = 100                                       # largeur de l'image, en pixels
hauteur = 700                                       # hauteur de l'image, en pixels
couleur = 'RGB'

##----- Conception de la nouvelle image -----##
im = Image.new(couleur, (largeur, hauteur))

h = 0
for i in range(2):
    for j in range(2):
        for k in range(2):
            if not(i == j == k ==1):
                carre(im, h, 255*k, 255*j, 255*i)
                h += 1

im.save('Carres_Empiles.jpg')
im.show()

Dégradé de 256x256x256 couleurs

On souhaite afficher la palette de toutes les couleurs possibles (certaines en double) au format RGB (ci-contre, sans les textes).

  1. Quelles sont les dimensions, en pixels, de cette figure ?
  2. On s'intéresse au rectangle Jaune-Cyan-Bleu-Rouge.
    Exprimer les composantes R, G et B d'un pixel en fonction de son abscisse x et de sa colonne y.
  3. Exprimer de la même manière les composantes R, G et B d'un pixel du second rectangle en fonction de son abscisse x et de sa colonne y.
  4. En déduire le programme permettant d'afficher cette palette de couleur.
  • Une solution

##----- Importation des Modules -----##
from PIL import Image                               # Ne pas oublier de l'importer pour un programme indépendant

##----- Informations sur l'image -----##
largeur = 511                                       # largeur de l'image, en pixels
hauteur = 511                                       # hauteur de l'image, en pixels
couleur = 'RGB'

##----- Conception de la nouvelle image -----##
im = Image.new(couleur, (largeur, hauteur))

for x in range(largeur):
	# La figure est divisée en quatre carrés
	# Première moitié des abscisses
    if x < 256:
        r = 255-x
        b = 0
	
	# Seconde moitié des abscisses
    else:
        r = 0
        b = x-255
    for y in range(hauteur):
		# Le carré "blanc"
        if x < 256 and y > 255:
            r, v, b = 255, 255, 255
		# Première moitié des ordonnées
        elif y < 256:
            v = 255-y
		# Seconde moitié des ordonnées
        else:
            v = 0
            r = y-255
        im.putpixel((x, y), (r, v, b))

im.save('Palette.jpg')
im.show()

Mise en relief

Cet exercice est un extrait et une adaptation du document ressource disponible sur le site eduscol.

Dans une image matricielle, chaque pixel a, au maximum, huit voisins.
On a représenté ci-contre le pixel de coordonnées (x, y) avec ses voisins.
A partir d'une image en nuances de gris (au format 'L' du module PIL, c'est-à-dire que la nuance de chaque pixel est définie par un unique entier compris entre 0 et 255.), programmer l'algorithme ci-dessous afin d'obtenir une «mise en relief» de cette image :

  • On considère un pixel de coordonnées (x, y) et de niveau de gris g.
  • On calcule la nouvelle valeur de g à partir des niveaux de gris des pixels voisins :
    g = 128 + (-2*m-n-p+q+s+2*t) // 8.
    On pourra définir une fonction voisins() qui renvoie la liste des coordonnées des pixels voisins du pixel de coordonnées (x, y).

Question supplémentaire : justifier que cette nouvelle valeur de g est bien comprise entre 0 et 255.

Image d'origine Image en relief
  • Une piste
  • La fonction voisins()
  • Une solution
  • Une variante
Je suis gentil, voici une petite piste :
on pourra commencer par transformer l'image d'origine en lui ajoutant des «bords noirs» de 1 pixel d'épaisseur pour avoir une fonction voisins() plus facile à définir...

Première fonction possible : on a encadré l'image de bords noirs donc chaque pixel de l'image d'origine a huit voisins.


def voisins(x, y, l, h):
    """ Entrees : x et y sont les coordonnées d'un pixel situé dans une image de largeur l et de hauteur h
		Sorties : Renvoie la liste des coordonnées des pixels voisins de ce pixel."""
    liste = [(x-1, y-1),                            # On considère que chaque pixel a 8 voisins
             (x-1, y),
             (x-1, y+1),
             (x, y-1),
             (x, y+1),
             (x+1, y-1),
             (x+1, y),
             (x+1, y+1)]
	return liste

Seconde fonction possible : on gère les pixels situés au bord et/ou dans les «coins» de l'image.


def voisins(x, y, l, h):
    """ Entrees : x et y sont les coordonnées d'un pixel situé dans une image de largeur l et de hauteur h
		Sorties : Renvoie la liste des coordonnées des pixels voisins de ce pixel."""
    liste = [(x-1, y-1),                            # On considère que chaque pixel a 8 voisins
             (x-1, y),
             (x-1, y+1),
             (x, y-1),
             (x, y+1),
             (x+1, y-1),
             (x+1, y),
             (x+1, y+1)]

	for coor in reversed(liste):                    # On parcourt la liste renversée
        if coor[0]<0 or coor[0]>l-1 or coor[1]<0 or coor[1]>h-1:
            liste.remove(coor)                      # Lorsque les coordonnées sont hors-domaine, on les enlève de la liste
                                                    # (raison pour laquelle il faut parcourir la liste renversée)
    return liste

Avec la première fonction voisins() et la création d'une image comportant des bords :


##----- Importation des Modules -----##
from PIL import Image

##----- Définition des Fonctions -----##
def voisins(x, y, l, h):
    """ Entrees : x et y sont les coordonnées d'un pixel situé dans une image de largeur l et de hauteur h
        Sorties : Renvoie la liste des coordonnées des pixels voisins de ce pixel."""
    liste = [(x-1, y-1),                            # On considère que chaque pixel a 8 voisins
             (x-1, y),
             (x-1, y+1),
             (x, y-1),
             (x, y+1),
             (x+1, y-1),
             (x+1, y),
             (x+1, y+1)]
    return liste


def compo(im, liste):
    """ Entrees : im est une image, liste contient les coordonnées des voisins d'un pixel
        Sorties : Renvoie la composante grise du pixel calculée à partir des composantes
            de ses 6 voisines m, n, p, q, s et t dont les coordonnées se trouvent aux indices
            0, 1, 3, 4, 6, 7 dans la liste."""
    for i in range(8):
        liste[i] = int(im.getpixel(liste[i]))
    return 128 + (-2*liste[0]-liste[1]-liste[3]+liste[4]+liste[6]+2*liste[7])


##----- Informations sur l'image d'origine -----##
im = Image.open('Logo_ISN-IREM_Gris.jpg')
l, h = im.size

##----- Ajout de bords à cette image -----##
im_grande = Image.new('L', (l+2, h+2))
                                            
for x in range(l+2):
    for y in range(h+2):
        if x==0 or y==0 or x==l+1 or y==h+1:
            im_grande.putpixel((x,y), 0) 
        else:
            im_grande.putpixel((x,y), im.getpixel((x-1, y-1)))

im_grande.save('Logo_ISN-IREM_Bords.jpg')
im_grande.show()
                                         

##----- Conception de la nouvelle image -----##
im_grande = Image.open('Logo_ISN-IREM_Bords.jpg')
im_relief = Image.new('L', (l, h))
                                            
for x in range(l):
    for y in range(h):
        im_relief.putpixel((x,y), compo (im_grande, voisins(x+1, y+1, l, h))) 
                                         

##-----Finalisation-----##
im_relief.save('Logo_ISN-IREM_Relief.jpg')
im_relief.show()

Avec la seconde fonction voisins(), davantage modifiée :


##----- Importation des Modules -----##
from PIL import Image

##----- Définition des Fonctions -----##
def voisins(im, x, y, l, h):
    """ Entrees : x et y sont les coordonnées d'un pixel situé dans l'image im de largeur l et de hauteur h
        Sorties : Renvoie la liste des coordonnées des pixels voisins de ce pixel."""
    liste = [(x-1, y-1),                            # On considère les 6 voisins "intéressants"
             (x-1, y),
             (x, y-1),
             (x, y+1),
             (x+1, y),
             (x+1, y+1)]
    s = []                                          # Liste des composantes actualisées m, n, p, q, s et t
             
    for coor in liste:
        if coor[0]<0 or coor[0]>l-1 or coor[1]<0 or coor[1]>h-1:
            s.append(255)                           # Si le pixel est "hors cadre", on lui attribue la couleur blanc
        else:
            s.append(im.getpixel(coor))             # Sinon on ajoute sa composante
    
    g = 128 + (-2*s[0]-s[1]-s[2]+s[3]+s[4]+2*s[5])
    return g


##----- Informations sur l'image d'origine -----##
im = Image.open('Logo_ISN-IREM_Gris.jpg')
l, h = im.size

##----- Conception de la nouvelle image -----##
im_relief = Image.new('L', (l, h))
                                            
for x in range(l):
    for y in range(h):
        im_relief.putpixel((x,y), voisins(im, x, y, l, h)) 
                                         

##-----Finalisation-----##
im_relief.save('Logo_ISN-IREM_Relief.jpg')
im_relief.show()

Algorithme de seuil

Pour localiser différents objets dans une image, il est nécessaire de réussir à localiser les contours de ces objets. Généralement, les nuances des pixels voisins seront très différentes les unes des autres.

Pour cela, on peut appliquer un algorithme de distance 1 (au sens de «distance dans un repère orthonormé») qui va calculer pour le pixel de coordonnées (x, y) la distance entre les niveaux de gris de ses voisins : \(d = \sqrt{\left(n-s\right)^2+\left(p-q \right)^2}\).

En comparant cette distance à un certain seuil (à définir), le pixel de coordonnées (x, y) sera affiché soit en noir, soit en blanc. Appliquer le programme ainsi conçu au logo ISN-IREM Gris définit en nuances de gris.

Image d'origine Image détourée
  • Une piste
  • Une solution
On importe la racine carrée avec le module math. Pour le reste, c'est quasi-identique à la mise en relief.

Attention, l'image d'origine est en mode couleur 'L' qui définit une image en nuances de gris.


##----- Importation des Modules -----##
from PIL import Image
from math import sqrt

##----- Définition des Fonctions -----##
def ecart(im, x, y, l, h):
    """ Entrees : x et y sont les coordonnées d'un pixel situé dans une image de largeur l et de hauteur h
		Sorties : calcule les variations de niveau de gris autour (distance 1) de ce pixel"""
    liste = [(x, y-1),                  		# On considère les 4 voisins "intéressants"
             (x, y+1),
             (x-1, y),
             (x+1, y)]
    compo = []                         		 	# Liste des composantes actualisées n, s, p et q
             
    for coor in liste:
        if coor[0]<0 or coor[0]>l-1 or coor[1]<0 or coor[1]>h-1:
            compo.append(255)               	# Si le pixel est "hors cadre", on lui attribue la couleur blanc
        else:
            compo.append(im.getpixel(coor)) 	# Sinon on ajoute sa composante
    
    d = int(sqrt((compo[0]-compo[1])**2+(compo[2]-compo[3])**2))
    return d

##----- Informations sur l'image d'origine -----##
im = Image.open('Logo_ISN-IREM_Gris.jpg')
l, h = im.size

##----- Conception de la nouvelle image -----##
seuil = 25                              # Seuil de "détourage"
im_detour = Image.new('L', (l, h))
                                            
for x in range(l):
    for y in range(h):
        if ecart(im, x, y, l, h) < seuil:
            im_detour.putpixel((x,y), 255)
        else:
            im_detour.putpixel((x,y), 0)
                                         

##-----Finalisation-----##
im_detour.save('Logo_ISN-IREM_detoure.jpg')
im_detour.show()

Compresser une image

Le logo ISN-IREM est trop grand (1600*1000) pour être utilisée en vraie grandeur dans certains des exercices précédents L'image a été compressée (réduite) de manière automatique par le logiciel d'affichage (ici le navigateur).

Le but de cet exercice est de programmer une compression de l'image en suivant l'algorithme ci-dessous :

  1. On note k le coefficient de réduction : la largeur et la hauteur de l'image seront divisées par k donc le nombre total de pixels sera divisé par .
  2. On «regroupe» les pixels par carrés de côtés k.
    Ci-contre, de côté k = 2.
  3. On remplace ces pixels par un unique pixel dont les composantes (r, v, b) sont les moyennes des composantes des pixels de l'image d'origine.
  • Une solution
  • Une variante

On a appliqué le coefficient de réduction k = 4 :


##----- Importation des Modules -----##
from PIL import Image
                     
##----- Coefficient de Réduction -----##
k = 4

##----- Informations sur l'image d'origine -----##
im = Image.open('Logo_ISN-IREM_Original.jpg')
l, h = im.size

##----- Conception de la nouvelle image -----##
largeur, hauteur = l//k, h//k                               # Nouvelles dimensions
im_red = Image.new('RGB', (largeur, hauteur))               # Image "compressée"

for x in range(0, largeur*k, k):                            # Saut de k en k en largeur
    for y in range(0, hauteur*k, k):                        # Saut de k en k en hauteur
        rouge, vert, bleu = 0, 0, 0                         # Somme des composantes des k² pixels
        for i in range(k):
            for j in range(k):
                r, v, b = im.getpixel((x+i, y+j))
                rouge += r
                vert += v
                bleu += b
        
        rouge = rouge//(k*k)                                # On divise les sommes par le nombre de pixels (moyennne)
        vert = vert//(k*k)
        bleu = bleu//(k*k)
        
        # On place le pixel dans l'image réduite (attention aux coordonnées...)
        im_red.putpixel((x//k, y//k), (rouge, vert, bleu))
                                         

##-----Finalisation-----##
im_red.save('Logo_ISN-IREM_Reduit.jpg')                     # Sauvegarde
im_red.show()                                               # Affichage

Le programme précédent «détruit» dans l'image les lignes en bas et les colonnes à droite qui sont en trop. Avec un peu d'arithmétique, on peut «harmoniser» cette destruction entre les lignes du haut et du bas ainsi qu'entre les colonnes de gauche et de droite :


##----- Importation des Modules -----##
from PIL import Image

##----- Définition des Fonctions -----##
def moy(im, x, y, k):
	""" Entrees : im est une image, x et y sont les coordonées du pixel étudié, k est le coef de réduction
		Sorties : Renvoie le nouveu triplet de composantes (r, v, b) du pixel de coordonnées (x, y).
			Ces composantes sont la moyenne des composantes d'un carré de pixels de côté k."""
    rouge, vert, bleu = 0, 0, 0                             # Somme des composantes des k² pixels
    for i in range(k):
        for j in range(k):
            r, v, b = im.getpixel((x+i, y+j))
            rouge += r
            vert += v
            bleu += b
        
    rouge = rouge//(k*k)                                    # On divise les sommes par le nombre de pixels (moyennne)
    vert = vert//(k*k)
    bleu = bleu//(k*k)
    return (rouge, vert, bleu)

##----- Coefficient de Réduction -----##
k = 4

##----- Informations sur l'image d'origine -----##
im = Image.open('Logo_ISN-IREM_Original.jpg')
l, h = im.size

##----- Conception de la nouvelle image -----##
largeur, hauteur = l//k, h//k                               # Nouvelles dimensions
im_red = Image.new('RGB', (largeur, hauteur))               # Image "compressée"

espace_l = (l-k*largeur)//2                                 # nombre de pixels à enlever à gauche des lignes
espace_c = (h-k*hauteur)//2                                 # nombre de pixels à enlever en haut des colonnes

for x in range(espace_l, espace_l+largeur*k, k):            # Saut de k en k en largeur
    for y in range(espace_c, espace_c+hauteur*k, k):        # Saut de k en k en hauteur
                
        # On place le pixel dans l'image réduite (attention aux coordonnées...)
        im_red.putpixel((x//k, y//k), moy(im, x, y, k))
##-----Finalisation-----##
im_red.save('Logo_ISN-IREM_Reduit.jpg')                     # Sauvegarde
im_red.show()                                               # Affichage