é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