INTRODUCTION |
Les humains interprètent une image comme une surface plane généralement rectangulaire comportant des formes colorées. En informatique et dans l’industrie de l’impression, une image est plutôt vue comme une grille de points colorés appelés pixels. Le nombre de pixels par unité de surface est appelé résolution de l’image et est souvent indiquée en ppp (points par pouces). En anglais, on parle de dpi (dots per inch). Pour pouvoir stocker et traiter une image dans un ordinateur, il faut définir les couleurs par des valeurs numériques. Il y a plusieurs possibilités de le faire qui sont appelés modèles de couleurs ou systèmes colorimétriques . Un des modèles les plus populaires est RGB qui représente l’intensité de chacune des trois couleurs fondamentales rouge, vert et bleu par une échelle comprise entre 0 (pas de couleur) et 255 (intensité maximale). [plus...Ce modèle correspond à la perception des couleurs par l’œil humain.La rétine est en effet formée de cellules photosensibles appelées cônes qui sont sensibles au rouge, au vert et au bleu]. Le modèle ARGB inclut encore un autre nombre compris entre 0 et 255 qui indique la transparence (valeur alpha) de la couleur [plus...La couleur d’un pixel est alors codée sur 32 bits : bits 0..7=bleu, bits 8..15=vert, bits 16..23=rouge, bits 24..31=valeur alpha]. En bref : une image est représentée dans un ordinateur comme un tableau bidimensionnel de nombres représentant chacun la couleur d’un pixel. Cette représentation d’une image est appelée un bitmap.
|
MÉLANGES DE COULEURS DANS LE MODÈLE |
Le programme ci-dessous utilise un objet GBitmap pour dessiner les fameux disques de la synthèse additive des trois couleurs fondamentales en parcourant le bitmap avec une boucle imbriquée. from gpanel import * xRed = 200 yRed = 200 xGreen = 300 yGreen = 200 xBlue = 250 yBlue = 300 makeGPanel(Size(501, 501)) window(0, 501, 501, 0) # y axis downwards bm = GBitmap(500, 500) for x in range(500): for y in range(500): red = green = blue = 0 if (x - xRed) * (x - xRed) + (y - yRed) * (y - yRed) < 16000: red = 255 if (x - xGreen) * (x - xGreen) + (y - yGreen) * (y - yGreen) < 16000: green = 255 if (x - xBlue) * (x - xBlue) + (y - yBlue) * (y - yBlue) < 16000: blue = 255 bm.setPixelColor(x, y, makeColor(red, green, blue)) image(bm, 0, 500) |
MEMENTO |
Les couleurs sont définies par leurs composantes de rouge, vert et bleu. La fonction makeColor(red, green, blue) permet de combiner ces composantes pour former un objet couleur. Les images utilisent typiquement un système de coordonnées où l’origine se trouve au coin supérieur gauche, l’axe y pointant vers le bas [plus... il est possible d’avoir des erreurs d’arrondis car GPanel utilise des coordonnées flottantes]. |
CREER UNE IMAGE EN NIVEAUX DE GRIS |
Vous vous êtes peut-être déjà demandé comment un logiciel de traitement d’images tel que Photoshop fonctionne. On va voir ensemble quelques notions de base du traitement d’images qui permettront de comprendre les principes élémentaires. Le programme suivant va transformer une image colorée en nuances de gris. On utilise pour ce faire la moyenne des composantes de rouge, vert et bleu pour déterminer la valeur de la nuance de gris. from gpanel import * size = 300 makeGPanel(Size(2 * size, size)) window(0, 2 * size, size, 0) # y axis downwards img = getImage("sprites/colorfrog.png") w = img.getWidth() h = img.getHeight() image(img, 0, size) for x in range(w): for y in range(h): color = img.getPixelColor(x, y) red = color.red green = color.green blue = color.blue intensity = (red + green + blue) / 3 gray = makeColor(intensity, intensity, intensity) img.setPixelColor(x, y, gray) image(img, size, size) |
MEMENTO |
On peut déterminer les composantes d’un objet couleur en utilisant les méthodes color.red, color.green, color.blue Le fond doit être blanc et non transparent. Pour permettre la transparence, on peut déterminer la valeur de transparence avec alpha = getAlpha() et utiliser ensuite cette valeur comme paramètre supplémentaire de makeColor(red, green, blue, alpha). |
RÉUTILISABILITÉ |
Dans la plupart des programmes de traitement d’images, l’utilisateur doit être en mesure de sélectionner une portion rectangulaire de l’image. Pour cela, on peut dessiner un rectangle temporaire « élastique » en glissant la souris. Lorsque le bouton de la souris est relâché, la zone est définitivement choisie. Un programmeur avisé commencera par résoudre ce problème récurrent avant de s’attaquer au développement du logiciel dans son ensemble puisque cette fonctionnalité sera réutilisée pour implémenter de nombreuses fonctions et applications différentes. La réutilisabilité est un des critères de qualité majeurs dans tous les domaines du développement logiciel. Comme nous l’avons déjà vu, le dessin de lignes « élastiques » peut être considéré comme une animation. Dans le cas qui nous intéresse, cela impliquerait cependant de redessiner l’image dans sa totalité à chaque changement du rectangle de sélection. Une astuce très performante pour éviter cela consiste à utiliser le mode de dessin XOR. Ce mode a la particularité de combiner la figure en cours de dessin avec les pixels déjà présents dans le canevas. Deux dessins successifs de la même figure en mode XOR la font disparaître sans pour autant affecter les pixels sous-jacents. Le petit bémol de ce procédé réside dans le changement de couleur incontrôlable pendant le dessin, ce qui ne se remarque toutefois presque pas pour une surface aussi faible que le bord d’un rectangle de sélection.
from gpanel import * size = 300 def onMousePressed(e): global x1, y1 global x2, y2 setColor("blue") setXORMode(Color.white) # set XOR paint mode x1 = x2 = e.getX() y1 = y2 = e.getY() def onMouseDragged(e): global x2, y2 rectangle(x1, y1, x2, y2) # erase old x2 = e.getX() y2 = e.getY() rectangle(x1, y1, x2, y2) # draw new def onMouseReleased(e): rectangle(x1, y1, x2, y2) # erase old setPaintMode() # establish normal paint mode ulx = min(x1, x2) lrx = max(x1, x2) uly = min(y1, y2) lry = max(y1, y2) doIt(ulx, uly, lrx, lry) def doIt(ulx, uly, lrx, lry): print("ulx = ", ulx, "uly = ", uly) print("lrx = ", lrx, "lry = ", lry) x1 = y1 = 0 x2 = y2 = 0 makeGPanel(Size(size, size), mousePressed = onMousePressed, mouseDragged = onMouseDragged, mouseReleased = onMouseReleased) window(0, size, size, 0) # y axis downwards img = getImage("sprites/colorfrog.png") image(img, 0, size) |
MEMENTO |
On peut récupérer le bitmap d’une image enregistrée sur l’ordinateur avec la fonction getImage() qui demande soit le chemin d’accès complet au fichier ou uniquement un chemin relatif par rapport au dossier contenant le programme Python. Pour charger les images se trouvant dans l’archive JAR de la distribution TigerJython, il faut utiliser le dossier spécial sprites. Dans le gestionnaire de l’événement de clic de la souris, on bascule le système de dessin en XOR mode pour permettre au gestionnaire de glissé de la souris onMouseDragged de supprimer le vieux rectangle en le redessinant une deuxième fois pour ensuite le redessiner avec les nouvelles coordonnées de la souris. Les coordonnées x1, y1, x2, y2 doivent être stockées dans des variables globales pour permettre la communication entre les gestionnaires d’événements. D’autre part, si l’on redessinait le rectangle de sélection lorsque le bouton de la souris est relâché avant de repasser au mode de dessin normal, il disparaîtrait. Il faut donc commencer par repasser au mode de dessin normal pour éviter qu’il ne disparaisse. Le programme est assez flexible pour retourner les coordonnées ulx,uly et lrx, lry quelle que soit la manière de dessiner le rectangle de sélection, même si l’on commence par exemple par le coin supérieur droit. Ainsi, on aura toujours ulx < lrx et uly < lry. Notez que le programme n’effectue aucune conversion entre les coordonnées de la souris (mouse coordinates) et les coordonnées fenêtre (window coordinates) puisque les deux systèmes coïncident dans le mesure où l’on choisit une taille identique pour la fenêtre avec size() et le système de coordonnées avec window().Même lorsque la souris est déplacée à l’extérieur de la fenêtre, le programme continue de recevoir des événements de type « glissé de la souris ». Il faut être très prudent à l’utilisation de ces coordonnées extérieures à la fenêtre, sans quoi le programme pourrait planter de manière inattendue. |
SUPPRESSION DES YEUX ROUGES |
from gpanel import * size = 300 def onMousePressed(e): global x1, y1 global x2, y2 setColor("blue") setXORMode("white") x1 = x2 = e.getX() y1 = y2 = e.getY() def onMouseDragged(e): global x2, y2 rectangle(x1, y1, x2, y2) # erase old x2 = e.getX() y2 = e.getY() rectangle(x1, y1, x2, y2) # draw new def onMouseReleased(e): rectangle(x1, y1, x2, y2) # erase old setPaintMode() ulx = min(x1, x2) lrx = max(x1, x2) uly = min(y1, y2) lry = max(y1, y2) doIt(ulx, uly, lrx, lry) def doIt(ulx, uly, lrx, lry): for x in range(ulx, lrx): for y in range(uly, lry): col = img.getPixelColor(x, y) red = col.red green = col.green blue = col. blue col1 = makeColor(3 * red / 4, green, blue) img.setPixelColor(x, y, col1) image(img, 0, size) x1 = y1 = 0 x2 = y2 = 0 makeGPanel(Size(size, size), mousePressed = onMousePressed, mouseDragged = onMouseDragged, mouseReleased = onMouseReleased) window(0, size, size, 0) # y axis downwards img = getImage("sprites/colorfrog.png") image(img, 0, size) |
MEMENTO |
Le code effectuant le traitement de l’image est contenu dans la fonction doIt(). Le reste du code n’est que réutilisation des programmes précédents. Le traitement se fait simplement en parcourant tous les pixels de la zone rectangulaire sélectionnée et en diminuant de 25% la composante rouge. Remarquez le double slash qui représente une division entière permettant d’obtenir à nouveau un nombre entier. |
COUPER ET STOCKER DES IMAGES |
from gpanel import * size = 300 def onMousePressed(x, y): global x1, y1 global x2, y2 setColor("blue") setXORMode("white") x1 = x2 = int(x) y1 = y2 = int(y) def onMouseDragged(x, y): global x2, y2 rectangle(x1, y1, x2, y2) # erase old x2 = int(x) y2 = int(y) rectangle(x1, y1, x2, y2) # draw new def onMouseReleased(x, y): rectangle(x1, y1, x2, y2) # erase old setPaintMode() ulx = min(x1, x2) lrx = max(x1, x2) uly = min(y1, y2) lry = max(y1, y2) doIt(ulx, uly, lrx, lry) def doIt(ulx, uly, lrx, lry): width = lrx - ulx height = lry - uly if ulx < 0 or uly < 0 or lrx > size or lry > size: return if width < 20 or height < 20: return cropped = GBitmap.crop(img, ulx, uly, lrx, lry) p = GPanel(Size(width, height)) # another GPanel p.window(0, width, 0, height) p.image(cropped, 0, 0) rc = save(cropped, "mypict.jpg", "jpg") if rc: p.title("Saving OK") else: p.title("Saving Failed") x1 = y1 = 0 x2 = y2 = 0 makeGPanel(Size(size, size), mousePressed = onMousePressed, mouseDragged = onMouseDragged, mouseReleased = onMouseReleased) window(0, size, size, 0) # y axis downwards img = getImage("sprites/colorfrog.png") image(img, 0, size) |
MEMENTO |
Il est possible d’afficher plusieurs fenêtres GPanel en créant plusieurs objets GPanel. Pour spécifier dans laquelle il faut effectuer les instructions de dessin, il faut invoquer les opérations graphiques avec l’opérateur point. Si la région sélectionnée est trop petite, par exemple si on clique avec la souris sans la déplacer, ou si elle se situe en partie en dehors de la fenêtre, la fonction doIt() se termine avec une instruction return vide. Pour sauvegarder une image, on peut utiliser la méthode save() où le dernier paramètre détermine le format de l’image. Les valeurs permises sont "bmp", "gif", "jpg", "png". |
EXERCICES |
|
MATÉRIEL BONUS |
FILTRER DES IMAGES PAR CONVOLUTION |
Vous connaissez certainement des programmes de traitement d’images mettant à disposition de nombreux filtres tels que le lissage, l’accentuation des contours, le floutage, etc … L’implémentation de ces filtres fait systématiquement appel à une opération mathématique appelée convolution sur laquelle vous pouvez sans problème vous renseigner plus en détails sur le Web [plus... Cla convolution est un principe mathématique utilisé très souvent en mathématiques, dans les sciences naturelles ou dans les sciences de l’ingénieur]. In this process, you change the color values of each pixel by calculating a new value from it and its eight neighboring pixels, according to a filtering rule. Voici une explication plus précise du fonctionnement de la convolution sur une image en nuances de gris où chaque pixel possède une valeur de gris v comprise entre 0 et 255. La règle de filtrage est définie par neuf nombres disposés en carré : m00 m01 m02
Pour simplifier, on pourrait dire que pour calculer la valeur de gris d’un pixel (rouge sur le schéma), on place le centre de la matrice de convolution au-dessus du pixel à recalculer, que l’on multiplie chacun de ses éléments avec la valeur de gris du pixel sous-jacent et que l’on fait la somme de ces neufs produits. from gpanel import * size = 300 makeGPanel(Size(2 * size, size)) window(0, size, size, 0) # y axis downwards bmIn = getImage("sprites/frogbw.png") image(bmIn, 0, size) w = bmIn.getWidth() h = bmIn.getHeight() bmOut = GBitmap(w, h) #mask = [[1/9, 1/9, 1/9], [1/9, 1/9, 1/9], [1/9, 1/9, 1/9]] # smoothing mask = [[ 0, -1, 0], [-1, 5, -1], [0, -1, 0]] #sharpening #mask = [[-1, -2, -1], [ 0, 0, 0], [ 1, 2, 1]] #horizontal edge extraction #mask = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]] #vertical edge extraction for x in range(0, w): for y in range(0, h): if x > 0 and x < w - 1 and y > 0 and y < h - 1: vnew = 0 for k in range(3): for i in range(3): c = bmIn.getPixelColor(x - 1 + i, y - 1 + k) v = c.getRed() vnew += v * mask[k][i] # Make int in 0..255 vnew = int(vnew) vnew = max(vnew, 0) vnew = min(vnew, 255) gray = Color(vnew, vnew, vnew) else: c = bmIn.getPixelColor(x, y) v = c.getRed() gray = Color(v, v, v) bmOut.setPixelColor(x, y, gray) image(bmOut, size / 2, size) |
MEMENTO |
Lors d’une convolution, la valeur du pixel central est remplacée par une moyenne pondérée de la valeur actuelle du pixel et celle de ses huit voisins. La pondération des différents facteurs est indiquée par la matrice de convolution qui détermine le type de filtre dont il s’agit. |
Pourquoi ne pas expérimenter avec les matrices de convolution bien connues ci-dessous ou inventer vos propres matrices de convolution ?
|