Univ. Lille1 - Licence STS 1ère année 2013-2014 Algorithmes et Programmation Impérative 1 Traitements d'images Objectifs du TP : 1. Manipuler des tableaux à deux dimensions. 2. Découvrir et comprendre la représentation des images sous forme de tableau de pixels. 3. Découvrir et programmer certaines transformations d'images. 1 Représentation d'une image Sous leur forme numérisée, les images sont des tableaux à deux dimensions dont les éléments sont des pixels (pour pict ure el ement), un pixel étant l'unité atomique d'une image représentant une nuance de couleur. 0 1 2 j l 2 l 1 0 1 2 h i h 2 h 1 l Figure 1 Une image composée de h l pixels, et le pixel de coordonnees (i, j) La gure 1 donne une représentation de la décomposition d'une image en un tableau de h l pixels, h étant la hauteur de l'image et l sa largeur, exprimées en nombre de pixels. Le module Graphics livré avec toute distribution d'objective Caml dénit un type Graphics.color, dont les valeurs représentent des nuances de couleur que peuvent prendre les pixels d'une image. Ces couleurs peuvent être dénies grâce à la fonction Graphics.rgb : int int int Graphics.color, 1
qui renvoie une couleur dénie par ses trois composantes Rouge, Verte et Bleue désignées par un entier compris entre 0 et 255. Remarque : Toutes les lignes de code écrites dans cette section supposent que le module Graphics a été chargé dans l'interpréteur ou le compilateur (en ajoutant le nom du chier graphics.cma dans la ligne de commande). Par exemple, > ocaml graphics.cma Commençons par dénir les dimensions de l'image que nous souhaitons construire, et ouvrons une fenêtre graphique dans laquelle nous visualiserons les images. (* dimensions de l image a construire *) let hauteur = 256 and largeur = 384 ;; (* ouverture d une fenetre graphique avec les dimensions de l image *) Graphics.open_graph (" "^(string_of_int largeur)^"x"^(string_of_int hauteur)) ;; (* etablissement du texte du bandeau de cette fenetre *) Graphics.set_window_title "Mon image" ;; Construisons maintenant un tableau de pixels tous noirs. (* Un tableau de pixels noirs (R=0, V=0, B=0) *) let tableau_noir = Array.make_matrix hauteur largeur (Graphics.rgb 0 0 0) ;; Avant de visualiser l'image correspondant à ce tableau, il faut convertir le tableau de pixels en une donnée du type Graphics.image à l'aide de la fonction Graphics.make_image : Graphics.color array array Graphics.image. (* conversion du tableau de pixels en une image (type du module Graphics) *) let image_noire = Graphics.make_image tableau_noir ;; Tout est prêt maintenant pour visualiser notre image noire. C'est la procédure Graphics.draw_image : Graphics.image int int unit, l'instruction Graphics.draw_image img x y dessine l'image img en plaçant son coin inférieur gauche au point de coordonnées x,y. (* dessin de l image dans la fenetre graphique depuis le coin inferieur gauche *) Graphics.draw_image image_noire 0 0 ;; Cette instruction produit l'image montrée sur la gure 2. Si on préfère une image rouge : (* Un tableau de pixels rouges (R=255, V=0, B=0) *) let tableau_rouge = Array.make_matrix hauteur largeur (Graphics.rgb 255 0 0) ;; 2
Figure 2 Une image noire (* conversion du tableau de pixels en une image (type du module Graphics) *) let image_rouge = Graphics.make_image tableau_rouge ;; (* dessin de l image dans la fenetre graphique depuis le coin inferieur gauche *) Graphics.draw_image image_rouge 0 0 ;; ou bien un beau dégradé vertical de gris allant du noir au blanc, comme celui de la gure 3 : (* Un tableau de pixels avec une nuance de gris par ligne *) let tableau_degrade_vertical = let t = Array.make_matrix hauteur largeur (Graphics.rgb 0 0 0) in for i = 0 to hauteur - 1 do let gris = Graphics.rgb i i i in for j = 0 to largeur - 1 do t.(i).(j) <- gris done done ; t ;; let image_degrade_vertical = Graphics.make_image tableau_degrade_vertical ;; Graphics.draw_image image_degrade_vertical 0 0 ;; 2 Créations d'images 2.1 Dégradé horizontal Question 1 Réalisez une image donnant un dégradé de nuances de gris horizontal analogue au dégradé vertical de la gure 3. 3
Figure 3 Un dégradé de gris 2.2 Bandes horizontales et verticales Question 2 Réalisez une image de 256 256 pixels constituée de huit bandes horizontales d'égales largeurs alternativement noires et blanches. Question 3 Idem avec des bandes verticales. 2.3 Échiquers Question 4 Créez une image de 256 256 pixels formant un échiquier noir et blanc (ou jaune et vert, ou bleu et rouge,..., les deux couleurs pouvant être passées en paramètre d'une fonction de construction) de 8 cases carrées par ligne et par colonne. 3 Le module Images Dans la suite de ce TP, vous allez utiliser le module Images qui dénit trois fonctions qui permettent la lecture d'une image depuis un chier, la sauvegarde d'une image dans un chier, et l'achage d'une image dans une fenêtre graphique. Ce module suppose installée la suite logicielle Image Magick, ce qui est la cas dans les salles de TP. Vous pouvez vous procurer cette suite logicielle à l'adresse http://www.imagemagick.org/. La documentation de ce module est consultable en ligne ici. 3.1 Récupérer et préparer le matériel Question 5 Commencez par charger les deux chiers images.mli et images.ml qui sont les deux chiers donnant l'implantation du du module Images en Caml. Dans un terminal, compilez ces deux chiers avec les commandes > ocamlc -c images.mli > ocamlc -c images.ml 4
Ces deux commandes de compilation doivent avoir produit deux chiers images.cmi et images.cmo. Question 6 Lancez l'interpréteur Caml en ajoutant les modules à utiliser sur la ligne de commande, > ocaml graphics.cma images.cmo puis, tapez les directives # open Graphics ;; # open Images ;; Question 7 Récupérez le chier shadok.png et sauvegardez-la dans votre répertoire de travail. Ce chier est une image qui nous servira dans la suite. 3.2 Lire une image dans un chier La première des fonctionnalités dénies dans le module Images est la fonction lire_image dont le type est string Graphics.color array array. Cette fonction prend le nom d'un chier (string) en paramètre et renvoie un tableau de pixels (Graphics.color array array). Le chier dont le nom est passé en paramètre doit être un chier d'image dans l'un des formats suivants : png, jpg, gif, bmp, pgm, ppm. Le nom du chier doit donc avoir l'une de ces extensions. Question 8 Faîtes la déclaration de variable suivante let image = lire_image "shadok.png" ;; Question 9 Déterminez les dimensions de l'image obtenue. 3.3 Dessiner une image La deuxième fonctionnalité du module est la procédure dessiner_image dont le type est Graphics.color array array unit. Cette procédure dessine l'image passée en paramètre dans une fenêtre graphique qui doit avoir été préalablement ouverte (contrainte d'utilisation). (Elle opère en enchaînant les deux commandes Graphics.make_image et Graphics.draw_image.) Question 10 Ouvrez une fenêtre graphique adaptée aux dimensions de l'image et visualisez l'image lue précédemment dans le chier shadok.png. 3.4 Sauvegarder une image La dernière fonctionnalité est la procédure sauver_image dont le type est Graphics.color array array string unit. Elle sauvegarde l'image passée en premier paramètre (Graphics.color array array) dans un chier dont le nom est passé en second paramètre (string). Le format du chier image 5
produit dépend de l'extension choisie dans le nom du chier, extension qui doit obligatoirement correspondre à l'un des formats cités plus haut (contrainte d'utilisation). Question 11 Sauvegardez l'image représentée par la variable image dans un chier au format jpg, puis depuis un terminal visualisez l'image ainsi produite avec la commande display 1. display shadok.jpg 4 Transformations des couleurs Dans cette partie on s'intéresse à la transformation des couleurs d'une image donnée. 4.1 Extraction des composantes d'une couleur Si le module Graphics ore la fonction rgb pour construire une couleur à partir de ses trois composantes rouge, verte et bleue, il n'ore pas de fonctions réciproques extrayant ces trois composantes. Dans les transformations qui vont suivre, il pourra être utile d'avoir accès à ces composantes. Une couleur (type Graphics.color) est un entier c calculé à partir des trois composantes rouge (r), verte (v) et bleue (b) par la formule c = 256 2 r + 256v + b. Les entiers représentant les couleurs sont donc compris entre 0 et 256 3 1 = 16777215. Question 12 Réalisez la fonction composantes_rgb de type int int int int, qui extrait d'une couleur (int) le triplet des composantes (int int int). Pour que les valeurs données par cette fonction soient signicatives, il est nécessaire que l'entier passé en paramètre soit compris entre 0 inclus et 256 3 1 = 16777215 (contrainte utilisation). 4.2 Versions monochromes d'une image Vous allez construire des versions monochromes d'images données. La gure 4 en donne un exemple. Pour obtenir une version monochrome d'une image il sut de transformer chaque couleur de pixel c = (r, v, b) en annulant deux des trois composantes, i.e. c = (r, 0, 0) pour une version rouge ; c = (0, v, 0) pour une version verte ; et c = (0, 0, b) pour une version bleue. Question 13 Réalisez trois fonctions filtre_rouge, filtre_vert et filtre_bleu de type int array array Graphics.color array array qui construisent les versions monochromes de l'image donnée en paramètre. Appliquez votre fonction aux images de votre choix, et sauvegardez les images obtenues dans des chier à l'aide de la procédure de sauvegarde du module Images. 1. cette commande fait partie de la suite logicielle Image Magick. Sous Windows, elle se nomme imdisplay. 6
Figure 4 Une image et sa composante monochrome rouge 4.3 Négatif Vous allez maintenant construire des négatifs d'images données. La gure 5 en donne un exemple. Figure 5 Une image et l'image négative La couleur négative d'une couleur c = (r, v, b) est la couleur c = (255 r, 255 v, 255 b). Question 14 Réalisez une fonction negatif de type int array array Graphics.color array array qui construit l'image négative de celle passée en paramètre Appliquez votre fonction aux images de votre choix, et sauvegardez les images obtenues dans des chier à l'aide de la procédure de sauvegarde du module Images. 7
4.4 Nuances de gris Vous allez construire des versions en nuances de gris d'images en couleurs. La gure 6 en donne un exemple. Figure 6 Une image en couleurs et la même image en nuances de gris Une nuance de gris est une couleur obtenue en donnant la même valeur aux trois composantes RGB d'une couleur donnée. On obtient un gris avec l'appel à la fonction rgb rgb x x x où x est un entier compris entre 0 et 255. Pour convertir une couleur en une nuance de gris, on peut par exemple attribuer à l'entier x la moyenne arithmétique de ses trois composantes rouge, verte et bleue 2. Question 15 Réalisez une fonction nuances_de_gris de type int array array Graphics.color array array qui construit la version en nuances de gris de l'image passée en paramètre. Appliquez votre fonction aux images de votre choix, et sauvegardez les images obtenues dans des chier à l'aide de la procédure de sauvegarde du module Images. 4.5 Binarisation Vous allez construire des versions en deux noir et blanc seulement (pas de gris intermédiaire) d'images en couleurs. La gure 7 en donne un exemple. Le principe consiste à calculer pour chaque couleur de pixel la nuance de gris qui lui correspond et transformer ce gris en un noir ou un blanc selon que la nuance de gris est ou non supérieur à un seuil xé. Question 16 Réalisez une fonction binarise de type int array array int Graphics.color array array qui construit la version binaire de l'image passée en (premier) paramètre avec un seuil passé en (second) paramètre. 2. Il est aussi possible de pondérer diéremment les trois composantes de couleurs. 8
Figure 7 Une image en couleurs et la même image en noir et blanc avec un seuil de 127 Appliquez votre fonction aux images de votre choix, et sauvegardez les images obtenues dans des chier à l'aide de la procédure de sauvegarde du module Images. 5 Transformations bijectives Dans cette partie les transformations auxquelles nous nous intéressons sont des transformations qui changent la position des pixels d'une image donnée, et non la couleur. Ces transformations sont caractérisées par une fonction f qui aux coordonnées (i, j) d'un pixel associe les coordonnées (i, j ) de la nouvelle position de ce pixel dans l'image. Pour en savoir plus sur ces transformations, lire l'article de Jean-Paul Delahaye paru dans la revue Pour la Science de décembre 1997. 5.1 Symétrie d'axe vertical Question 17 Quelle est la fonction f de calcul des coordonnées d'arrivée (i, j ) en fonction des coordonnées de départ (i, j)? Question 18 Programmez la fonction symetrie_verticale de type Graphics.color array array Graphics.color array array qui calcule l'image obtenue en appliquant une symétrie d'axe vertical sur l'image passée en paramètre. 5.2 Demi-tour Question 19 Quelle est la fonction f de calcul des coordonnées d'arrivée (i, j ) en fonction des coordonnées de départ (i, j)? Question 20 Programmez la fonction demi_tour de type Graphics.color array array Graphics.color array array qui calcule l'image obtenue en appliquant un demi tour sur l'image passée en paramètre. 9
Figure 8 Une image en couleurs et l'image symétrique 5.3 Transformation du photo-maton La transformation du photo-maton ne peut s'appliquer qu'à des images dont les hauteur et largeur sont paires (contrainte d'utilisation). Elle agit sur des blocs de 2 2 pixels. Le pixel du coin supérieur gauche de ce bloc est envoyé dans le cadrant supérieur gauche, celui du coin supérieur droit dans le cadrant supérieur droit, celui du coin inférieur gauche dans le cadran inférieur gauche, et celui du coin inférieur droit dans le cadran inférieur droit. La gure 11 illustre cette idée. Question 21 Quelle est la fonction f de calcul des coordonnées d'arrivée (i, j ) en fonction des coordonnées de départ (i, j)? (Indication : elle ne dépend que des parités de i et j, ainsi que de la hauteur et de la largeur de l'image) Question 22 Programmez la fonction photo_maton de type Graphics.color array array Graphics.color array array qui calcule l'image obtenue en appliquant une transformation du photo-maton sur l'image passée en paramètre. 5.4 Transformations itérées On s'intéresse maintenant à des transformations itérées d'une image. Si T est une transformation bijective, et img une image, on note T (img) l'image obtenue en transformant l'image img par la transformation T. Comme T (img) est une image, on peut lui appliquer la même transformation T pour obtenir une nouvelle image T (T (img)) qui elle-même peut être transformée pour obtenir l'image T (T (T (img))), et ainsi de suite... Nous noterons T n (img) l'image obtenue par application n fois consécutivement de la transformation T sur l'image img. Question 23 Que vaut T n (img) lorsque T est la transformation de la symétrie d'axe vertical, ou bien du demi-tour? Question 24 En utilisant l'image joconde.png, et en prenant la transformation du photo-maton pour T, calculez les images T n (img) et dessinez-les pour n = 0, 1,..., 9. Que constatez-vous? 10
Figure 9 Une image en couleurs et l'image obtenue par demi-tour Obtient-on la même chose avec shadok.png? Question 25 Avec l'image shadok.png, et la transformation du photo-maton, calculez les images T n (img) pour n = 34, 35, 36, 69, 70, 71, 628, 627, 630. Que remarquez-vous? 11
Figure 10 Une image en couleurs et l'image obtenue par la transformation du photo-maton j j + 1 j/2 (j + l)/2 i/2 i i + 1 (i + h)/2 Figure 11 Principe de la transformation du photo-maton 12