Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • master
1 result

Target

Select target project
  • rvuillem/inf-tc1
  • hwei/inf-tc1
  • nmbengue/inf-tc1
  • fernanda/inf-tc1
  • mleger/inf-tc1
  • lmeng/inf-tc1
  • gferryla/inf-tc1
  • jconso/inf-tc1
  • smaghsou/inf-tc1
  • emarquet/inf-tc1
  • ecluzel/inf-tc1
  • aaudeoud/inf-tc1
  • tsegond/inf-tc1
  • aetienne/inf-tc1
  • djoly/inf-tc1
  • bcampeas/inf-tc1
  • dnovarez/inf-tc1
  • ruetm/inf-tc1
  • cchenu/inf-tc1
  • cguiotdu/inf-tc1
  • mclouard/inf-tc1
  • gwachowi/inf-tc1
  • qbaalaou/inf-tc1
  • sbrocas/inf-tc1
  • ppupion/inf-tc1
  • kinty/inf-tc1
  • hadomo/inf-tc1
  • tgicquel/inf-tc1
  • rhahn/inf-tc1
  • cguyau/inf-tc1
  • mpairaul/inf-tc1
  • rmuller/inf-tc1
  • rlecharp/inf-tc1
  • asebasty/inf-tc1
  • qmaler/inf-tc1
  • aoussaid/inf-tc1
  • kcherigu/inf-tc1
  • sgu/inf-tc1
  • malcalat/inf-tc1
  • afalourd/inf-tc1
  • phugues/inf-tc1
  • lsteunou/inf-tc1
  • llauschk/inf-tc1
  • langloia/inf-tc1
  • aboucard/inf-tc1
  • wmellali/inf-tc1
  • ifaraidi/inf-tc1
  • lir/inf-tc1
  • ynedjar/inf-tc1
  • schneidl/inf-tc1
  • zprandi/inf-tc1
  • acoradid/inf-tc1
  • amarcq/inf-tc1
  • dcombet/inf-tc1
  • gplaud/inf-tc1
  • mkernoaj/inf-tc1
  • gbichot/inf-tc1
  • tdutille/inf-tc1
58 results
Select Git revision
  • master
1 result
Show changes
Showing
with 1194 additions and 46 deletions
File deleted
%% Cell type:markdown id:39a8f123 tags:
NAME:
%% Cell type:markdown id:e1098061-ab50-4ba8-aa59-0b76ec1049a2 tags:
# INF TC1 - TD3 (2h) - Arbres binaires
%% Cell type:markdown id:fc6c7558-96c4-435b-a841-e38c5d14bc9f tags:
---
%% Cell type:markdown id:b0997389-5a87-4e8d-9600-29ed76e01759 tags:
<details style="border: 1px">
<summary> RAPPELS SUR L'UTILISATION DES NOTEBOOKS</summary>
### Comment utiliser ces notebooks ?
Le but de votre travail est de répondre aux questions des exercices en **remplissant certaines cellules de ce notebook avec votre solution**. Ces cellules, une foit remplies et lancées au fur et à mesure de vos avancées, permettront de valider des tests écrits dans d'autres cellules de ce notebook. **Il est donc important de bien suivre les instructions et répondre aux questions dans l'ordre**, et ne pas changer le nom des fonctions et/ou les cellules. En particulier :
1) Répondez aux questions dans les cellules en dessous des questions.
2) Votre code devra remplacer le texte suivant :
```python
# YOUR CODE HERE
raise NotImplementedError()
```
(vous pouvez effacer ces deux lignes quand vous les rencontrez mais ne modifiez pas les noms de fonctions sinon les tests ne marchent plus).
3) Exécuter enfin les cellules dans leur ordre d'apparition, de haut en bas et si votre code est correct alors les tests (sous forme d'`assert` seront validés (ils ne lanceront pas d'exception du type `AssertionError` ). Vous pouvez lancer plusieurs fois la même cellule, cela ne pose pas de soucis.
4) Vous pouvez créer de nouvelles cellules comme bon vous semble.
**En cas de problème, une solution est de relancer les cellules depuis le début du notebook une par une.** Pensez à bien sauvegarder ce notebook et ne pas le remplacer par un notebook qui a le même nom.
</details>
%% Cell type:markdown id:de645c99-52cf-4cff-9c6b-b156101ad47c tags:
## Objectif du TD
Ce TD vous fera manipuler les Arbres Binaires, qui sont une structure de données permettant de stocker des informations de manière hierarchique, et de les récupérer grace à des méthodes de parcours.
%% Cell type:markdown id:abde77ea-e21d-434e-b72e-a62ac464c793 tags:
## Exercice 1 : Introduction aux Arbres Binaires
Dans ce exercice vous allez créer et parcourir un Arbre Binaire. Un Arbre Binaire est un Arbre qui a les propriétés suivantes :
- il est dit _binaire_ car il comporte des noeuds ayant au _maximum_ deux enfants
- il est dit _complet_ si tous les niveaux de l'arbre sont remplis (sauf éventuellement le dernier), et dans lequel les feuilles sont alignées à gauche
- il est dit _équilibré_ si la différence de hauteur entre les sous-arbres gauche et droit de chaque nœud est au plus 1
Voici un exemple d'Arbre Binaire :
```
1
/ \
2 3
```
Dans cet exercice nous stockeront l'Arbre Binaire avec une structure de donnée _explicite_ (en POO comme ci-dessous, et non plus dans un tableau comme pour le tas dans le TD 2) qui contient des valeurs entières (et uniques, à savoir deux noeuds n'auront pas la même valeur `value`) dans chaque noeud :
%% Cell type:code id:9318f291-f289-4eb6-a7f7-94cbc4e3f459 tags:
``` python
class Node:
def __init__(self, value : int, left = None, right = None):
self.value = value
self.left = left
self.right = right
```
%% Cell type:markdown id:31993aa6-7361-4a58-856d-32f3a3fcec36 tags:
**Question 1.1** - Utilisez la structure de donnée `Node` ci-dessus afin d'implémenter l'arbre donné en introduction. Votre arbre sera stocké dans la variable `root`. Vous pouvez rajouter des noeuds supplémentaires à cet arbre (mais il doit rester binaire).
%% Cell type:code id:48a9b647-260e-40a5-a3da-c11ed77a9e62 tags:
``` python
root = Node(1) # a modifier
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:0a4461c4-09b8-4f9f-ad70-6b953dc4b7e6 tags:
Les tests suivant doivent être validés (rajoutez d'autres tests) :
%% Cell type:code id:c08d3050-ccba-4b51-8a01-1d2689455e96 tags:
``` python
assert root.value == 1
assert root.left.value == 2
assert root.right.value == 3
```
%% Cell type:markdown id:24670502-ecd5-4d86-a32c-1d7d43eb04fe tags:
**Question 1.2.** Proposez une méthode `bfs` de _parcours en largeur_ de l'Arbre afin d'afficher la valeur des noeuds dans l'ordre croissant. Pour cela vous utiliserez une structure de données de File (sous forme de liste, cela sera suffisant). Une méthode possible pour mener cela à bien consiste à :
1. intialiser la File avec la racine de l'Arbre;
2. dé-filer une valeur et la stocker dans une liste de résultats;
3. en-filer ses enfants (si il y en a) dans la File;
4. répéter l'étape 2 jusqu'à ce que la File soit vide, renvoyer le résultat.
%% Cell type:code id:1f92e83b-ba51-4fa4-a127-2aa970a96a26 tags:
``` python
def bfs(node: Node):
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:c1b198c7-8387-4552-a163-7a70a70c61a3 tags:
``` python
assert bfs(root) == [1, 2, 3]
```
%% Cell type:markdown id:675c8c46-ed69-4a2e-a9cf-57301112e3ab tags:
**Question 1.3** - Écrivez une fonction `depth` de calcul de la _profondeur_ d'un noeud d'un Arbre. La profondeur est défini comme la distance entre ce noeud et la racine (celle-ci aura une profondeur de `0`). Autrement dit la profondeur correspond au nombre d'arêtes entre un noeud et la racine de l'arbre. Cette fonction prendra en paramètre la racine de l'arbre `root`, ainsi que le noeud dont on veut calculer la profondeur avec le paramètre `target_value`.
Conseils :
- s'inspirer de la fonction précédente de parcours en largeur en incluant la profondeur de chaque noeud parcouru lors de son ajout dans la file;
- autrement dit rajouter un tuple `(noeud, profondeur)` au lieu du simple noeud parcouru afin de mémoriser la profondeur des noeuds parcourus.
%% Cell type:code id:6c21f3f7-0f21-47ad-a4ce-a2af7b9743d4 tags:
``` python
def depth(root: Node, target_value: int) -> int:
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:37a27a50-68f4-486b-a0da-b0905b8db8dd tags:
``` python
assert depth(root, 1) == 0
assert depth(root, 2) == 1
assert depth(root, 3) == 1
```
%% Cell type:markdown id:f8abb2f4-24dc-4346-9081-4400df30e3af tags:
**Question 1.4** - Écrivez une fonction `height` de calcul de la _hauteur_ d'un Arbre définie comme la prodondeur maximale possible dans un Arbe. Vous pouvez l'implémenter comme la profondeur maximale des noeuds de l'Arbre, ou bien de manière récursive.
%% Cell type:code id:4944b0bb-5f59-41c2-b5d9-052a0d5386dd tags:
``` python
def height(root: Node) -> int:
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:2d11935c-2212-4ec3-ba57-5303ec05b274 tags:
``` python
assert height(root) == 1
```
%% Cell type:markdown id:29fca7e0-aaf9-495b-822d-f20e1a672092 tags:
**Question 1.5** - Ecrivez une fonction de validation afin de déterminer si un Arbre est bien équilibré, autrement dit si il n'y a pas de différence de profondeur suppérieur à 1 entre les feuilles d'un arbre. Conseil : utilisez une approche récursive.
%% Cell type:code id:3b4f56e8-e4bf-4d27-97eb-b24995d741fe tags:
``` python
def est_equlibre(root: Node) -> bool:
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:69b03c9b-2092-4343-b9e4-7d9904a445b0 tags:
``` python
assert est_equlibre(root)
```
%% Cell type:markdown id:9e29fddc-c249-4b2a-a9d3-26e56f5474ab tags:
## Exercice 2 : Arbres syntaxiques
Un Arbre _syntaxique_ permet le stockage d'une expression structurée, par exemple une équation. Dans cet exercice nous allons modéliser un tel arbre sous forme d'arbre binaire (exemple ci-dessous à gauche) afin de réaliser un calcul arithmétique simple à partir de l'expression fournie de manière textuelle :
Expression : `(3-2) * (7+(10/2))`
Résultat : `12.0`
Nous ferons l'hypothèse que les opérations sont limitées à `+ - / *`, seront toujours binaires et porteront sur des valeurs numériques entières (mais le résultat peut ne pas être un entier).
```
*
/ \
- +
/ \ / \
3 2 7 /
/ \
10 2
```
Vous utiliserez la structure d'arbre binaire ci-dessous afin de stocker l'arbre comme pour l'abre binaire précédent :
%% Cell type:code id:e78ec913-cd5f-4304-8c8a-438758d909f4 tags:
``` python
class Noeud:
def __init__(self, v = None, g = None, d = None) -> None:
self.valeur = v
self.gauche = g
self.droit = d
```
%% Cell type:markdown id:88fbc62f-ae5a-4765-bf8f-bb127f5d5a13 tags:
**Question 2.1** - Stockez l'Arbre syntaxique donné en exemple graphique en utilisant la structure de données d'arbre binaire ci-dessus (il s'agit d'écrire une instance d'arbre dans la variable `arbre`, et non une fonction).
%% Cell type:code id:4661ca46-489c-4330-9e72-64fe25e4b49c tags:
``` python
arbre = None # à changer
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:cee8c8d3-f4ab-4dcc-931e-f0b8681b225c tags:
``` python
assert arbre.valeur == "*"
assert arbre.gauche.valeur == "-"
assert arbre.droit.valeur == "+"
```
%% Cell type:markdown id:0be8aab9-1aba-4340-9235-eb3d1cc7fd29 tags:
**Question 2.2** - Implémentez désormais une méthode d'évaluation (autrement dit de calcul) automatique d'un arbre syntaxique tel que vous l'avez stocké dans la variable `arbre` ci-dessus.
Conseil : proposez une solution récursive avec un cas d'arrêt et des appels récursifs comme suit :
- si la valeur du noeud en cours est un opérateur, l'appliquer sur les deux sous-branches;
- si il s'agit d'une valeur numérique, renvoyez cette valeur (cas d'arrêt car c'est une feuille de l'arbre).
%% Cell type:code id:dc899d17-67fd-4cc9-bf92-f5f32e08f89e tags:
``` python
def eval(r: Noeud) -> float:
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:00eb3e33-64b1-486f-97c8-d87de80f3a1a tags:
``` python
eval(arbre)
```
%% Cell type:markdown id:de5c394d-df35-4d7b-b057-164620b0b38d tags:
**Question 2.3** - Écrivez une méthode permettant de construire l'Arbre à partir d'une expression fournie sous forme de chaîne de caractère en entrée comme `( ( 3 - 2 ) * ( 7 + ( 10 / 2 ) ) )`. Les espaces sont nécessaires et vous permettront de faire un `.split(" ")`.
Conseil :
- parcourez caractère par caractère l'expression textuelle transformée en liste;
- créez au fur et à mesure l'arbre initialisé par une racine et une variable stockage le noeud courant en cours de construction;
- identifiez les 4 cas possibles : début d'une expression, valeurs numériques, opérateur, fin d'une expression :
- si une expression commence, créez un nouveau fils gauche au noeud courant et stockez le noeud courant dans une Pile
- si une expression finie, considérez comme noeud courant l'élément que vous dé-Pilerez
- si un opérateur, stockez sa valeur dans le noeud courant, empilez ce noeud mais avant lui rajoute un fils droit qui devient le noeud courant
- si une valeur numérique, la stocker dans le noeud courant et considérez comme noeud courant l'élément que vous dé-Pilerez
%% Cell type:code id:1c33faaa-2289-498f-9ef1-5afeb4da4628 tags:
``` python
def construit_arbre(exp: str) -> int:
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:b56d0304-5d23-4e1c-91fb-09b511465ad5 tags:
``` python
r = construit_arbre("( ( 3 - 2 ) * ( 7 + ( 10 / 2 ) )")
```
%% Cell type:markdown id:89a9015c-dd4e-409d-91b2-9b64b55288e9 tags:
Enfin vérifiez que vous obtenez la bonne valeur en évaluant l'expression.
%% Cell type:code id:e5c49c1f-75e0-4872-8578-40a532c7cea1 tags:
``` python
assert eval(r) == 12.0
```
%% Cell type:markdown id:012a5378-a2b7-498f-8115-e35f55c529ce tags:
**Question 2.4 (Bonus) -** Ecrire une fonction qui renvoie `True` ou `False` si l'Arbre est bien un arbre syntaxique tel que défini en introduction. Autrement dit qu'il est binaire, et comporte des opérateurs partout sauf aux feuilles.
Proposez d'abord une méthode itérative :
%% Cell type:code id:823d29e7-66c3-41a0-bdad-da6862a5e420 tags:
``` python
def valide_ite(r: Noeud) -> bool:
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:06e73b57-959a-436f-bd7a-e12c615c805c tags:
``` python
assert valide_ite(arbre)
```
%% Cell type:markdown id:dfdf1d42-60bb-4a7e-9cd4-e6cb72b8130a tags:
Proposez ensuite une méthode récursive :
%% Cell type:code id:df0033e9-c076-4cde-ab6f-982a3677ce05 tags:
``` python
def valide_rec(r: Noeud) -> bool:
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:9998c852-ec74-4225-9f01-4368899e303f tags:
``` python
assert valide_rec(arbre)
```
%% Cell type:markdown id:3503e6a5-2bc1-46d2-bef0-d1f1ceb6827e tags:
Montrez que les deux méthodes ont un comportement identique :
%% Cell type:code id:81e1b468-5d22-4a25-a2c3-17ab165627e5 tags:
``` python
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:81842c6c-1784-432b-8322-f3793d633e03 tags:
**Question 2.5 (Bonus) -** Créez une table de hachage afin de mémoriser les opérations déja réalisées pour l'Arbre syntaxique. Conseil : utilisez les informations sur le noeud comme clé (valeurs, opérateur).
%% Cell type:code id:fb91bda6-7076-44be-9014-ceb5507eb01c tags:
``` python
def eval_hash(r: Noeud, memo=None) -> float:
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:22979a11-3455-4525-b816-a84b2164291c tags:
``` python
assert eval_hash(arbre) == eval(arbre)
```
%% Cell type:markdown id:85c6a3e6-dea0-4e86-b7d9-b0c5735c3630 tags:
## Pour aller plus loin
- Rajouter des tests dans les exemples ci-dessus avec des arbres plus complexes, des cas particuliers, etc.
- Inclure des [Exceptions](https://docs.python.org/3/tutorial/errors.html) dans votre code afin de gérer par exemple l'accès à un index de liste inexistant, etc.
- Modulariser votre code afin de créer des fonctions auxiliaires (par exemple pour vérifier la valeur des noeuds de l'Arbre syntaxique).
%% Cell type:markdown id:08c48261 tags:
## Visualiser avec Graphviz
Graphviz est une bibliothèque de visualisation de graphe. Elle peut être très pratique afin de comprendre la structure de votre instance de graphe.
Cependant, la bibliothèque pose quelques soucis d'installation en Python, voici des étapes éventuellement à suivre.
Tout d'abord vérifiez que le module est bien installé :
%% Cell type:code id:c8c95b89 tags:
``` python
!pip install graphviz
```
%% Cell type:markdown id:5a9ebd95 tags:
Si oui (vous avez le message du type `Requirement already satisfied:`) testez l'import du module :
%% Cell type:code id:e897987f tags:
``` python
import graphviz
from graphviz import Digraph
```
%% Cell type:markdown id:2bf9b677 tags:
Si vous avez des erreurs, essayer l'installation du module à partir des exécutables
%% Cell type:code id:bd3cfb66 tags:
``` python
https://graphviz.org/
```
%% Cell type:markdown id:9373f3bc tags:
Un autre source d'erreur est l'installation à partir de la mauvaise version de Python qu'il faut utiliser avec pip.
%% Cell type:code id:504eff4a tags:
``` python
!python --version
```
%% Cell type:code id:5eb4ab77 tags:
``` python
from IPython.display import display
def visualize_oop(root):
def build(node, dot=None):
if dot is None:
dot = graphviz.Digraph(format='png')
if node is not None:
dot.node(str(node.value))
if node.left is not None:
dot.edge(str(node.value), str(node.left.value))
build(node.left, dot)
if node.right is not None:
dot.edge(str(node.value), str(node.right.value))
build(node.right, dot)
return dot
return build(root)
```
%% Cell type:code id:ded5b1fe tags:
``` python
visualize_oop(root)
```
TD03/Image10.bmp

192 KiB

TD03/Image8.bmp

2.84 MiB

from graphviz import Digraph
name = 'arbre-viz-profondeur'
g = Digraph('G', filename = name + '.gv', format='png') # par defaut format='pdf'
g.edge('chien', 'petit')
g.edge('chien', 'et')
g.edge('petit', 'le')
g.edge('et', 'jaune')
g.edge('et', 'noir')
# génere et affiche le graphe name.gv.png
g.view()
\ No newline at end of file
TD03/ecl.jpg

103 KiB

File deleted
File deleted
%% Cell type:markdown id:f6c54511 tags:
NAME:
%% Cell type:markdown id:b97bad7e-82ff-44a7-9779-13c139085623 tags:
# INF TC1 - TD4 (2h) - Images
%% Cell type:markdown id:1bb26026-8560-4a3c-90e6-2cfd7a49320a tags:
---
%% Cell type:markdown id:99ee8fad-7f32-4fe2-85d3-3b8da49f317f tags:
<details style="border: 1px">
<summary> RAPPELS SUR L'UTILISATION DES NOTEBOOKS</summary>
### Comment utiliser ces notebooks ?
Le but de votre travail est de répondre aux questions des exercices en **remplissant certaines cellules de ce notebook avec votre solution**. Ces cellules, une foit remplies et lancées au fur et à mesure de vos avancées, permettront de valider des tests écrits dans d'autres cellules de ce notebook. **Il est donc important de bien suivre les instructions et répondre aux questions dans l'ordre**, et ne pas changer le nom des fonctions et/ou les cellules. En particulier :
1) Répondez aux questions dans les cellules en dessous des questions.
2) Votre code devra remplacer le texte suivant :
```python
# YOUR CODE HERE
raise NotImplementedError()
```
(vous pouvez effacer ces deux lignes quand vous les rencontrez mais ne modifiez pas les noms de fonctions sinon les tests ne marchent plus).
3) Exécuter enfin les cellules dans leur ordre d'apparition, de haut en bas et si votre code est correct alors les tests (sous forme d'`assert` seront validés (ils ne lanceront pas d'exception du type `AssertionError` ). Vous pouvez lancer plusieurs fois la même cellule, cela ne pose pas de soucis.
4) Vous pouvez créer de nouvelles cellules comme bon vous semble.
**En cas de problème, une solution est de relancer les cellules depuis le début du notebook une par une.** Pensez à bien sauvegarder ce notebook et ne pas le remplacer par un notebook qui a le même nom.
</details>
%% Cell type:markdown id:d48155b2-8db8-4557-a66b-363351712560 tags:
## Objectif du TD
Ce TD vous fera manipuler des images en Python, et réaliser un algorithme de remplissage basé sur leur contenu (par exemple leur couleur). Nous verrons en particulier la structure de données matricielle et les méthodes de parcours associées. Enfin ce TD sera une préparation au TD 5 qui fera l'objet d'un rendu à réaliser à partir des concepts abordés dans ce TD.
IMPORTANT : Dans le cadre de ce TD 4 (mais aussi le prchain TD 5), nous n'autorisons pas l'utilisation de [OpenCV](https://docs.opencv.org/4.x/), [NumPy](https://numpy.org/) ou tout autre module permettant la manipulation et le traitement d'imag. Sauf le module [Pillow](https://pillow.readthedocs.io/en/stable/) mais dont seules quelques fonctions seront autorisées (cela sera indiqué dans les questions).
%% Cell type:markdown id:647cb3f9-cd85-4bf3-8a18-18262f58041c tags:
## Exercice 1 : Charger une image et dessiner
Une image en informatique est stockée sous forme de matrice de pixels qui contiennent les couleurs pour chaque cellule. Le modèle classique de couleurs est dit "RGB" (Red, Green, Blue) [(doc)](https://fr.wikipedia.org/wiki/Rouge-vert-bleu) où chaque pixel contient une information colorimétrique encodée sous forme de triplets `(r, g, b)` (red, green, blue). Les valeurs de couleur peuvent varier de 0 à 255 pour chacune de ces composantes. Par exemple le rouge <span style="display:inline-block;width:15px;height:15px;background-color:rgb(255,0,0);"></span> est encodé en `(255, 0, 0)`, le gris <span style="display:inline-block;width:15px;height:15px;background-color:rgb(128,128,128);"></span> en `(128, 128, 128)`, etc. Ces couleurs sont organisées en une matrice de dimension égale à celle de l'image. Les couleurs sont indépendantes les unes des autres.
Dans ce TD nous allons utiliser un module Python appelé PIL (Pillow [doc](https://pillow.readthedocs.io/en/stable/)) pour créer et manipuler des images. Il est possible de l'initialiser avec le code ci-dessous. Une fois le module importé, vous pourrez charger une image dans une variable `px` dite d'accès de pixel `PixelAccess` [(doc)](https://pillow.readthedocs.io/en/stable/reference/PixelAccess.html). Le module Pillow est normalement installé, si ce n'est pas le cas, vous devez exécuter la commande suivante dans une fenêtre de terminal Anaconda (Menu Démarrer / Anaconda 64bit / Anaconda PowerShell Prompt) : `pip install Pillow`. Si vous l'installer dans un notebook même utilisez la commande `!pip install Pillow`.
Pour tester si le module est présent sur votre ordinateur, charger une image comme suit :
%% Cell type:code id:5a97d177-3289-451f-bb64-3d97ad023cd4 tags:
``` python
from PIL import Image
from IPython.display import display
im = Image.open("lyon.png")
im = im.convert("RGB") # important pour bien avoir 3 couleurs
px = im.load()
W, H = im.size # taille de l'image
r, g, b = px[10, 20] # on récupère un pixel
px[10, 21] = r, g, b # on change un pixel
im = im.resize((W//2, H//2))
display(im) # on affiche l'image dans la cellule
# im.show() # on affiche l'image si vous n'utilisez pas de notebook
```
%% Cell type:markdown id:c2a6f43f-7d5d-46ff-85cb-37d0fb717c85 tags:
Vous pouvez également créer une **nouvelle image** `im2` vide (noire) de taille identique à `im` :
%% Cell type:code id:11e98fc1-9f60-476b-909b-d632d802f3f4 tags:
``` python
im2 = Image.new('RGB', (im.width, im.height))
px2 = im2.load()
display(im2)
```
%% Cell type:markdown id:482f0b25-342e-403c-975b-65a42283e1cc tags:
**Question 1.1 -** Ecrivez une fonction de lecture d'une image donnée, qui renvoie la couleur d'un pixel à la position $(x, y)$. Inspirez vous du code précédent ou de la documentation Pillow [(doc)](https://pillow.readthedocs.io/en/stable/).
%% Cell type:code id:567ca46f-9a29-44da-93a0-9f3992f39b03 tags:
``` python
def getPixel(x: int, y: int, px) -> tuple:
"""Get the color of a pixel
Args:
x (int): pixel x coordinate
y (int): pixel y coordinate
px (PixelAccess): image pixel access
Returns:
tuple: color of the pixel
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:08846407-027f-4b9e-86c7-43cce4234cd6 tags:
``` python
assert getPixel(0, 0, px) == (69, 119, 170) # bleu
assert getPixel(0, 0, px2) == (0, 0, 0) # noir
```
%% Cell type:markdown id:49116049-3a09-4415-84c7-e71c6ea85c55 tags:
Afin de vérifier visuellement votre résultat, nous vous fournissons la fonction `draw_rectangle` qui permet de dessiner la couleur d'un pixel dans une cellule :
%% Cell type:code id:b2098f50-57c2-4c4b-9a07-b695103590fa tags:
``` python
from IPython.display import display, HTML
def draw_rectangle(rgb_color):
color = f'rgb({rgb_color[0]}, {rgb_color[1]}, {rgb_color[2]})'
html = f'<svg width="100" height="100"><rect width="100" height="100" fill="{color}" /></svg>'
display(HTML(html))
# utilisation : draw_rectangle((69, 119, 170))
```
%% Cell type:code id:c02ced7f-22bb-41c0-83d8-e91485dc4c5c tags:
``` python
draw_rectangle(getPixel(0, 0, px)) # bleu
```
%% Cell type:code id:aa9391e7-8623-4c7e-9865-99ce35dc8683 tags:
``` python
draw_rectangle(getPixel(0, 0, px2)) # noir
```
%% Cell type:markdown id:69c1f988-3e28-45a7-a6c2-e0a7075c19f6 tags:
**Question 1.2 -** Ecrivez une fonction de coloriage d'un pixel à une position $(x, y)$ dans une image avec une couleur donnée en argument sous forme de `tuple` $(r, g, b)$.
%% Cell type:code id:df54ba88-6681-4eaa-9608-20c8d8db67ae tags:
``` python
def setPixel(x: int, y:int, color: tuple, px) -> None:
"""Set the color of a pixel
Args:
x (int): pixel x coordinate
y (int): pixel y coordinate
color (tuple): color to set
px (PixelAccess): image pixel access
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:ab0b00f6-5a33-4da0-a97e-5ce74e827122 tags:
``` python
r, g, b = (0, 0, 0)
setPixel(0, 0, (r, g, b), px2) # on écrit un pixel dans le coin en haut à gauche
assert getPixel(0, 0, px2) == (r, g, b) # on vérifie que la couleur a bien changé
```
%% Cell type:markdown id:a46817a7-f6be-4f1b-b79f-0347669844cc tags:
**Question 1.3 -** Écrivez une fonction permettant de remplir une région de l'image avec une même couleur qui sera la moyenne des couleurs. Dans un premier temps définissez cette fonction de calcule de `moyenne` d'une région donnée.
%% Cell type:code id:1583490b-8d8a-4dab-94df-05281ebc668c tags:
``` python
def moyenne(corner_x: int, corner_y: int, region_w: int, region_h: int, px: int) -> tuple:
"""Compute the average color of a region
Args:
corner_x (int): top left corner x coordinate
corner_y (int): top left corner y coordinate
region_w (int): region width
region_h (int): region height
px (PixelAccess): image pixel access
Returns:
tuple: average color of the region
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:bcb59088-8939-429c-9c82-a989d78922d4 tags:
``` python
assert moyenne(0, 0, 1, 1, px2) == (0, 0, 0) # le coin en haut à gauche est noir
```
%% Cell type:markdown id:913c742d-04ba-4220-adce-861bf39d999d tags:
Ensuite écrivez la fonction `setRegion` qui permet de remplir une région donnée avec une couleur (par exemple la couleur moyenne de cette région.
%% Cell type:code id:a7ba398a-bf70-4c70-83d2-28c55bdf2762 tags:
``` python
def setRegion(x: int, y: int, w: int, h: int, color: tuple, px) -> None:
"""Set the color of a region
Args:
x (int): top left corner x coordinate
y (int): top left corner y coordinate
w (int): region width
h (int): region height
color (tuple): color to set
px (PixelAccess): image pixel access
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:6e7ce82a-7fd9-4c4d-b76d-17f96da87cd5 tags:
Le code ci-dessous doit dessiner un rectangle blanc au milieu d'une image noire.
%% Cell type:code id:6b9fbf24-ac6a-4b08-a633-fd9a6bc4708f tags:
``` python
im2 = Image.new('RGB', (im.width, im.height))
px2 = im2.load()
W, H = im.size
setRegion(W//3, H//3, W//3, H//3, (255, 255, 255), px2)
display(im2)
```
%% Cell type:markdown id:38f3a987-f93a-4119-a1fa-d87eaf55f9d1 tags:
Nous allons maintenant réaliser un remplissage un peu plus avancé de l'image à partir de son contenu (dans notre cas nous nous baserons sur les couleurs contenues dans l'image). Avant de commencer, nous avons besoin d'une fonction de calcul de distance entre deux couleurs.
%% Cell type:markdown id:daa5c6f4-eabc-4f00-8d32-86d5695af06b tags:
**Question 1.4 -** Ecrivez une fonction de calcul de [distance Euclidienne](https://fr.wikipedia.org/wiki/Distance_(math%C3%A9matiques)) entre deux couleurs RGB (Red, Green, Blue) $C_1$ et $C_2$ comme suit :
$d_{\text{euclidienne}} = \sqrt{(R_2 - R_1)^2 + (G_2 - G_1)^2 + (B_2 - B_1)^2}$
Attention il s'agit de distances _entre_ les couleurs à comparer et non pas la distance entre les positions des pixels.
%% Cell type:code id:9be2d008-74fd-4f97-bdaf-d88525142f95 tags:
``` python
from math import sqrt
def distance(c1: tuple, c2: tuple) -> float:
"""Compute the distance between two colors
Args:
c1 (tuple): first color
c2 (tuple): second color
Returns:
float: distance between the two colors
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:f673184f-81f9-4172-ac4f-58e6cccceffc tags:
``` python
assert distance((0, 0, 0), (0, 0, 0)) == 0.0
```
%% Cell type:markdown id:3e09490e-9955-4639-b149-53c16855e8df tags:
**Question 1.5 -** Nous allons désormais travailler sur une méthode de remplissage de région basée sur son contenu, en utilisant un critère d'homogénéité des couleurs dans la région. Pour cela nous allons ré-utiliser les méthodes ci-dessus en particulier la distance Euclidenne, mais également les piles vues précédemment. Nous utiliserons l'algorithme dit de _flood fill_ [(doc)](https://en.wikipedia.org/wiki/Flood_fill) et qui fonctionne comme suit :
1. Chargez une image et initialiser deux listes vides : une liste de pixels à visiter et une liste de pixels déjà visités
2. Définissez un pixel de départ $p_{(x, y)}$ et le rajouter dans la liste de pixels à visiter
3. Récupérez un pixel $p_{(i, j)}$ de la liste des pixels à visiter, il constituera la couleur $c_{(i, j)}$ de la région homogène et le rajouter dans une troisième liste de pixels homogènes à colorier avec cette couleur
4. Tant que la liste de pixels homogènes n'est pas vide, récupérez un pixel de cette liste :
- Coloriez le pixel avec la couleur $c_{(i, j)}$ et le rajouter dans la liste des pixels visités
- Explorez les 4 voisins autour du pixel (haut, bas, gauche, droite) et pour chaque voisin :
- Si la couleur du voisin est en dessous d'un seuil d'homogénéité alors l'inclure dans la liste de pixels homogènes
- Sinon rajoutez le pixel dans la liste de pixels à visiter
- Répétez cela tant que la liste de pixels homogènes n'est pas vide
5. Répétez cela tant que la liste des pixels non visités n'est pas vide
Le résultat attendu est une image remplie coloriée avec un nombre de couleur inférieur au nombre initial de couleurs.
%% Cell type:code id:7dfc1c84-e0ca-49d1-b089-8a23175f715f tags:
``` python
def floodFill(w: int, h: int, start_x: int, start_y: int, seuil: int, px, px2) -> tuple:
"""Flood fill algorithm
Args:
w (int): image width
h (int): image height
start_x (int): starting x coordinate
start_y (int): starting y coordinate
seuil (int): color distance threshold
px (PixelAccess): image pixel access
px2 (PixelAccess): image pixel access (image we will re-color as a result)
Returns:
tuple: visited pixels, not visited pixels, unique colors used
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:1a50b940-d3b5-4dd5-9b9f-cd4977c9bc24 tags:
Le code ci-dessous va tester votre solution avec une image fournie et générer quelques statistiques liés au nombre de couleurs utilisées.
%% Cell type:code id:102f2245-a8ce-41d6-9250-e163c96bb952 tags:
``` python
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:1647215c tags:
Vous pouvez tester avec l'image noire (de dimension 100x100) avec un rectangle blanc au centre et vous devrez obtenir :
```
nombre de pixels dans l'image: 10000
nombre de noeuds coloriés 10000
nombre de noeuds non visités 0
nombre de couleurs utilisées 2
````
%% Cell type:code id:40510314 tags:
``` python
im = Image.new('RGB', (100, 100))
px = im.load()
W, H = im.size
setRegion(W//3, H//3, W//3, H//3, (255, 255, 255), px)
im2 = Image.new('RGB', (im.width, im.height))
px2 = im2.load()
W, H = im.size
print("nombre de pixels dans l'image:", W * H)
seuil = 10
start_x, start_y = 0, 0
visited, not_visited, colors = floodFill(W, H, start_x, start_y, seuil, px, px2)
print("nombre de noeuds coloriés", len(visited)) # 10000
print("nombre de noeuds non visités", len(not_visited)) # 10000 (tous visités)
print(f"nombre de couleurs utilisées {len(colors)}") # 0 (aucun oubli)
display(im2) # 2 couleurs utilisées (noir et blanc)
```
%% Cell type:markdown id:e2eb0bd7-ef80-41fd-8aba-2460de5b6ad4 tags:
## Pour aller plus loin
- Testez le remplissage dans 8 directions (en prenant en compte les diagonales) au moyen dans la fonction d'exploration de voisinage des pixels.
%% Cell type:markdown id:0e3f9555-c2fe-4c68-b4cd-b659b928755e tags:
## Exercice 2 : Traitement d'image par filtre
Nous allons aborder un deuxième aspect de la manipulation d'image : le traitement d'image, afin d'en extraire des informations intéressantes (contours, formes, etc.). En particulier nous allons créer différents _filtres_ dont le but sera de transformer les valeurs des pixels afin de par exemple réduire le bruit que les images peuvent contenir (à savoir les variations locales de valeur).
La plupart de ces méthodes étant couteuses en temps, nous travaillerons sur une version en niveau de gris.
%% Cell type:markdown id:057fa44d-2c9f-423c-af12-16731b401884 tags:
**Question 2.1 -** Écrivez une fonction de conversion d'image en niveaux de gris (soit la moyenne des triplets `(r,g,b)` ou en utilisant la formule suivante :
$ C_{gray} = (0.3 \times R) + (0.59 \times G) + (0.11 \times B)$
%% Cell type:code id:cee93e0f-a63b-4a19-87e7-00c736191465 tags:
``` python
def conversion_gris(px, W: int, H: int) -> None:
"""Convert an image to grayscale.
Args:
px (PixelAccess): image pixel access
W (int): image width
H (int): image height
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:4828de37-3b06-48f6-b32a-6d6e53285787 tags:
``` python
im = Image.open("lyon.png")
im = im.convert("RGB")
px = im.load()
W, H = im.size
conversion_gris(px, W, H)
display(im)
```
%% Cell type:markdown id:1f8656cb-ec1f-4ce3-a837-2b938e36f1e5 tags:
Nous commençon avec le filtre dit de _Flou Gaussien_, basé sur une opération dite de _convolution_, permettant d'appliquer une fonction de distribution gaussienne aux voisins d'un pixel et d'en faire la moyenne. Autrement dit il s'agira de réaliser la moyenne pondérée de chaque pixel en réalisant la moyenne du pixel et de ses voisins en utilisant par exemple la matrice ci-dessous (dont les valeurs sont définies par la distribution gaussienne donnée en annexe pour une matrice $3 \times 3$) :
$G(x) = \frac{1}{\sqrt[]{2 \pi } \sigma} e^{- \frac{x^{2}}{2 \sigma ^{2}}}$
%% Cell type:code id:9faad901-6b61-4011-be8e-55d85d3b7ded tags:
``` python
gauss3 = [[1,2,1],
[2,4,2],
[1,2,1]]
gauss7 = [[1,1,2,2,2,1,1],
[1,2,2,4,2,2,1],
[2,2,4,8,4,2,2],
[2,4,8,16,8,4,2],
[2,2,4,8,4,2,2],
[1,2,2,4,2,2,1],
[1,1,2,2,2,1,1]]
```
%% Cell type:markdown id:d90ad339-06fc-4423-a302-034c1d872301 tags:
**Question 2.2 -** Implémentez le filtre gaussien tel que défini ci-dessus en définissant les filtres donnés ci-dessous. La fonction doit effectuer la multiplication des pixels centrés sur le pixel en cours avec les valeurs de la matrice, puis la somme pondérée. Vous pourrez utiliser les matrices ci-dessus d'approximation du filtre (en commençant par le filtre Gaussien $3 \times 3$ dont le total des valeurs est $16$). Utilisez la version de l'image en niveaux de gris afin de simplifier les traitements.
Commencez tout d'abord par définir une fonction qui calcul la somme des valeurs d'une matrice (de type `gauss3`).
%% Cell type:code id:06cd5d47-9fde-4b0f-a833-b7ce415e6349 tags:
``` python
def somme_matrice(m: list = []) -> float:
"""Compute the sum of all the elements in a matrix.
Args:
m (list): matrix
Returns:
float: sum of all the elements in the matrix
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:f02e6ba8-d870-40e0-8ed9-67b35ef12e57 tags:
``` python
assert somme_matrice(gauss3) == 16
```
%% Cell type:markdown id:c0030b8b-159b-463d-a830-d6d554e6382b tags:
Ecrire la fonction de convolution. Pensez à prendre en compte les bords de l'image. Conseil : ne vous approchez pas trop du bord afin de ne pas réaliser une convolution en dehors de l'image.
%% Cell type:code id:0c295c65-4e2c-4751-91a8-cb8108e18ba2 tags:
``` python
def convolution(px, W: int, H: int, m: list) -> None:
"""Apply a convolution matrix on an image.
Args:
px (PixelAccess): image pixel access
W (int): image width
H (int): image height
m (list): convolution matrix
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:726a449a-e975-4647-9f57-e31f3581124d tags:
``` python
im = Image.open("lyon.png")
im = im.convert("RGB")
px = im.load()
W, H = im.size
conversion_gris(px, W, H)
convolution(px, W, H, gauss3)
display(im)
```
%% Cell type:markdown id:746bd3e7-f8fc-4ed2-a89b-2b4e52d6ace1 tags:
Vous pouvez comparer votre floutage avec celui de PIL :
%% Cell type:code id:513a179c-89bc-43e6-87e2-ca033ffee304 tags:
``` python
from PIL import Image, ImageFile, ImageDraw, ImageChops, ImageFilter
_im = im.filter(ImageFilter.GaussianBlur)
display(_im)
```
%% Cell type:markdown id:7bb648b1-450b-4bd9-b52a-c798107d6079 tags:
**Question 2.3 -** Testez votre code avec ces filtres ci-dessous avec un facteur de normalisation/pondération de 1 : que se passe-t-il ? [(doc)](https://en.wikipedia.org/wiki/Kernel_(image_processing)).
%% Cell type:code id:f213b45e-768f-4d89-b456-f12050a3217c tags:
``` python
sobely3 = [[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]]
sobelx3 = [[-1, -2, -1],
[0, 0, 0],
[1, 2, 1]]
```
%% Cell type:code id:760d4ca7-5f31-457f-8b09-6e0dfc12c7a8 tags:
``` python
def convolution_sobel(px, W: int, H: int, m: list, f = 1) -> None:
"""Apply a sobel filter convolution on an image
Args:
px (PixelAccess): image pixel access
W (int): image width
H (int): image height
m (list): convolution matrix
f (int): factor
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:00c62110-6e7b-42d6-8450-d8ed7c7e233a tags:
## Tests pour la fonction de floodfill
%% Cell type:code id:d61c4763-f479-42fe-91bb-27311925f6c0 tags:
``` python
W, H = 100, 100
im = Image.new('RGB', (W, H))
px = im.load()
setRegion(W//3, H//3, W//3, H//3, (255, 255, 255), px)
display(im)
im2 = Image.new('RGB', (W, H))
px2 = im2.load()
display(im2)
d = distance((0, 0, 0), (255, 255, 255)) # pour trouver le seuil
print("on est en dessous du seuil on garde les 2 couleurs")
print("on part du coin en haut à gauche -> noir")
visited, not_visited, colors = floodFill(W, H, 0, 0, d - 1, px, px2)
print("nombre de noeuds coloriés", len(visited))
print("nombre de noeuds non visités", len(not_visited))
print(f"nombre de couleurs utilisées {colors}")
display(im2)
print("on part du milieu -> blanc")
visited, not_visited, colors = floodFill(W, H, W/2, H/2, d - 1, px, px2)
print("nombre de noeuds coloriés", len(visited))
print("nombre de noeuds non visités", len(not_visited))
print(f"nombre de couleurs utilisées {colors}")
display(im2)
print("on est au dessus du seuil on ne garde qu'une couleur")
print("on part du coin en haut à gauche -> noir")
visited, not_visited, colors = floodFill(W, H, 0, 0, d + 1, px, px2)
print("nombre de noeuds coloriés", len(visited))
print("nombre de noeuds non visités", len(not_visited))
print(f"nombre de couleurs utilisées {colors}")
display(im2)
print("on part du milieu -> blanc")
visited, not_visited, colors = floodFill(W, H, W/2, H/2, d + 1, px, px2)
print("nombre de noeuds coloriés", len(visited))
print("nombre de noeuds non visités", len(not_visited))
print(f"nombre de couleurs utilisées {colors}")
display(im2)
```
%% Cell type:markdown id:f9d03a14 tags:
## FAQ
1. Problème d'accès aux fichiers d'images (image non trouvée) : vérifiez que l'image fonctionne bien et se situe dans le répertoire de votre notebook que vous pouvez trouver avec la commande ci-dessous.
%% Cell type:code id:980dfda1-8ac9-49da-ac66-6b460d91b5f2 tags:
``` python
import os
print(os.getcwd())
```
labyrinthe = [[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
def affiche_labyrinthe(l):
print('\n'.join([''.join(['{:4}'.format(item) for item in row])
for row in l]))
\ No newline at end of file
from queue import PriorityQueue
# initialisation de la file
file_prio = PriorityQueue()
# remplissage
file_prio.put((2, "Bob"))
file_prio.put((1, "Alice"))
file_prio.put((6, "Nat"))
# permet d'accéder au premier élément de la file
# (sans le supprimer)
print(file_prio.queue[0])
# tant que non vide, affiche par ordre de priorité
# (mais supprimer chaque élément accédé)
while not file_prio.empty():
print(file_prio.get()[1])
File moved
File deleted
%% Cell type:markdown id:751c51f9 tags:
NAME:
%% Cell type:markdown id:b97bad7e-82ff-44a7-9779-13c139085623 tags:
# INF TC1 - TD5 (2h + 2h AUTONOMIE) - Devoir à rendre #1
%% Cell type:markdown id:1bb26026-8560-4a3c-90e6-2cfd7a49320a tags:
---
%% Cell type:markdown id:dd8d4905-55f9-4957-8008-a963cc6de061 tags:
Vous serez évalué sur le rendu de ce TD qui sera à déposer sur Moodle **deux (2) semaines** après la séance d'autonomie #1. Le rendu sera à réaliser sous forme de **notebook** qui contient votre code et images.
%% Cell type:markdown id:99ee8fad-7f32-4fe2-85d3-3b8da49f317f tags:
<details style="border: 1px">
<summary> MODALITES DE RENDU</summary>
### Comment rendre son devoir ?
Vous serez évalué sur le rendu de ce TD qui sera à déposer sur Moodle **deux (2) semaines** après les séances d'autonomie et de TD. Vous devrez créer une archive (zip, rar, etc.) nomée `nom1-nom2-inf-tc1-td5.zip` qui contiendra tous les éléments de votre rendu (rapport en notebook, code, images de test). Vous pouvez rendre ce rapport seul ou en binôme. Le rendu du TD doit contenir a minima :
1. Toutes les étapes jusqu'à la 6ème doivent avoir été abordées
2. Justifications, illustrations et tests sur plusieurs images
**A garder en tête :**
- Un code fonctionnel et les tests appropriés devront être fournis dans l'archive qui doit être autonome (le correcteur ne doit pas avoir à rajouter d'image ou de fichier supplémentaire)
- Vous fournirez les images de test et leurs résultats; évitez cependant de prendre des tailles d'images trop importantes.
- Le rapport **devra être au format Notebook Jupyter** et comprendre :
- Le détail des étapes que vous avez suivies
- La description de parties de code difficiles
- Tout souci ou point bloquant dans votre code
- Les graphiques et diagrammes nécessaires
- Des analyses et discussions en lien avec votre approche
- Des exemples simples mais aussi difficiles
**Tout travail supplémentaire (méthode originale, optimisation poussée) fera l'objet de points en bonus.**
*Voici une suggestion afin de se faire un ordre d'idée*
Un groupe avec une note entre 10 et 12 :
- Les étapes suivies
- Un code fonctionnel et les méthodes basiques
- Un rapport succint
- Un code certes fonctionnel mais peu commenté
- Les exemples d'images fournies
Un groupe entre 12 et 14 a en plus proposé :
- Des structures de données avancées (Ensembles, Files, etc)
- Une justification de chaque étape
- Une méthode un petit peu plus poussée
Un groupe entre 14 et 16 a en plus proposé :
- Une méthode originale (K-Means, etc)
- Une démarche expérimentale très détaillée sur les optimisations
- Des tests plutôt originaux
Un groupe au-dessus de 16 comporte une ou plusieurs parties exceptionnelles :
- Rapport très détaillé et exemplaire sur le fond comme sur la forme
- Une démarche expérimentale très détaillée sur les optimisations
- Code et tests
</details>
%% Cell type:markdown id:d48155b2-8db8-4557-a66b-363351712560 tags:
## Objectif du devoir
Le but de ce devoir est de **déterminer automatiquement une palette de couleurs optimale** pour une image donnée. Cette palette devra valider les contraintes suivantes :
1. utiliser moins de couleurs que le nombre disponible dans l'image donnée;
2. être la plus représentative possible des couleurs de l'image donnée.
Comme nous l'avons vu dans le TD 4, les couleurs peuvent être encodée par composantes rouge, verte et bleue (soit 256 valeurs possibles par composante, autrement dit sur 8 bits) ainsi potentiellement utiliser $256 \times 256 \times 256 = 16 777 216$ couleurs. En réalité, beaucoup moins sont nécessaires et surtout perceptibles par l'humain. Réduire le nombre de couleurs ou réaliser une "_quantification de couleurs_" est une tâche fréquente et c'est une fonctionnalité classique des outils éditeurs d'images (Photoshop, Gimp, etc.) implémentée aussi dans le module Pillow de Python. A noter que cette réduction s'effectue avec perte de couleurs et doit être réalisée avec les bons paramètres (nombre et choix des couleurs) ce qui est votre objectif.
La figure ci-dessous illustre le problème à résoudre : étant donnée une image en entrée, proposer une liste de couleurs (que l'on appellera la palette), afin de re-colorier une image en sortie.
<div style="text-align:center;">
<table>
<tr>
<td>
<img src="figures/color-rainbow.png" alt="Image originale" style="height:5cm;">
<p>Image donnée</p>
</td>
<td>
<img src="figures/rainbow-palette-8.png" alt="Palette de 8 couleurs représentatives" style="height:5cm;">
<p>Palette de 8 couleurs représentatives</p>
</td>
<td>
<img src="figures/rainbow-recoloriee.png" alt="Image originale recoloriée avec la palette" style="height:5cm;">
<p>Image donnée recoloriée avec la palette</p>
</td>
</tr>
</table>
</div>
%% Cell type:markdown id:fd464e65-adfe-4e11-bf87-f12c513fbaea tags:
## Étapes de travail
Voici des étapes de travail suggérées :
1. Prenez une image de votre choix (pas trop grande) en la chargeant avec PIL. Lister les couleurs présentes, identifier celles qui sont uniques et leur fréquence.
2. Proposez une méthode (naïve pour commencer) de choix d'une palette de $k$ couleurs. Affichez là sous forme d'image (exemple de d'image au milieu de la figure du dessus) avec une nouvelle image PIL. Utilisez également des images simples où le résultat attendu est connu comme pour les images ci-dessous :
<div style="text-align:center;">
<table>
<tr>
<td>
<img src="figures/1-color-back.png" alt="1 couleur noir" style="width:3cm;">
<p>1 couleur noir</p>
</td>
<td>
<img src="figures/4-color.png" alt="4 couleurs" style="width:3cm;">
<p>4 couleurs</p>
</td>
</tr>
</table>
</div>
3. Re-coloriez une image avec une palette de $k$ couleurs, et affichez le résultat sous forme d'image PIL. Pour re-colorier chaque pixel, prendre la couleur la plus proche dans la palette en utilisant une fonction de distance (Euclidienne par exemple..).
4. Proposez une méthode de validation de votre approche. Par exemple affichez la différence entre l'image originale et celle re-coloriée. Calculez un score global d'erreur.
5. Améliorez le choix des $k$ couleurs afin de minimiser l'erreur entre l'image originale et re-coloriée. Une piste possible est de trier les couleurs dans une liste, diviser cette liste en $k$ intervals de couleurs et prendre la couleur du milieu de chaque interval. D'autres méthodes plus avancées peuvent être explorées !
6. Testez votre palette sur plusieurs images de votre choix ou générées automatiquement avec un nombre et une distribution connue de couleurs. Comparer les performances de vos techniques avec d'autres méthodes (cette fois vous pouvez utiliser un éditeur de texte ou la fonction _quantize_ de PIL [(doc)](https://pillow.readthedocs.io/en/stable/reference/Image.html).
7. Utilisez un pré-traitement des images (flou gaussien, etc) afin de lisser les couleurs. Cela est une piste afin de choisir de meilleurs couleurs représentatives. Proposez une comparaison de cette amélioration (ou de déterioration éventuelle) avec les autres méthodes.
8. Proposez une méthode d'amélioration de calcul de la distance entre deux couleurs, vous pouvez vous baser sur d'autres espaces de couleur [(doc)](https://fr.wikipedia.org/wiki/Espace_de_couleur). Cette partie est difficile, les espaces de couleurs possibles sont complexes à comprendre.
9. Optimisez les étapes précédentes (complexité, espace nécessaire, structures de données, etc.) et justifiez vos choix.
### Bonus
10. Créez une palette représentative à partir de plusieurs images.
TD05/figures/1-color-back.png

270 B

TD05/figures/4-color.png

767 B

TD05/figures/analyse.png

38.8 KiB

TD05/figures/color-rainbow.png

12.3 KiB

TD05/figures/palette-16.png

519 B

TD05/figures/placeholder.png

7.67 KiB