Des tableaux... de tableaux !
Pré-requis indispensable : être à l'aise avec la notion de tableau vu au chapitre précédent, et comment créer un tableau, le modifier, s'adresser à un de ses éléments, les parcourir, ...
Dans le cas contraire : revoir le chapitre !!
Vous avez vu aux chapitres précédents la notion de tableaux, correspondant au type list de Python.
Rien n’empêche en Python ( et dans de nombreux autres langages de programmation ) de construire des tableaux à deux dimensions, ou plutôt des "tableaux de tableaux", c'est à dire des tableaux dont les éléments eux-mêmes sont des tableaux !
Si les "sous-tableaux" de ces "tableaux de tableaux" contiennent tous le même nombre d'éléments, alors on parle dans ce cas de matrices, que l'on peut se représenter comme une "grille" formée de "colonnes" et de "lignes", l'intersection de chaque "colonne" avec chaque "ligne" représentant une "case" dans cette grille.
C'est ce type de structure de données que l'on étudiera dans ce chapitre; en effet, les matrices ont de nombreuses applications en informatique, comme par exemple :
- la représentation en mémoire d'un "plateau de jeu" comme un échiquier, un damier,...
- la représentation en mémoire des images numériques, chaque "case" de la matrice correspondant alors à un point de l'image, ce que l'on appelle un pixel ( objectif de ce chapitre...)
- ..............
Structure d'une matrice
Un matrice de 4 lignes et 4 colonnes pourrait se présenter sous cette forme :
matrice = [[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]]
On retrouve les crochets déjà utilisés pour définir un tableau et les virgules séparant les différents éléments...
Les sauts de ligne sont là pour structurer l'affichage ci-dessus de la matrice de façon à ce qu'elle ressemble effectivement à un tableau avec des "lignes" et des "colonnes"; mais en réalité, ces sauts ne sont pas à faire figurer.
Cette matrice doit en réalité être écrite ainsi :
matrice = [[0, 1, 2, 3],[4, 5, 6, 7],[8, 9, 10, 11],[12, 13, 14, 15]]
Adressage dans une matrice
Adressage d'une "ligne" entière :
Pour adresser une "ligne" complète d'un tableau, ( donc un des "sous-tableau" ), on utilise la notation déjà vue : un élément est repéré par son index donnant sa position dans la tableau.
>>> matrice[2] # soit : afficher la ligne d'index 2
[8, 9,10,11]
>>> matrice[0]
[0,1,2,3]
Adressage d'une "case"
Une "case" de la matrice ( donc un élément dans un des "sous-tableau'... ) devra par contre être repérée par deux index : le numéro de la "ligne" et le numéro de la "colonne" où il est stocké.
on donne toujours le numéro de la ligne en premier selon la syntaxe suivante :
nom_de_la_matrice[ligne][colonne]
Exemples :
>>> matrice[2][0] # soit : afficher le 1er élément de la 3ème ligne
8
>>> matrice[3][1] # 2ème élément de la 4ème ligne
13
Pas évident !! Il faudra bien y faire attention...
Adressage d'une "colonne"
Impossible à faire directement...Il faudra parcourir la matrice ( cf chapitre suivant ).
Nombre d'éléments dans une matrice
Nombre de "lignes"
Il est égal aux nombres de "sous-tableaux", donc au nombre d'éléments de la matrice :
>>> len(matrice)
4
Nombre de "colonnes"
Il est égal aux nombres d'éléments dans chaque sous-tableau; puisque toutes les "lignes" ont le même nombre de "colonnes", il suffit donc de regarder le nombre d'éléments qu'il y a, par exemple, dans le premier "sous-tableau" :
>>> len(matrice[0])
4
Nombre de "cases"
A votre avis ? On fait comment ??
Construction d'une matrice
A part la définition complète comme dans l'exemple ci dessus, on peut également créer une matrice par compréhension.
Vous avez déjà vu ça au chapitre sur les tableaux., mais pour une matrice, la construction nécessitera deux boucles : la première pour parcourir les "colonnes", la deuxième pour les "lignes."
Exemples :
Une matrice de 5 lignes et 4 colonnes ne contenant que des 0 :
>>> [[0 for j in range(4)] for i in range(5)] # boucle j = "colonnes" / boucle i = "lignes"
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
Pour obtenir dans une matrice les tables de multiplication de 1 à 9 :
>>> [[i*j for j in range(1,10)] for i in range(1,10)]
[[1, 2, 3, 4, 5, 6, 7, 8, 9], [2, 4, 6, 8, 10, 12, 14, 16, 18], [3, 6, 9, 12, 15, 18, 21, 24, 27], [4, 8, 12, 16, 20, 24, 28, 32, 36], [5, 10, 15, 20, 25, 30, 35, 40, 45], [6, 12, 18, 24, 30, 36, 42, 48, 54], [7, 14, 21, 28, 35, 42, 49, 56, 63], [8, 16, 24, 32, 40, 48, 56, 64, 72], [9, 18, 27, 36, 45, 54, 63, 72, 81]]
Méthode très puissante à condition de bien savoir ce que l'on doit écrire !
Il faut donc avoir en tête que dans l'expression :
[[... for j in ...] for i in ...]
- l'indice j (situé à l'intérieur des lignes) représente l'indice de colonne,
- l'indice i (situé à l'extérieur des lignes) représente l'indice de ligne.
QCM d'entraînement
Exercices
Construction de matrices
Construire, en complétant l'instruction de l'éditeur ci-dessous, puis afficher, les matrices suivantes :
- une matrice de 5 lignes et 10 colonnes dont chaque élément est un entier aléatoire entre 0 et 100
- une matrice mat2 d'entiers aléatoires comme celle-ci :
[[3, 8, 7, 2, 1, 6, 4], [7, 6, 2, 8, 4, 2, 5], [6, 4, 4, 7, 6, 5, 3], [6, 7, 5, 4, 7, 4, 9], [4, 4, 7, 5, 3, 8, 4], [2, 3, 1, 3, 7, 1, 2], [3, 5, 3, 8, 7, 6, 2]] - la matrice mat3 ci-dessous :
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [2, 2, 2, 2, 2, 2, 2, 2, 2, 2], [3, 3, 3, 3, 3, 3, 3, 3, 3, 3], [4, 4, 4, 4, 4, 4, 4, 4, 4, 4], [5, 5, 5, 5, 5, 5, 5, 5, 5, 5], [6, 6, 6, 6, 6, 6, 6, 6, 6, 6]] - la matrice mat4 suivante :
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]] - une matrice mat5 identique à celle ci-dessous :
[[0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1]] - la matrice mat6 donnée en introduction :
matrice = [[0,1,2,3],[4,5,6,7],[8,9,10,11],[12,13,14,15]]
Mutation/Copie de matrices
Comme les tableaux, les matrices sont des objets mutables : on peut modifier leurs éléments sans avoir à réaffecter commplètement la matrice entière.
Mais par conséquent, on rencontre avec elles les mêmes problèmes que l'on avait évoqués concernant la copie de tableaux, à savoir que modifier une matrice obtenue par "simple" affectation à une variable modifiera aussi la matrice d'origine; il faut donc faire une "vraie" copie d'une matrice pour obtenir un objet "indépendant" de l'original...
Dans l'éditeur ci-dessous :
- Compléter la fonction
viser_coins()qui :- prend en paramètre une matrice de caractères 'X' ou 'O' comprenant 6 lignes et 5 colonnes,
- la mute afin que les valeurs présentes dans les coins soient égales à 'X' (sans modifier les autres valeurs).
Tester le bon fonctionnement de la fonction avec les matrices ci-dessous :
mat_a = [['X', 'O', 'O', 'O', 'O'], ['O', 'O', 'O', 'O', 'O'], ['O', 'O', 'X', 'O', 'X'], ['X', 'O', 'O', 'O', 'O'], ['X', 'O', 'O', 'O', 'O'], ['O', 'O', 'O', 'O', 'O']] mat_b = [['O', 'O', 'O', 'O', 'O'], ['O', 'O', 'X', 'O', 'O'], ['O', 'X', 'O', 'O', 'O'], ['O', 'O', 'O', 'O', 'X'], ['O', 'O', 'O', 'O', 'X'], ['X', 'O', 'O', 'O', 'O']] - Compléter la fonction
viser_les_coins()qui :- prend en paramètre une matrice de caractères 'X' ou 'O' désormais de taille quelconque (mais non vide),
- la mute afin que les valeurs présentes dans les coins soient égales à 'X' (sans modifier les autres valeurs).
Tester votre fonction avec les matrices ci-dessous :
mat_a = [['O', 'O', 'O'], ['O', 'O', 'O'], ['O', 'X', 'O']] mat_b = [['X', 'O', 'O', 'O'], ['O', 'X', 'O', 'O'], ['O', 'O', 'O', 'O'], ['O', 'O', 'O', 'O'], ['O', 'O', 'O', 'O'], ['O', 'X', 'O', 'O'], ['O', 'O', 'O', 'O']] mat_c = [['O']] - Compléter la fonction
viser_les_coins_copie()qui :- prend en paramètre une matrice mat de caractères 'X' ou 'O' désormais de taille quelconque (mais non vide),
- renvoie une nouvelle matrice, copie de mat avec les valeurs présentes dans les coins égales à 'X' (sans modifier les autres valeurs).
Tester votre fonction avec les matrices ci-dessous :
mat_a = [['O', 'O', 'O'], ['O', 'O', 'O'], ['O', 'X', 'O']] mat_b = [['X', 'O', 'O', 'O'], ['O', 'X', 'O', 'O'], ['O', 'O', 'O', 'O'], ['O', 'O', 'O', 'O'], ['O', 'O', 'O', 'O'], ['O', 'X', 'O', 'O'], ['O', 'O', 'O', 'O']] mat_c = [['O']]
Images matricielles
Qu'est-ce qu'une image matricielle ?
Images matricielles et vectorielles
Une image informatique est constituée d'un ensemble de points élémentaires, appelés pixels; la vision de ces pixels de plus ou moins loin permet à l’œil de reconstituer globalement l'image réelle.
Ces pixels étant organisés en "lignes" et en "colonnes", c'est à dire une matrice ( en anglais bitmap ), on parle d'image matricielle lorsque l'on décrit effectivement l'image pixel par pixel. ( il existe en effet un autre type d'images dites vectorielles, qui ne sont pas décrites point par point mais par des fonctions mathématiques : droites, cercles, courbes,...)
On comprend que ce principe est à l'origine des limitations liées aux images matricielles :
- pour être la plus fidèle à la réalité, une image doit donc être composée de pixels les plus petits possibles : ils ne doivent même pas être discernables par l’œil. Si c'est le cas, on dit que l'image est pixelisée.
- plus les pixels sont petits, plus leur nombre sera donc grand pour coder une image donnée; le fichier qui stockera les informations relatives à l'image sera donc d'autant plus gros. Selon la situation ( taille du support de stockage, lourdeur de l'envoi de l'image sur un réseau, ....), cette taille peut poser problème.
Un compromis est donc généralement à faire entre la finesse de l'image et la taille du fichier résultant; ce compromis dépend de la destination de l'image : affichage en petite ou grande taille, affichage ou impression, envoi ou pas sur un réseau,...
Les fichiers d'images vectorielles sont beaucoup plus "légers", mais par contre, ils ne permettent pas de représenter tous les détails d'une photo par exemple.
Quelques notions à savoir sur les images matricielles
- Définition d'une image = nombre de pixels composant l'image; la définition est généralement donnée sous forme du produit du nombre de pixels horizontaux de l'image par le nombre de pixels verticaux : 640 x 480, 1900 x 1040,...
Modifier la définition d'une image permet donc de la redimensionner, la déformer, de diminuer sa taille d'impression ou d'affichage à l'écran, etc... - Profondeur des couleurs : il s'agit de la manière dont la luminosité ou la couleur est codée dans l'image. Plusieurs modes de couleur existent :
- Monochrome: Avec ce mode, il est possible d'afficher uniquement des images en deux "couleurs" : noir ou blanc.
Un pixel dont la valeur de luminosité est égale à 0 est considéré comme noir, 1 comme blanc. - Niveaux de gris ( "Noir et blanc" ): il permet d'obtenir différentes valeurs de gris, afin d'afficher des images nuancées.
Plus la valeur est grande, et plus la luminosité du pixel est claire.Le nombre de niveaux de gris utilisables est donné par le codage de chaque pixel, exprimée en bits :
Image en noir et blanc
Image en niveaux de gris - 8 bits -> 256 niveaux de gris
- 16 bits -> 65 536 niveaux de gris
- ...
-
Mode colorimétrique RVB : pour les images en couleur, on se base sur le principe de la synthèse additive des couleurs, selon lequel toute nuance de couleur peut être reproduite par le "mélange" d'une quantité variable de trois couleurs primaires, le Rouge, le Vert et le Bleu; le blanc correspond au mélange de la même "quantité" de chacune de ces 3 couleurs, le noir à l'absence de ces trois couleurs.
L'information sur chaque couleur primaire est stockée séparément, et la couleur d'un pixel est donc donnée par un ensemble de trois chiffres indiquant la "quantité" de chaque couleur primaire dans la teinte globale du pixel.
Cette "quantité" est codée par un nombre d'autant plus grand que la couleur primaire est présente dans la teinte finale du pixel; en mode 8 bits, ce nombre peut varier de 0 à 255, en mode 16 bits, de 0 à 65 535 : le nombre de nuances possibles est donc alors plus élevé...
Synthèse additive des couleurs Pour coder par exemple un pixel rouge en mode 8 bits, les trois valeurs seront ainsi : (255,0,0); pour un bleu : (0,0,255); un jaune ( mélange de rouge et de vert ) : (255,255,0),etc...
- Monochrome: Avec ce mode, il est possible d'afficher uniquement des images en deux "couleurs" : noir ou blanc.
Représentation d'une image matricielle en mémoire
On voit tout de suite le lien avec la notion de matrices pour représenter une image matricielle en mémoire d'un ordinateur :
- chaque pixel correspondra à une "case" d'une matrice, qui aura autant de "lignes" et de "colonnes" que nécessite la définition de l'image; les coordonnées de chaque pixel dans l'image correspondront aux index de l'élément correspondant dans la matrices ( [ligne][colonne] ).
- l'information stockée dans chaque "case" correspondra à la valeur du pixel : un nombre codant la luminosité pour les images monochrome ou en niveaux de gris, et un ensemble de trois valeurs ( un tuple par exemple...) codant les 3 couleurs primaires dans le cas d'une image en couleur.
Les formats d'image
Toutes ces informations sur les pixels, il va bien falloir les stocker dans un fichier à un moment donné !
De nombreux formats de fichiers image existent, un des plus connus, et le plus utilisé de nos jours, est le format JPEG (Join Photographic Expert Group).
Ce type de fichier est compressé, la compression des données permettant de faire en sorte qu'elles occupent moins d'espace ( les pixels d'une image RVB de définition 800x600 px prendraient plus de 11 Mo d'espace si les données n'étaient pas compressées ! ) .
Vous utiliserez le module Python PIL qui permet ( notamment ) de manipuler les pixels d'une image.
Le module PIL
Ce module permet de faire très simplement de la manipulation d'images matricielles; vous trouverez ici un aperçu des nombreuses possibilités de ce module.
Pour le travail à faire ci-dessous, vous vous contenterez cependant de créer le tableau de tableaux représentant les pixels des images, le reste du code se chargera de créer et d'afficher les images à proprement parler.
Travail à faire
Vous allez écrire un unique script qui utilise des matrices pour créer automatiquement des images numériques en niveaux de gris ou en couleurs.
Pour chaque image, vous écrirez une fonction qui construira la matrice des valeurs de pixels, et qui renverra ensuite cette matrice au programme principal.
La matrice sera construite selon une méthode à votre choix : élément par élément, ou par compréhension.
Voila un exemple qui crée une image de 200 x 200 pixels tous noirs :
On rappelle l'existence en Python d'un opérateur ternaire :
if else
...qui fonctionne ainsi :
- si condition est vraie, alors c'est expression 1 qui est évaluée,
- sinon ( si la condition est fausse ), c'est expression 2 qui est évaluée.
L'expression peut être une simple valeur, une expression arithmétique ou booléenne, etc...La condition peut être aussi complexe que nécessaire ( des and, des or,...)
Du noir et du blanc
Commençons pour simplifier par des images en niveaux de gris.
Une image grise
Une image ne contenant que des pixels gris, et dont la largeur, la hauteur et le niveau de gris ( entre 0 et 255 ) sont passées comme arguments à la fonction.
La fonction devra vérifier que le valeur du niveau de gris donnée par l'utilisateur est correcte.
Une image aléatoire
Une image contenant des pixels de valeurs aléatoires ( entre 0 et 255 ), et dont la largeur, la hauteur sont passées comme arguments à la fonction.
Le quadrillage
- une image de 200 x 200 pixels avec 10 "lignes" verticales
- même chose avec 10 "lignes" horizontales
Le dégradé de gris
- Une image de 256 x 256 pixels montrant un dégradé de gris horizontal, depuis la valeur 0 à gauche jusqu'à la valeur 255 à droite
- Modifier la fonction précédente pour que le dégradé soit vertical
De la couleur
Et maintenant, un peu de couleur...Vous pourrez souvent vous inspirer des scripts précédents.
Rappelez-vous que dans ce cas, la couleur d'un pixel est codée non pas par une valeur unique, mais par un tuple de 3 valeurs représentant, dans l'ordre, la composante Rouge, la Verte et la Bleue ( chaque composante variant entre 0 et 255 ).
Chaque "case" des matrices représentant l'image contiendra donc un tuple de 3 valeurs et non pas une valeur unique.
Voila un exemple qui crée une image de 200 x 200 pixels tous rouges :
Une image colorée aléatoire
Une image contenant des pixels ayant chacun une couleur aléatoire, et dont la largeur, la hauteur sont passées comme arguments à la fonction.
Le dégradé coloré
- Une image de 256 x 256 pixels montrant un dégradé progressif entre deux couleurs quelconques.
- Modifier la fonction pour que le dégradé soit vertical
Drapeaux
- le drapeau français en 256x200 pixels; arriverez-vous à construire la matrice correspondante par compréhension 😎 ?
- le drapeau italien en 256x200 pixels.