#!/usr/bin/env python # coding: utf-8 # [Commentaire 1]: <> (Cours sous licence CC SA BY NC, Frédéric Mandon 2015) # # Jouer avec des images # # ## Lire / écrire dans un fichier # # _Cette partie est une reprise du notebook "compléments" ; si vous l'avez déjà fait vous pouvez néanmoins le reprendre_. # Commencez par créer un simple fichier texte, avec le bloc-notes, ou Text Edit, contenant deux lignes séparées par un return. Enregistrez le dans le même dossier que ce TD, en format texte, sous le nom "a_modifier.txt". Créez également un fichier vide "copie.txt", dans le même dossier. # Le programme suivant vous montre la lecture et l'écriture dans ce fichier. # # In[ ]: # Ouverture du fichier a lire et ecrire (mode "r+" read write) nomFichier = "a_modifier.txt" fichierInit = open(nomFichier, "r+") # ouverture ou creation d'un nouveau fichier s'il n'existe pas lors du "open" fichierCopie = open ("copie.txt", "r+") # affichage du contenu du fichier par ligne print("affichage ligne par ligne") for line in fichierInit: print(line) # repositionnement au debut du fichier et lecture d'une ligne fichierInit.seek(0) # la méthode "seek" repositionne au debut du fichier print("relecture de la première ligne") print(fichierInit.readline()) # Recopie caractere par caractere dans le fichier "copie.txt" # On pourrait aussi bien faire une copie par ligne, ou globale (plus rapide) fichierInit.seek(0) for lettre in fichierInit: fichierCopie.write(lettre) fichierInit.seek(0,2) # positionnement a la fin du fichier # Oieme bit avant la fin (2) fichierInit.write(str(42)) # on ne peut ecrire que des chaines de caracteres print("lecture du fichier intial") fichierInit.seek(0) print(fichierInit.read()) print("lecture du fichier copie") fichierCopie.seek(0) print(fichierCopie.read()) fichierInit.close() # Fermeture du fichier source (essentiel) fichierCopie.close() # Fermeture du fichier copie (essentiel) # #### Remarque : chemin relatif, chemin absolu # Dans l'instruction `nomFichier = "a_modifier.txt"`, le chemin d'accès est relatif. C'est à dire que le programme considère que le chemin d'accès part du répertoire courant. Si le fichier est dans un sous-repertoire "documents", on tape `nomFichier = "documents\a_modifier.txt"`. Un chemin absolu part de la __racine__ du système : `nomFichier = "C:\utilisateur\documents\a_modifier.txt"`. Lors des tests, utilisez des chemins relatifs. # En effet, Il peut se produire divers problèmes avec les chemins absolus. Le caractère "`\ `", dit backslash ou antislash, est un caractère spécial dans de nombreux langages, dont Python. par exemple, l'instruction `print("\"")` permet d'imprimer juste un guillemet. Si un programme comportant un chemin d'accès absolu renvoie une erreur sur le chemin, deux possibilités s'offrent à vous : taper `\\` au lieu de `\ `, ou bien taper `/` au lieu de `\ ` (ce qui correspond aux chemins sous Linux). Pour l'anecdote, il arrive qu'aucune de ces deux possibilités ne fonctionne, et il est arrivé aussi que sur deux machines du lycée a priori clonées, donc avec le même Python, les comportements se sont avérés différents. # ## Lire et modifier un fichier image # # ### Avec un éditeur d'images # # Lancer Gimp. Prendre une image de votre choix, de préférence de petite taille. L'exporter en format pbm, ppm, et pgm (respectivement portable bitmap file format en noir et blanc, portabke pixmap file format, et petit gros mou file format, en niveaux de gris). Le faire dans les deux possibilités : format brut et format ASCII (ce qui vous fait donc 6 images au total). # Sauvegarder vos images, dans un dossier où vous garderez ces originaux. En effet, on va les modifier de nombreuses fois : je vous conseille de ne travailler que sur des copies. # # ### Avec un éditeur hexadécimal # # Copier les deux images en brut et en ASCII en pgm dans un dossier de travail. # # Lancer ensuite l'éditeur hexadécimal Frhed. # Ouvrir l'image en format brut dans Frhed. Les premiers caractères sont lisibles, ils sont du type : # P5 # _commentaires_ nombre1 nombre2 255 (cela peut être un autre nombre). # P6 est le nombre magique indiquant le format, nombre1 et nombre2 donnent respextivement la largeur et la hauteur de l'image (le nombre de colonnes puis le nombre de lignes). Enfin, 255 est la profondeur de codage du gris, qui admet 256 nuances (avec le 0). Ces lignes forment l'en-tête du fichier, elles contiennent les métadonnées de l'image. Les métadonnnées peuvent être bien plus conséquentes ; on peut par exemple trouver des commentaires, commençant par #, indiquant le propriétaire de l'image, des données GPS, etc... La suite du fichier est codée en bytes (ici des octets) et n'est que partiellement affichable. # # # __Faire une copie de l'image en format ASCII__, et l'ouvrir dans Frhed. # Ne pas modifier l'en-tête (vous riqueriez d'avoir des difficultés de lecture par la suite). # Vous remarquez que les données numériques sont séparées par un point. Ce "point" est un séparateur de données, il est fondamental de bien le repérer lors du traitement de l'image. En effet, il ne doit surtout pas être modifié, puisqu'il prévient le programme que l'on passe d'une donnée à la suivante. Promenez-vous sur les premiers caractères du fichiers. Comparez la partie de droite et celle de gauche : les points sont-ils toujours codés par le même nombre ? # Le script ci-dessous permet de tester les différents codages ASCII et le caractère correspondant. # Modifiez-le pour trouver le code correspondant au séparateur de données, gardez-le en mémoire. Que fait ce caractère bien particulier ? # In[ ]: asc = int("2f",16) # si le code ASCII comporte des lettres entre a et f # c'est de l'hexadecimal, ici vous avez la méthode pour # trouver le caractère ASCII de code 2f ou 2F print("code ASCII ",asc," caractere ",chr(asc)) print('\n') # Que fait ce caractère ? asc = 97 print("code ASCII ",asc," caractere ",chr(asc)) # Dans la partie où l'on peut lire l'en-tête, modifiez les premières données, __toujours dans la copie__. Mettez soit 0 soit 255 à la place des 20 premières données (après le 255 donc). Enregistrez et fermez le fichier. Rouvrez l'image sous Gimp. Agrandissez en haut à gauche, que constatez-vous ? # # ### Avec Python # Les programmes que l'on va faire pour traiter une image sous Python doivent: # * ouvrir le fichier source # * ouvrir le fichier où l'on va sauver notre image modifiée. Si ce fichier n'existe pas, Python le crée lors de l'ouverture # * lire l'en-tête du fichier source et le garder en mémoire # * lire les données de l'image source # * traiter ces données # * écrire l'entête dans le fichier modifié # * écrire les données modifiées # * fermer les deux fichiers # # On a déjà vu dans la première partie de ce TD l'ouverture et la fermeture des fichiers, ainsi que la lecture et l'écriture. # # On va affiner un peu la lecture et l'écriture des données avant de passer au traitement global d'une image. # # # Le programme suivant affiche l'en-tête de l'image, puis les 20 premières données. Il travaille sur l'image originelle, en format pgm ASCII. # In[ ]: print ("Affichage des premières données d'une image en format pgm") # La variable suivante contient le chemin d'accès à l'image initiale. # Bien sur, ces noms sont à modifier si vous utilisez une autre image. # chemins absolus : #nomFichierSource = "C:Dudule\documents\imageb.pgm" #nomFichierDestination = "C:Dudule\documents\essaitransforme.pgm" nomFichierSource = "lyceejj.pgm" print ("Fichier source :",nomFichierSource) # ouverture du fichier source en format ASCII fichierSource = open(nomFichierSource,'r',encoding = 'ASCII') TailleFichier = len(fichierSource.read()) print ("\nTaille du fichier (en octets) :",TailleFichier) # lecture de l'entete du fichier (ici format pgm, 4 lignes d'entete) fichierSource.seek(0,0) entete = fichierSource.readline() + fichierSource.readline() + fichierSource.readline() + fichierSource.readline() print("en-tete") print(entete,"\n") # lecture des donnees du fichier source donnees = [i for i in fichierSource.read()] for i in range(20): print("donnee n°",i,"caractère",donnees[i]) # fermeture des fichiers fichierSource.close() print("c'est fait !") # Pensez-vous que les données soient réellement 1 puis 3 puis 8 ? Doit-on traiter aussi le saut de ligne ? # Réponses : # Vous pouvez comparer avec le fichier ouvert dans Frhed (ouvrir le fichier originel). # On va donc faire un petit programme qui permet de travailler sur les données réelles. Ceci nécessite de transformer toute une ligne en un nombre, puis ensuite de retransformer ce nombre en suite de caractères. # In[ ]: nomFichierSource = "lyceejj.pgm" print ("Fichier source :",nomFichierSource) # ouverture du fichier source en format ASCII fichierSource = open(nomFichierSource,'r',encoding = 'ASCII') entete = fichierSource.readline() + fichierSource.readline() + fichierSource.readline() + fichierSource.readline() print("en-tete") print(entete,"\n") for i in range(5): # un traitement idiot sur les 5 premieres lignes line = fichierSource.readline() nombre = int(line) print(line, end=' ') nombre = nombre//2 # une opération au hasard ! print("valeur",str(nombre)) fichierSource.close() # Il ne reste plus qu'à recoller les différents morceaux pour faire : # ### Exercice : inverser les niveaux de gris d'une image # Le programme ci-dessous est presque complet. Il réalise toutes opérations d'ouverture/fermeture, lecture/conversion des données/écriture. Il ne vous reste qu'à mettre l'unique ligne de code, dans la fonction traitement, qui transformera un pixel noir en un pixel blanc, un gris foncé en un gris clair, etc... # In[ ]: # coding: utf8 # from codecs import open print ("Inversion des niveaux de gris d'une image \n") def traitement(valeur): """ cette fonction ... @param : valeur entier @return :valeurbis entier """ valeurbis = 255 - valeur # a changer parce que là ça ne fait rien ! # si vous voulez vous pouvez reprendre l'exemple # ci-dessus pour en voir l'effet return valeurbis # les deux variables suivantes contiennent le chemin d'accès à l'image initiale d'une part, # et à l'image transformée d'autre part (le fichier sera crée automatiquement) # Bien sur, ces noms sont à modifier. # chemins absolus : #nomFichierSource = "C:Dudule\documents\imageb.pgm" #nomFichierDestination = "C:Dudule\documents\essaitransforme.pgm" nomFichierSource = "lyceejj.pgm" nomFichierDestination = "transformation.pgm" print ("Fichier source :",nomFichierSource) print ("Fichier destination :",nomFichierDestination) # ouverture du fichier source en mode lecture d'octets fichierSource = open(nomFichierSource,'r',encoding='ASCII') TailleFichier = len(fichierSource.read()) print ("\nTaille du fichier (en octets) :",TailleFichier) # lecture de l'entete du fichier (ici format pgm, 4 lignes d'entete) fichierSource.seek(0,0) entete = fichierSource.readline() + fichierSource.readline() + fichierSource.readline() + fichierSource.readline() print("en-tete") print(entete,"\n") # lecture des donnees du fichier source #donnees = [i for i in fichierSource.read()] #nb_donnees = len(donnees) # ouverture du fichier destination en mode ecriture d'octets fichierDestination = open(nomFichierDestination,'w',encoding = 'ASCII') # écriture de l'en-tête du fichier destination fichierDestination.write(entete) # traitement des lignes de données et ecriture dans le fichier destination for line in fichierSource: nombre = int(line) t = traitement(nombre) # on change la donnée fichierDestination.write(str(t)+'\n') # puis on l'écrit dans le fichier modifié # et on écrit aussi le séparateur de données # qui est '0a' en hexa soit 10 en décimal # fermeture des fichiers fichierSource.close() fichierDestination.close() print("c'est fait !") # #### Remarque # Le code ci-dessous effectue de manière plus basique, mais moins compacte, la conversion des données récupérées une par une entre deux séparateurs, en un nombre. # In[ ]: i = 0 while i < 20: chaine = "" # on cree une chaine vide while donnees[i] != '\n': # on ajoute les caractères (chiffres) chaine = chaine + donnees[i] # à la chaine de caractères i = i + 1 nombre = int(chaine) # on transforme la chaine en nombre print("nombre à traiter",nombre) i = i + 1 #on saute le séparateur de données #
#
# [![Licence CC BY NC SA](https://licensebuttons.net/l/by-nc-sa/3.0/88x31.png "licence Creative Commons CC BY SA")](http://creativecommons.org/licenses/by-nc-sa/3.0/fr/)
# [**Frederic Mandon**](mailto:frederic.mandon@ac-montpellier.fr), Lycée Jean Jaurès - Saint Clément de Rivière - France (2015-2017)