énoncé

Transcription

énoncé
Psi 945 – 2014/2015
http://blog.psi945.fr
TP Python
Images (1/2) : manipulations de base
Vendredi 23 et 30 janvier 2015
Buts du TP
– Comprendre le principe de la représentation Bitmap (liste/matrice de pixel).
– Prendre en main la bibliothèque Image avec des manipulations élémentaires (accès/modification de pixels).
– Découvrir Dobble, un jeu formidable !
Exercice 1. Créer (au bon endroit) un dossier associé à ce TP. Y placer et décompresser l’archive de travail
contenant les images utiles pour le TP 1 . Vous devez maintenant disposer de deux sous-dossiers : fichiers_entree
et fichiers_sortie
Lancer Spyder, sauvegarder immédiatement au bon endroit le fichier .py ; écrire une commande absurde, de type
print(5*3) dans l’éditeur, sauvegarder et exécuter. Définir deux variables de chemins qui vous permettront ensuite
de lire/écrire plus facilement dans des sous-répertoires :
entree, sortie = ’fichiers_entree/’, ’fichiers_sortie/’
# ...
Mona = im.open(entree+’joconde.bmp’)
Attention : sous windows, les slashs deviennent des backslashs, pénibles à gérer dans les chaînes de caractères. On
pourra tout mettre à la racine du TP de travail. C’est laid (et mal !) mais pragmatique.
Les différentes bibliothèques utilisées seront :
– Image pour traiter les fichiers d’image. Je suggère un import Image as im
– Plutôt que des listes de pixels, on préférera presque toujours des tableaux numpy, qui respectent mieux l’intuition
« matrice » et facilitent la manipulation des pixels : import numpy as np
– Ceux qui seront très rapide pourront éventuellement faire un petit graphique :
import matplotlib.pyplot as pypl
– Ceux qui sont encore plus rapide (ou qui se donnent bien plus que deux heures pour faire le TP !) pourront en
plus ajouter dans leurs imports : from random import randint voire from PIL.ImageDraw import Draw
Les images utilisées dans la troisième partie sont issues de la bibliothèque libre openclipart.org
1
Kit de survie
Quoi
THE librairie...
Ouverture (en lecture)
Récupérer la taille
Conversion en niveaux (Levels) de gris
Sauvons Mona
Si on préfère le jpg...
ou le pdf...
Comment
import Image
Mona = Image.open(’FilesIn/joconde.bmp’)
Mona.size
MonaBlack = Mona.convert(’L’)
MonaBlack.save(’FilesOut/jocondeBW.bmp’)
Mona.save(’FilesOut/joconde.jpg’)
Mona.save(’FilesOut/joconde.pdf’)
Les données sont accessibles via la méthode getdata(), qui fournit un objet pouvant être transformé en liste.
Pour le travail inverse, il s’agit de la méthode putdata(). Si on travaille avec numpy (et c’est bien pratique...), on
passera par des array.
Quoi
Extraire les données...
et en faire une liste (linéaire)
Mettre les données dans un tableau numpy
Créer une image à partir d’une liste
Créer une image à partir d’un tableau
Comment
donnees = MonaBlack.getdata()
ldonnees = list(donnees)
bar = numpy.array(MonaBlack)
MonaAgain = Image.new(MonaBlack.mode,MonaBlack.size)
MonaAgain.putdata(ldonnees)
MonaStill = Image.fromarray(bar)
1. Et les sous-dossiers, si tout se passe bien à la décompression !
1
2
2.1
Images bitmap : niveaux de gris et couleurs
De l’image vers la matrice (B&W)
On va commencer par créer effectivement le fichier jocondeBW.bmp qui n’est pas fourni.
Exercice 2. Taper dans le fichier de script, puis exécuter :
Mona = im.open(entree+’joconde.bmp’)
MonaBlack = Mona.convert(’L’)
MonaBlack.save(sortie+’jocondeBW.bmp’)
On aurait bien entendu pu faire ça en une seule commande !
im.open(entree+’joconde.bmp’).convert(’L’).save(sortie+’jocondeBW.bmp’)
On notera également qu’en changeant le suffixe lors de la sauvegarde, on peut obtenir un fichier au format jpg, pdf,
etc (essayez !). Et il ne s’agit pas simplement d’un changement de nom, mais bien de format : c’est la bibliothèque
Image qui fait le travail de façon transparente.
Exercice 3. Pixels de Mona Lisa
Déterminer la « taille » de l’image jocondeBW.bmp (les images possèdent un attribut size...). Récupérer la liste
des pixels, puis le tableau numpy correspondant. Quelles sont leurs tailles ?
Formulation volontairement ambiguë : pour une liste, il s’agit bien entendu de la longueur. Pour un tableau numpy,
il y a deux attributs : size et shape.
On peut maintenant réaliser l’histogramme de MonaBlack c’est-à-dire, pour chaque i ∈ [[0, 255]], le nombre de
pixels égaux à i. Il suffit pour cela de créer une liste constituée de 255 zéros, de parcourir la liste des pixels de
l’image, et pour chacun de ces pixels, incrémenter la case correspondante dans l’histogramme.
Exercice 4. Déterminer l’histogramme de l’image MonaBlack
On peut visualiser cet histogramme de deux façons différentes :
– En sauvegardant l’histogramme dans un fichier puis en ouvrant le fichier avec un tableur et en réalisant un
diagramme basique :
fichier_histo = open(’autres_fichiers/histo.csv’, ’w’)
for i in range(256):
fichier_histo.write(str(i)+","+str(histo[i])+’\n’)
fichier_histo.close()
– Directement via matplotlib :
pypl.plot(list(range(256)), histo)
pypl.axis([0,255,0,5000])
pypl.savefig(’autres_fichiers/histo.pdf’)
5000
4000
3000
2000
1000
00
50
100
150
200
250
Figure 1 – Histogramme : Libre Office vs. matplotlib
Exercice 5. Huhu !
Au fait, que fait la méthode histogram de l’objet MonaBlack ?
2.2
Et vice-versa (B&W)
Comme expliqué dans le kit de survie, on peut créer une image à partir d’une liste de pixels ou du tableau numpy
correspondant. Pour modifier une image, on peut donc récupérer les pixels, les modifier, puis créer une nouvelle
2
image à partir de la liste (du tableau) modifié(e). On préférera les tableaux numpy, pour accéder plus simplement
à des pixels (voire zones de pixels) par leurs coordonnées.
Petit « rappel » : on crée ici un tableau de 4 lignes et 5 colonnes, et par slicing, on change une zone complète, en
mettant à la place un tableau (qui doit avoir les bonnes dimensions !) initialisé grâce à la fonction numpy.ones qui
crée un tableau constitué de 1... mais qui ne demandent qu’à être multiplié si besoin (il existe aussi une fonction
numpy.zeros).
On reviendra plus tard sur la question 2 du type de tableau rendu par np.ones
>>> foo = np.array([[10*i+j for j in range(5)] for i in range(4)])
>>> foo
array([[ 0, 1, 2, 3, 4],
[10, 11, 12, 13, 14],
[20, 21, 22, 23, 24],
[30, 31, 32, 33, 34]])
>>> foo[1:3 , 0:3] = 42*np.ones((2,3))
>>> foo
array([[ 0, 1, 2, 3, 4],
[42, 42, 42, 13, 14],
[42, 42, 42, 23, 24],
[30, 31, 32, 33, 34]])
Exercice 6. Blanchir (c’est-à-dire mettre à 255) les pixels de la zone [[60, 180[[×[[120, 200k de MonaBlack ; sauvegarder 3 l’image au format pdf dans le dossier fichiers_sortie/
Pour l’exercice suivant, on se souvient ce qui se passe quand on essaie d’échanger deux variables via :
x = y
y = x
On pourra donc passer par un tableau intermédiaire qui sera une copie d’une zone du tableau initial :
intermediaire = ...[_:_ , _:_].copy()
Exercice 7. Copy/paste
Échanger les zones [[90, 190[[×[[120, 220[[ et [[400, 500[[×[[80, 180[[ de MonaBlack. Sauvegarder le résultat au format
jpg et au format bmp
2.3
Et en couleur
Alors que les niveaux de gris sont représentés par des entiers de 0 à 255, les pixels en couleur sont vus comme
une superposition de trois couleurs avec plus ou moins d’intensité : Red/Green/Blue. Par exemple, [255, 0, 0]
représente un pixel rouge ; [0, 255, 255] représente un pixel turquoise (vert mélangé avec du bleu).
Exercice 8. Combien de couleurs différentes peut-on ainsi représenter ? (exprimer le résultat comme une puissance
de deux).
Évaluer à la louche la taille (en octets) des fichiers joconde.bmp et jocondeBW.bmp. Vérifier ! Comparer à celle
de joconde.jpg
Exercice 9. Observer la valeur de array_pixels[:2 , :5] Que représente ce tableau ?
Exercice 10. Reprendre l’exercice 6, en colorant cette fois la zone en bleu. Pour créer le gros tableau de pixels
bleus, on peut par exemple utiliser :
np.array([[[0, 0, 255]] * 80] * 120)
Exercice 11. Reprendre l’exercice 7 en travaillant sur l’image en couleur.
Exercice 12. Expliquer :
>>> im.open(sortie+’jocondeBW.bmp’).histogram() == im.open(sortie+’jocondeBW.jpg’).histogram()
False
2. Fine et ennuyeuse
3. Et NON, je ne vous aiderai pas pour trouver le nom de l’image
3
Figure 2 – La Joconde, et des variantes
3
Vers un jeu de Dobble
3.1
Principe
Le jeu « Dobble » consiste en une collection de cartes (circulaires) contenant chacune huit symboles.
Figure 3 – Deux cartes de Dobble
Par construction, chaque couple de cartes possède exactement un symbole partagé.
Exercice 13. Quel est le symbole commun aux deux cartes présentées plus haut ?
Le principe du jeu consiste grosso modo 4 à détecter le plus rapidement possible quel est le symbole commun à
deux cartes présentes sur la table.
Un très joli article raconte les aspects mathématiques (construction du jeu de carte) :
http://images.math.cnrs.fr/Dobble-et-la-geometrie-finie.html
On y apprend/comprend en particulier que le jeu avec 8 symboles par cartes aurait pu posséder 82 − 8 + 1 = 57
cartes plutôt que les 55 du jeu du commerce !
On va réaliser un mini jeu de Dobble. Chaque carte possédera exactement quatre symboles. Chaque couple de
carte partage exactement un symbole. Il y a 13 (= 42 − 4 + 1) cartes... et autant de symboles. En annexe, on
fournit le résultat espéré.
3.2
Une carte basique
On va commencer par récupérer les différentes images :
images = [im.open(entree+’symboles/symbole%i.bmp’%k) for k in range(13)]
Pour insérer une image sur une page blanche, on peut commencer par créer une « matrice blanche » (constituée
de pixels tous égaux à 255) puis coller la matrice des pixels de l’image à coller.
4. Il y a plusieurs variantes
4
Exercice 14. Dans l’explorateur de variables, noter le type d’un tableau de pixels pour une image noir et blanc
(par exemple np.array(MonaBlack)). Comparer avec le type des tableaux créés par les commandes suivantes :
pixels1 = 255 * np.ones((1000,1000))
pixels2 = 255 * np.ones((1000,1000), dtype=’uint8’)
Ça y est : vous savez créer un tableau vierge du bon type...
Exercice 15. Créer une image bitmap de taille 1000 × 1000 avec un chat en bas à gauche.
Les véritables cartes ont 4 symboles.
Exercice 16. Créer une image 400 × 400 (ou 600 × 600...) représentant les quatre premiers symboles.
Figure 4 – Une carte représentant les quatre premiers symboles
Si vous avez du temps 5 , tentez ceci :
Exercice 17. Constituer une image regroupant tous les symboles :
Figure 5 – Les treize symboles
3.3
Récupération de données
On fournit dans le fichier cartes4.txt une façon de créer un jeu de 13 cartes de Dobble à 4 symboles. Ce fichier
est constitué de lignes de la forme
Carte 2 : Symboles 2 6 10 3
Carte 3 : Symboles 0 6 9 7
La première ligne signifie : pour la carte numéro 2, les symboles à utiliser sont les numéros 2, 6, 10 et 3.
5. Et connaissez les fonctions cosinus et sinus...
5
Exercice 18. Cassons une ligne
Après avoir défini ligne = "Carte 2 : Symboles 2 6 10 3", pronostiquer puis vérifier les résultats des instructions ligne.split(’ ’) puis ligne.split(’ ’)[4:] et enfin map(int, ligne.split(’ ’)[4:])
Exercice 19. Récupération globale
À l’aide de ce qui précède, récupérer la liste des (13) cartes à créer ; liste qui doit ressembler à :
[[0, 4, 8, 3], [1, 5, 9, 3], [2, 6, 10, 3], ... ]
3.4
Création du jeu complet
Exercice 20. Écrire une fonction prenant en entrée un numéro de carte (entre 0 et 12), et créant le fichier bitmap
(ou jpeg) représentant la carte avec les 4 symboles de cette carte (tels qu’indiqués dans la liste récupérée plus haut).
Pour ceux qui disposent de temps à perdre, un dernier exercice...
Exercice 21. Constituer le jeu de 13 cartes !
Encore du temps ?
Exercice 22. Vous pouvez faire tourner les symboles (méthode rotate possédée par les images), ajouter un cercle
autour (google : python, PIL, Draw, ellipse), mettre un ordre aléatoire pour les 4 symboles...
6