SNT Photo Contour

Identification

Infoforall

5 - Contour sur une image


La dernière fois, vous avez pris Thonny en main, vous avez appris à installer un module, à créer un programme avec Python et à modifier des images à l'aide d'un programme Python.

Aujourd'hui : révisions des notions vues la fois dernière et application avec la réalisation d'une nouvelle fonction.

1 - Python-Variable-Fonction

Pour manipuler les images avec Python, il faut possèder un module spécifique, qui gère les images : Pillow.

Commençons par voir si vous avez le module.

01° Tapez ceci dans la console pour voir si cela déclenche une erreur.

>>> from PIL import Image

Si c'est le cas :

  • Ouvrir le menu Tool/Outils en haut vers la droite dans Thonny
  • Sélectionner Manage Packages/Gérer les Paquets
  • Faire une recherche sur Pillow puis installer la bibliothèque. Ca peut être un peu long.
Vue Manage Packages

02° Téléchargez une image de type png ou jpg sur le Web et placez la dans un dossier nommé SNT-photographie. Choisissez une image possèdant pas mal de couleurs différentes si possible.

03° Enregistrer le programme ci-dessous (après avoir lu la remarque ATTENTION) avec Thonny en le plaçant dans le même répertoire SNT-photographie que votre image. ATTENTION : il faudra modifier le nom du fichier stocké dans la variable base de façon à ce qu'il corresponde à celui de votre fichier-image.

📁 SNT-photographie

📄 contour.py

📄 votre_image.png

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 69 70 71 72 73 74 75 76 77 78
from PIL import Image as Img # Nom du fichier-image original base = "nom_a_modifier.png" # Nom du fichier-image modifié sauvegarde = "modification.png" def pixel(nom, x, y): """Fonction qui renvoie les intensité d'un pixel (x,y) d'une image""" # Création de l'objet-image propre à Python ref_image = Img.open(nom) # Si image avec 3 couches : R G B if len(ref_image.getbands()) == 3: # On récupère les valeurs RGB du pixel de coordonnées (x,y) rouge, vert, bleu = ref_image.getpixel( (x,y) ) return(f"Pixel de coordonnées ({x}, {y}) : R = {rouge} - G = {vert} - B = {bleu}") # Si image avec 4 couches : R G B et transparence (alpha) if len(ref_image.getbands()) == 4: # On récupère les valeurs RGB du pixel de coordonnées (x,y) rouge, vert, bleu, alpha = ref_image.getpixel( (x,y) ) return(f"Pixel de coordonnées ({x}, {y}) : R = {rouge} - G = {vert} - B = {bleu} - A = {alpha}") # Si image grise avec 1 couche : if len(ref_image.getbands()) == 1: # On récupère la valeur du pixel de coordonnées (x,y) intensite = ref_image.getpixel( (x,y) ) return(f"Pixel de coordonnées ({x}, {y}) : Intensité = {intensite}") def filtrer_image(nom): """Fonction qui renvoie un objet-image après l'avoir filtré pixel par pixel""" # Création de l'objet-image propre à Python ref_image = Img.open(nom) # Lecture et action sur les pixels, un par un largeur, hauteur = ref_image.size # Si image avec 3 couches : R G B if len(ref_image.getbands()) == 3: for x in range(largeur): for y in range(hauteur): # On récupère les valeurs RGB du pixel de coordonnées (x,y) rouge, vert, bleu = ref_image.getpixel( (x,y) ) # On transforme les valeurs RGB avec la fonction de filtrage rouge, vert, bleu = filtrage(rouge, vert, bleu) # On transforme l'image-Python en mémoire ref_image.putpixel( (x, y) , (rouge, vert, bleu) ) # Sinon, si image avec 4 couches : R G B + Transparence(alpha) if len(ref_image.getbands()) == 4: for x in range(largeur): for y in range(hauteur): # On récupère les valeurs RGB du pixel de coordonnées (x,y) rouge, vert, bleu, alpha = ref_image.getpixel( (x,y) ) # On transforme les valeurs RGB avec la fonction de filtrage rouge, vert, bleu = filtrage(rouge, vert, bleu) # On transforme l'image-Python en mémoire ref_image.putpixel( (x,y), (rouge, vert, bleu, alpha) ) return ref_image def filtrage(r, g, b): """Fonction qui renvoie des valeurs r, g, b après les avoir modifiées""" rouge = b # On place la valeur b dans rouge vert = g # On laisse la valeur g dans vert bleu = r # On place la valeur r dans bleu return (rouge, vert, bleu) # Création d'une nouvelle image à partir de la source originale nouvelle = filtrer_image(base) # Affichage de l'objet-image Python à l'écran nouvelle.show() # Sauvegarde de l'image dans un nouveau fichier-image nouvelle.save(sauvegarde)

04° Lancer votre programme. Si une erreur apparaît (en rouge), lire le message.

Souvent :

  • soit vous n'avez pas installé le module Pillow !
  • soit vous n'avez pas modifié le nom associé à la variable base
  • soit vous n'avez pas mis votre image et votre fichier Python dans le même répertoire

Si vous bloquez, faire appel à l'enseignant.

Vous devriez avoir une image à l'écran, mais une image où les intensités bleu et le rouge ont été inversées : voir les lignes suivantes

66 67 68
rouge = b # On place la valeur b dans rouge vert = g # On laisse la valeur g dans vert bleu = r # On place la valeur r dans bleu

Exemple avec une image contenant beaucoup de rouge et d'orange :

Image contenant beaucoup de rouge
Image de base libre de droit : le Palais des congrès de Montréal

Et qui devient une image contenant beaucoup de bleu et de cyan :

Image contenant beaucoup de bleu
Même image en intervertisant Bleu et Rouge

05° Comment se nomment rouge, vert et bleu ?

  1. Fonction
  2. Variable
  3. Module
  4. Valeur
66 67 68
rouge = b # On place la valeur b dans rouge vert = g # On laisse la valeur g dans vert bleu = r # On place la valeur r dans bleu

...CORRECTION...

Ce sont des variables.

06° Si on écrit ceci :

66 67 68
rouge = b # On place la valeur b dans rouge vert = g # On laisse la valeur g dans vert bleu = r # On place la valeur r dans bleu

Est-ce

  1. rouge qu'on remplit avec r ou
  2. r qu'on remplit avec rouge

...CORRECTION...

C'est rouge qu'on remplit avec r.

Python évalue le membre de droite et va ensuite créer la variable à gauche et la remplir avec ce qu'il a évalué à droite.

C'est pour cela qu'on trouve souvent une fléche orientée plutôt qu'un égal dans les algorithmes :

rouger.

07° Comment se nomme filtrage :

  1. Fonction
  2. Variable
  3. Module
  4. Valeur
64 65 66 67 68 69
def filtrage(r, g, b): """Fonction qui renvoie des valeurs r, g, b après les avoir modifiées""" rouge = b # On place la valeur b dans rouge vert = g # On laisse la valeur g dans vert bleu = r # On place la valeur r dans bleu return (rouge, vert, bleu)

...CORRECTION...

C'est une fonction.

08° Que vont contenir les variables-paramètres r, g et b si on lance un appel à la fonction de cette façon :

>>> filtrage(200, 100, 50)
64 65 66 67 68 69
def filtrage(r, g, b): """Fonction qui renvoie des valeurs r, g, b après les avoir modifiées""" rouge = b # On place la valeur b dans rouge vert = g # On laisse la valeur g dans vert bleu = r # On place la valeur r dans bleu return (rouge, vert, bleu)

...CORRECTION...

C'est comme si on avait tapé ceci dans la fonction :

  1. r = 200
  2. g = 100
  3. b = 50

09° Comment indique-t-on à Python que les lignes 65 à 69 font partie de la fonction ?

64 65 66 67 68 69
def filtrage(r, g, b): """Fonction qui renvoie des valeurs r, g, b après les avoir modifiées""" rouge = b # On place la valeur b dans rouge vert = g # On laisse la valeur g dans vert bleu = r # On place la valeur r dans bleu return (rouge, vert, bleu)

...CORRECTION...

On décale les instructions vers la droite.

Cela consiste à placer 4 espaces.

10° Quel est le mot-clé permettant de demander à la fonction de répondre ?

64 65 66 67 68 69
def filtrage(r, g, b): """Fonction qui renvoie des valeurs r, g, b après les avoir modifiées""" rouge = b # On place la valeur b dans rouge vert = g # On laisse la valeur g dans vert bleu = r # On place la valeur r dans bleu return (rouge, vert, bleu)

...CORRECTION...

Ligne 69 : return.

La fonction répond en fournissant le résultat derrière le return.

2 - Intensité des couleurs

Et que contiennent les valeurs d'intensité d'un pixel ?

Une valeur entière comprise entre 0 et 255.

11° Pourquoi l'octet 0000 0000 vaut-il 0 ? Quel calcul doit-on faire ?

Nombre M =00000000
Les bits codent 1286432168421
On obtient donc

...CORRECTION...

Nombre M =00000000
Les bits codent 1286432168421
On obtient donc00000000

On fait l'addition mais comme il n'y a que des 0, on obtient... 0.

12° Pourquoi l'octet 1111 1111 vaut-il 255 ? Quel calcul doit-on faire ?

Nombre M =11111111
Les bits codent 1286432168421
On obtient donc

...CORRECTION...

Nombre M =11111111
Les bits codent 1286432168421
On obtient donc1286432168421

On fait l'addition 128+64+32+16+8+4+2+1. Ca donne 255.

Nous venons donc de voir que pour une intensité lumineuse codée sur 1 octet, les valeurs possibles vont de 0 à 255.

On rappellera d'ailleurs qu'en hexadécimal, 255 se note FF.

C'est pour cela que le code RGB HTML du rouge est  #FF000000 .

13° Lancer quelques commandes dans la console (le Shell dans la version anglaise) pour voir les valeurs des intensités R, G et B de quelques pixels au hasard sur l'image de base (variable base ou l'image modifiée (variable sauvegarde). Exemple ici sur l'image de base sur le pixel en x=50 et y = 100 par exemple.

>>> pixel(base, 50, 100) 'Pixel de coordonnées (50, 100) : R = 160 - G = 174 - B = 3' >>> pixel(sauvegarde, 50, 100) 'Pixel de coordonnées (50, 100) : R = 3 - G = 174 - B = 160'

Vérifier que les intensités sont toutes entre 0 et 255.

3 - Image en noir et blanc

Tentons de réaliser un effet Manga Noir et Blanc à partir de notre image.

Si on veut uniquement du noir (RBG = (0,0,0)) ou du blanc (RGB = (255,255,255)) en fonction de la luminosité globale, il faut utiliser une nouvelle notion : l'instruction conditionnelle.

Principe :

  1. on calcule l'intensité "grise" du pixel (version oeil humain)
  2. on décide d'un seuil :
    • Si l'intensité est supérieure au seuil, on place un pixel blanc.
    • Si l'intensité est inférieure ou égale au seuil, on place du noir.
1 2 3 4 5 6 7 8
def filtrage(r, g, b): """Fonction qui renvoie des valeurs r,g,b après les avoir modifiées""" gris = (21*r + 71*g + 8*b) // 100 seuil = 100 if gris > seuil: return (255, 255, 255) else: return (0,0,0)

14° Tester cette fonction avec différentes avec différents seuils : il suffit de modifier la valeur de la variable seuil.

Exemple avec un seuil de 80 :

Exemple avec un seuil de 120 :

15° Comment indique-t-on en Python qu'une instruction fait partie du SI (if en Python) ou du SINON (else en Python) ?

1 2 3 4 5 6 7 8
def filtrage(r, g, b): """Fonction qui renvoie des valeurs r,g,b après les avoir modifiées""" gris = (21*r + 71*g + 8*b) // 100 seuil = 100 if gris > seuil: return (255, 255, 255) else: return (0,0,0)

...CORRECTION...

Comme pour les fonctions, il faut décaler les instructions sur la droite, à l'aide de 4 espaces.

Du coup, le if est décalé de 4 espaces pour indiquer qu'il appartient à la fonction et l'instruction suivante est décalée de 4 espaces également pour montrer qu'elle appartient ... au if qui est dans la fonction.

4 - Détection de contour

Pour détecter un contour, nous avons besoin d'une image noir et blanc. C'est l'objet de la question précédente.

Pour détecter un contour, l'une des techniques consiste à étudier les 8 pixels (noir ou blanc) qui entourent le pixel central C.

C

On obtient ainsi une zone de 9 pixels.

Principe de la détection :

  • SI, dans ce carré de 9 pixels, entre 3 à 6 pixels ont la même couleur :
    • on considère qu'on est proche d'un contour : on dessine les 9 pixels en noir.
  • SINON
    • on dessine les 9 pixels en blanc : on est loin d'un contour.
    • Si on a 7 pixels blancs, c'est qu'il s'agit juste d'une zone très clair
    • Si on a 7 pixels noir, c'est qu'il s'agit juste d'une zone très sombre.

16° Ces 9 pixels caractérisent-ils une zone proche d'une bordure ? Font-ils les modifier pour les rendre tous blanc ou noir ?

C

...CORRECTION...

On a bien 3 blancs (ou 6 noirs) : la zone n'est ni assez blanche, ni assez noire. Il s'agit donc d'un contour.

Il faudra pendre les 9 pixels en noir.

17° Ces 9 pixels caractérisent-ils une zone proche d'une bordure ? Font-ils les modifier pour les rendre tous blanc ou noir ?

C

...CORRECTION...

2 pixels blancs (et donc 7 pixels noirs).

On va donc considérer qu'il ne s'agit pas d'un coutour : on mettra du blanc partout.

18° Tester ce programme avec une image sur laquelle des formes sont assez clairement détectables. N'oubliez pas de modifier le nom de base. Conclusion ?

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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
from PIL import Image as Img # Nom du fichier-image original base = "avion.png" # Nom du fichier-image modifié sauvegarde = "contour.png" def creer_contour(nom): ref_image = Img.open(nom) if len(ref_image.getbands()) == 3: return filtrer_image_3x3RGB(nom) elif len(ref_image.getbands()) == 4: return filtrer_image_3x3RGBA(nom) def filtrer_image_3x3RGB(nom): """Fonction qui renvoie un objet-image après l'avoir filtré pixel par pixel""" # Création de l'objet-image propre à Python ref_image = Img.open(nom) # Lecture et action sur les pixels, un par un largeur, hauteur = ref_image.size for x in range(1, largeur-1, 3): for y in range(1, hauteur-1, 3): # On crée les 9 coordonnées des pixels à surveiller px1 = (x-1,y-1) px2 = (x-1,y) px3 = (x-1,y+1) px4 = (x,y-1) px5 = (x,y) px6 = (x, y+1) px7 = (x+1,y-1) px8 = (x+1,y) px9 = (x+1,y+1) # On récupère les 9 valeurs RGB du pixel de coordonnées (x,y) r1, g1, b1 = ref_image.getpixel( px1 ) r2, g2, b2 = ref_image.getpixel( px2 ) r3, g3, b3 = ref_image.getpixel( px3 ) r4, g4, b4 = ref_image.getpixel( px4 ) r5, g5, b5 = ref_image.getpixel( px5 ) r6, g6, b6 = ref_image.getpixel( px6 ) r7, g7, b7 = ref_image.getpixel( px7 ) r8, g8, b8 = ref_image.getpixel( px8 ) r9, g9, b9 = ref_image.getpixel( px9 ) # On transforme les pixels en noir ou blanc avec la fonction filtrage r1, g1, b1 = filtrage(r1, g1, b1) r2, g2, b2 = filtrage(r2, g2, b2) r3, g3, b3 = filtrage(r3, g3, b3) r4, g4, b4 = filtrage(r4, g4, b4) r5, g5, b5 = filtrage(r5, g5, b5) r6, g6, b6 = filtrage(r6, g6, b6) r7, g7, b7 = filtrage(r7, g7, b7) r8, g8, b8 = filtrage(r8, g8, b8) r9, g9, b9 = filtrage(r9, g9, b9) # On calcule s'il s'agit d'un bord de séparation ou pas nbr_noir = compter(r1,r2,r3,r4,r5,r6,r7,r8,r9) if nbr_noir > 6: rgb = (255, 255, 255) elif nbr_noir < 3: rgb = (255, 255, 255) else: rgb = (0, 0, 0) # On modifie l'image en mettant les 9 pixels en noir OU en blanc ref_image.putpixel( (x-1,y-1) , rgb ) ref_image.putpixel( (x-1,y) , rgb ) ref_image.putpixel( (x-1,y+1) , rgb ) ref_image.putpixel( (x,y-1) , rgb ) ref_image.putpixel( (x,y) , rgb ) ref_image.putpixel( (x,y+1) , rgb ) ref_image.putpixel( (x+1,y-1) , rgb ) ref_image.putpixel( (x+1,y) , rgb ) ref_image.putpixel( (x+1,y+1) , rgb ) return ref_image def filtrer_image_3x3RGBA(nom): """Fonction qui renvoie un objet-image après l'avoir filtré pixel par pixel""" # Création de l'objet-image propre à Python ref_image = Img.open(nom) # Lecture et action sur les pixels, un par un largeur, hauteur = ref_image.size for x in range(1, largeur-1, 3): for y in range(1, hauteur-1, 3): # On crée les 9 coordonnées des pixels à surveiller px1 = (x-1,y-1) px2 = (x-1,y) px3 = (x-1,y+1) px4 = (x,y-1) px5 = (x,y) px6 = (x, y+1) px7 = (x+1,y-1) px8 = (x+1,y) px9 = (x+1,y+1) # On récupère les 9 valeurs RGB du pixel de coordonnées (x,y) r1, g1, b1, a1 = ref_image.getpixel( px1 ) r2, g2, b2, a2 = ref_image.getpixel( px2 ) r3, g3, b3, a3 = ref_image.getpixel( px3 ) r4, g4, b4, a4 = ref_image.getpixel( px4 ) r5, g5, b5, a5 = ref_image.getpixel( px5 ) r6, g6, b6, a6 = ref_image.getpixel( px6 ) r7, g7, b7, a7 = ref_image.getpixel( px7 ) r8, g8, b8, a8 = ref_image.getpixel( px8 ) r9, g9, b9, a9 = ref_image.getpixel( px9 ) # On transforme les pixels en noir ou blanc avec la fonction filtrage r1, g1, b1 = filtrage(r1, g1, b1) r2, g2, b2 = filtrage(r2, g2, b2) r3, g3, b3 = filtrage(r3, g3, b3) r4, g4, b4 = filtrage(r4, g4, b4) r5, g5, b5 = filtrage(r5, g5, b5) r6, g6, b6 = filtrage(r6, g6, b6) r7, g7, b7 = filtrage(r7, g7, b7) r8, g8, b8 = filtrage(r8, g8, b8) r9, g9, b9 = filtrage(r9, g9, b9) # On calcule si c'est un bord de séparation ou pas nbr_noir = compter(r1,r2,r3,r4,r5,r6,r7,r8,r9) if nbr_noir > 6: rgb = (255, 255, 255, a5) elif nbr_noir < 3: rgb = (255, 255, 255, a5) else: rgb = (0, 0, 0, a5) # On modifie l'image en mettant les 9 pixels en noir OU en blanc ref_image.putpixel( (x-1,y-1) , rgb ) ref_image.putpixel( (x-1,y) , rgb ) ref_image.putpixel( (x-1,y+1) , rgb ) ref_image.putpixel( (x,y-1) , rgb ) ref_image.putpixel( (x,y) , rgb ) ref_image.putpixel( (x,y+1) , rgb ) ref_image.putpixel( (x+1,y-1) , rgb ) ref_image.putpixel( (x+1,y) , rgb ) ref_image.putpixel( (x+1,y+1) , rgb ) return ref_image def compter(r1,r2,r3,r4,r5,r6,r7,r8,r9): return (r1+r2+r3+r4+r5+r6+r7+r8+r9) // 255 def filtrage(r, g, b): """Fonction qui renvoie des valeurs r,g,b (0,0,0) ou (255,255,255) en fonction du seuil""" gris = (21*r + 71*g + 8*b) // 100 seuil = 150 if gris > seuil: return (255, 255, 255) else: return (0,0,0) # Création d'une nouvelle image à partir de la source originale nouvelle = creer_contour(base) # Affichage de l'objet-image Python à l'écran nouvelle.show() # Sauvegarde de l'image dans un nouveau fichier-image nouvelle.save(sauvegarde)

Exemple d'utilisation :

chapeau contour du chapeau

Bien entendu, pour qu'un programme détecte que c'est un chapeau, il faut sortir d'autres programmes : les IA par exemple.

5 - FAQ

Pas de question pour le moment

Activité publiée le 06 01 2021
Dernière modification : 06 01 2021
Auteur : ows. h.