[Plan du site]
Vous êtes ici ---
> Le Site du Zéro
> Les tutoriels
> Non-Officiels
> Programmation
> Général
> Vision par Ordinateur > Notions de base et traitement d'images > Une image... c'est quoi ?
> Lecture du tutoriel
Une image... c'est quoi ?
Vous vous apprêtez à lire un tutoriel rédigé par un membre de ce site. Malgré tout le soin que ce membre a pu apporter au tutoriel, nous ne pouvons pas garantir que les informations contenues sur cette page sont exactes à 100%. Merci de garder cela en tête lorsque vous lirez cette page ;o)
Hum !
Je sais bien que vous savez ce que c'est qu'une image.
Ce chapitre est en fait un chapitre "d'introduction" à proprement parler, puisque nous allons voir les notions théoriques de base d'une part, et leurs analogues dans OpenCV d'autre part.
Comme vous le constaterez assez rapidement, les paragraphes de ce tutoriel sont clairement scindés en 2 approches : théorique et pratique.
Cette séparation a pour but de vous montrer que
tout ce que vous voyez en théorie ne dépend d'aucun langage de programmation.
Ce chapitre n'est pas "
particulièrement difficile", mais "
particulièrement long", et les notions qu'il renferme sont "
particulièrement fondamentales" (si vous me permettez l'expression

). Je vous conseille donc de prendre
tout votre temps pour le lire.
Vous n'avez pas besoin de tout apprendre par coeur du premier coup dans les parties marquées
[OpenCV]. L'essentiel est que vous compreniez le code. Le reste rentrera bien assez vite

.
Allez, c'est parti !
Définitions
Allons droit à l'essentiel :
Une
image numérique, c'est un
tableau de pixels.
Hum d'accord, mais un pixel, c'est quoi ?
"
Pixel" vient de l'anglais
picture element, c'est l'élément le plus petit composant une image. Il décrit la couleur d'une "case" de l'image.
Nous allons commencer simplement en considérant que nos images ne sont pas en couleurs mais en
niveaux de gris.
Dans ce cas, un pixel, c'est un nombre

compris entre 0 et 255, car il est codé sur un seul octet (= 256 valeurs possibles).
Ainsi on peut considérer notre image comme
la matrice (= "
le tableau")

:

.
Dans cette situation, nous avons une image de

pixels de large et

pixels de haut, chacun portant un nombre entre 0 et 255 appelé niveau de gris.
Le niveau de gris correspond à
la luminosité du pixel :
- 0 correspond au noir
- 255 correspond au blanc
- toutes les autres valeurs sont du gris "plus ou moins proche" du noir ou du blanc.
En gros, une image en niveaux de gris, c'est un tableau d'entiers à deux dimensions, c'est ça ?
Voilà !
C'est effectivement de cette manière que l'on va considérer nos images dans un premier temps, sans se brouiller avec les couleurs

.
Si cela vous parait un peu trop restrictif, sachez qu'énormément de traitements s'appliquent sur des images qui ont été préalablement passées en niveaux de gris, et que mieux vous maîtriserez ces derniers, plus il vous sera simple de traiter les images en couleur

.
Repère du plan pixellique
Pour être capables de situer les pixels les uns par rapport aux autres dans une image, il nous faut définir un
repère. Cela signifie définir une
origine, et deux axes : les
abscisses et les
ordonnées.
Par convention, en informatique, l'origine est le point le plus
en haut à gauche de l'image.
Le repère image est donc le repère suivant :
Ainsi, on peut accéder aux pixels simplement en donnant leurs coordonnées

comme dans un repère cartésien classique, mais
sans oublier que les ordonnées ont "la tête en bas".
Matrices et scalaires
Bon, je vous rassure, je ne vais pas vous sortir tout un cours d'algèbre sur le calcul matriciel !
En revanche, vous allez avoir besoin de quelques notions qui vous aideront à comprendre le traitement d'image au sens général d'une part, et le fonctionnement d'OpenCV d'autre part, donc
ne vous enfuyez pas !!
Ce n'est pas aussi terrible que le titre le laisse présager

.
Une
matrice, c'est plus ou moins
un tableau comme celui que je vous ai montré au début.

.
Je sais bien qu'au premier abord, c'est
moche et
effrayant (ça n'a pas grand chose à voir avec Keanu Reeves, mesdemoiselles, je vous l'accorde !). Mais dites-vous que c'est
l'objet mathématique qui correspond à un tableau, donc
a fortiori à une image

.
Un
élément d'une matrice s'appelle un
scalaire.
Dans notre cas, avec les images numériques :
Une image "est une matrice" et un pixel "est un scalaire".
Comme vous le voyez sur la matrice

, chaque scalaire

a deux
indices : ceux-ci désignent respectivement le
numéro de ligne, et le
numéro de colonne.
Par exemple :

désigne le scalaire qui se trouve sur la ligne numéro 38 (c'est donc la 39ème ligne), et la colonne numéro 12 (la 13ème colonne) de la matrice

.
Remarque : lorsque l'on cherche un élément dans une matrice, on commence par le chercher "verticalement" (sur "l'axe des ordonnées"), pour avoir son numéro de ligne, puis "horizontalement" pour avoir le numéro de colonne.
En résumé : Cela veut dire que sur une image,
le pixel 
de coordonnées

(par rapport au repère image) correspondra au
scalaire 
de la matrice

associée à cette image.

Ok Morpheus, et maintenant tu pourrais me neuro-simuler un
DOLIPRANE s'il te plait ?!
Bien ! J'ai compris le message : assez de maths pour le moment, passons à la pratique !
Dans ce paragraphe, nous allons voir quelques nouvelles fonctions qui permettent de manipuler des images. Nous mettrons tout ça en application juste après dans un petit programme qui servira à :
- Convertir une image en niveaux de gris,
- Inverser cette image convertie (c'est-à-dire la mettre "en négatif").
En attendant, les fonctions que vous allez voir vont vous faire découvrir un peu plus la strucure
IplImage.
Quelques champs utiles de la structure IplImage
Voici en vrac, quelques champs de la structure
IplImage, qui vont nous servir par la suite. Si
img est un
IplImage*, alors :
- img->width est la largeur de l'image.
- img->height est sa hauteur.
- img->depth est sa profondeur (ne scotchez pas là-dessus, on y viendra plus tard).
Pour ce qui est de la largeur et la hauteur de l'image, elles peuvent être résumées dans une structure "taille" :
CvSize, que l'on crée avec la fonction :
CvSize cvSize(int width, int height);
ou encore
récupérées d'une image avec la fonction :
CvSize cvGetSize(IplImage* img);
Créer une image vide, cloner une image
Créer une image
La fonction permettant de créer une image vide est la suivante :
IplImage* cvCreateImage(CvSize imageSize, int depth, int channels);
Exemple :
Code : C++1 | IplImage* image=cvCreateImage(cvSize(130, 150), IPL_DEPTH_8U, 1);
|
Détail des paramètres :
- imageSize : Taille de l'image.
C'est en fait une structure CvSize ("size" = taille), que l'on crée avec la fonction cvSize(int, int).
Pour une image de 130 pixels de large, et 150 de haut, on lui donne cvSize(130,150).
- depth : la "profondeur" de l'image.
Ne vous occupez pas de ça pour le moment, on utilisera toujours la constante IPL_DEPTH_8U, qui signifie que nos pixels sont des entiers naturels codés sur un octet.
- channels : le nombre de canaux de nos images.
On y reviendra quand on fera de la couleur. Retenez simplement qu'une image en niveaux de gris n'a qu'un seul canal
.
Cloner une image
Pour cloner une image, on utilisera la fonction :
IplImage* cvCloneImage(IplImage* src);
Cette fonction copie simplement une image dans une autre.
Exemple :
Code : C++1
2 | IplImage *image=cvLoadImage("monimage.jpg");
IplImage *copie=cvCloneImage(image);
|
Détail des paramètres :
- src : l'image que l'on clone.
Convertir une image en niveaux de gris / Retourner une image verticalement
Il existe plusieurs fonctions d'OpenCV pour faire tout ça

, je vais vous montrer la seule dont nous allons avoir besoin pour le moment :
void cvConvertImage(IplImage *src, IplImage *dst, int flags=0);
Cette fonction convertit l'image "src" en image "dst", avec des options facultatives.
Exemple :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11 | IplImage *image_couleur = cvLoadImage("monimage.jpg");
//Image en niveaux de gris :
//Même taille que image_couleur, même profondeur aussi, mais 1 canal
IplImage *image_nvg = cvCreateImage(cvGetSize(image_couleur), image_couleur->depth, 1);
//Convertir en niveaux de gris :
cvConvertImage(image_couleur, image_nvg);
//Convertir en niveaux de gris en retournant verticalement :
cvConvertImage(image_couleur, image_nvg, CV_CVTIMG_FLIP);
|
Détail des paramètres :
- src et dst : Images source et destination de la conversion.
- flags : Laisser à 0 si on fait une conversion simple, ou bien lui donner la constante CV_CVTIMG_FLIP pour retourner l'image en même temps.
Ca sert à quoi de retourner l'image ?
Excellente question

.
Vous vous souvenez que je vous ai dit tout au début que parfois
l'image n'est pas toujours initialisée avec l'origine conventionnelle du repère pixellique (en particulier sous Windows) ?
Quand c'est le cas, l'image a pour origine le point en bas à gauche (au lieu d'en haut).
Donc pour corriger ça, on convertit cette image en une autre (qui a la bonne origine), mais en la retournant pour éviter le changement de repère ne mette l'image la tête en bas

.
Donc on peut convertir une image couleur en une autre image couleur ?
Parfaitement !
C'est selon la forme de ce que vous donnez en paramètre à la fonction (tant que les images sont de même taille et de même profondeur).
Accéder aux pixels d'une image et les modifier
C'est ici que vous allez voir l'intéret de mon petit laïus de tout à l'heure sur les matrices et les scalaires.
Un pixel d'une image est représenté par la structure
CvScalar (scalaire).
Cela signifie clairement que pour OpenCV,
une image est une matrice. D'ailleurs la structure
IplImage est en fait une extension de la structure
CvArr (tableau), au même titre que la structure
CvMat (matrice), mais laissons ça de côté pour le moment, et intéressons-nous au
CvScalar.
Un scalaire pour OpenCV, ce n'est pas
un nombre, mais
un tableau de nombres (dont les champs peuvent être nuls si on n'a pas besoin d'eux).
Donc en fait pour accéder à la valeur d'un pixel en niveau de gris dans une image on doit :
- Accéder au scalaire dans l'image
- Accéder à la valeur dans le scalaire
Cela se fait en réalité très simplement grace à la fonction :
CvScalar cvGet2D([IplImage*] image, int y, int x);
Remarquez que l'on doit donner "y" avant "x"... comme pour accéder à un scalaire dans une matrice ! (c'est en fait la même fonction pour les images et les matrices dans OpenCV, c'est pour ça que j'ai mis le
IplImage* entre crochets).
Mais voyons un exemple pour simplifier les choses :
Code : C++1
2
3
4
5 | //J'accède ici au pixel de coordonnées (30,20) dans mon image ;)
CvScalar pixel = cvGet2D(mon_image, 20, 30);
//Dans "valeur" je mets la valeur du niveau de gris du pixel.
int valeur = pixel.val[0];
|
Vous voyez, ça n'est pas si terrible !!
Maintenant, pour modifier un pixel dans mon image, je peux utiliser la fonction :
void cvSet2D( [IplImage*] image, int y, int x, CvScalar scalaire);
Cette fonction modifie la valeur du pixel de coordonnées (x, y) dans l'image, en la remplaçant par le scalaire donné en paramètre

.

Y'en a encore combien, des trucs à retenir comme ça ?
Je vous l'ai dit :
N'apprenez pas tout par coeur, et
prenez votre temps. Vous allez comprendre tout ça avec le code du prochain paragraphe !
C'est vrai que mes chapitres sont très chargés, mais le but est de pouvoir vous faire faire quelque chose de nouveau et d'intéressant en TP à chaque fin de chapitre.
Inutile donc de vous précipiter

.
Nous y sommes ! Notre premier TP !
Nous allons réaliser un programme qui va nous permettre d'inverser une image après l'avoir convertie en niveaux de gris.
Faisons déjà entrer l'image en question :
Les plus observateurs l'auront reconnu, il s'agit de mon avatar

.
Pour les plus curieux, les caractères japonais à gauche (les "katakanas") signifient "noharu", c'est-à-dire NoHaR, c'est-à-dire mon pseudo

.
Mais ne nous égarons pas !
Nous allons transformer cette image en niveaux de gris et l'inverser !
Nous allons d'abord voir les différentes étapes du programme une par une avant de voir le fichier final.
Déclaration / Initialisation des images
Nous allons avoir besoin de 3 (pointeurs sur) structures
IplImage.
- Une pour stocker l'image de base ("img"),
- Une pour stocker l'image en niveaux de gris ("img_nvg"),
- Une pour stocker l'image inversée ("img_inv").
Voici ce que cela donne en code :
Code : C++1
2
3
4 | //Déclaration / Initalisation des images
IplImage *img=cvLoadImage("/home/arnaud/Images/noharu.png");
IplImage *img_nvg=cvCreateImage(cvGetSize(img), img->depth, 1);
IplImage *img_inv=cvCloneImage(img_nvg);
|
En clair, on charge l'image de base à partir du fichier "
noharu.png", on crée une image vide en niveaux de gris (même taille et même profondeur que
img, 1 seul canal), puis, au lieu de faire une 2ème fois la même initialisation pour l'image inversée, on clone l'image en niveaux de gris

.
Conversion en niveaux de gris
Là nous allons convertir notre image en niveaux de gris.
Mais
comme nous sommes prudents, nous nous souvenons que l'image de base peut ne pas avoir une origine convetionnelle.
Donc nous allons d'abord le vérifier, et si il le faut, on retournera l'image.
Code : C++ 1
2
3
4
5
6
7
8
9
10 | //On vérifie l'origine de l'image
//Si elle n'est pas en haut à gauche, il faut la corriger
int flip=0;
if(img->origin!=IPL_ORIGIN_TL)
{
flip=CV_CVTIMG_FLIP;
}
//Conversion en niveaux de gris
cvConvertImage(img, img_nvg, flip);
|
IPL_ORIGIN_TL est une constante qui signifie "Origine de l'
IplImage en haut à gauche" (TL = "Top Left").
Si l'origine n'est pas bonne,
flip contient l'instruction "retourner l'image", sinon, il contient 0 pour "ne rien faire".
On utilise ensuite la fonction
cvConvertImage pour convertir notre image en niveaux de gris

.
Inversion de l'image
Inverser une image en niveaux de gris, c'est transformer le clair en foncé et le foncé en clair.
Comme les pixels ont une valeur comprise entre 0 et 255, il suffit en fait de les remplacer par
255 - "valeur du pixel".
Nous allons donc parcourir toute l'image en niveaux de gris (qui est de même taille que l'image "inversée"), et mettre, dans chaque pixel de l'image "inversée", l'inverse de la valeur du pixel correspondant dans l'image en niveaux de gris.
En code, cela donne :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | //On déclare un scalaire pour l'inversion
CvScalar scalaire;
//On va parcourir toute l'image en niveaux de gris
//x -> dans le sens de la largeur
//y -> dans le sens de la hauteur
for(int x=0; x<img_nvg->width; x++)
{
for(int y=0; y<img_nvg->height; y++)
{
//On récupère le pixel (x,y) de l'image en niveaux de gris.
scalaire=cvGet2D(img_nvg, y, x);
//On "l'inverse"
scalaire.val[0]=255-scalaire.val[0];
//On le remplace dans l'image "inversée"
cvSet2D(img_inv, y, x, scalaire);
}
}
|
Voilà !
Pour ce qui est du reste, c'est exactement comme dans "helloworld.cpp"

.
Programme complet
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 | /**
* inversion.cpp
**/
#include <opencv/cv.h>
#include <opencv/highgui.h>
using namespace std;
int main()
{
//Déclaration / Initalisation des images
IplImage *img=cvLoadImage("/home/arnaud/Images/noharu.png");
IplImage *img_nvg=cvCreateImage(cvGetSize(img), img->depth, 1);
IplImage *img_inv=cvCloneImage(img_nvg);
//On déclare un scalaire pour l'inversion ;)
CvScalar scalaire;
//Création des fenêtres dans lesquelles nous afficherons les images
cvNamedWindow("img",CV_WINDOW_AUTOSIZE);
cvNamedWindow("nvg",CV_WINDOW_AUTOSIZE);
cvNamedWindow("inv",CV_WINDOW_AUTOSIZE);
//On vérifie l'origine de l'image chargée
//Si elle n'est pas en haut à gauche, il faut la corriger
int flip=0;
if(img->origin!=IPL_ORIGIN_TL)
{
flip=CV_CVTIMG_FLIP;
}
//Conversion en niveaux de gris
cvConvertImage(img, img_nvg, flip);
//On va parcourir toute l'image en niveaux de gris
//x -> dans le sens de la largeur
//y -> dans le sens de la hauteur
for(int x=0; x<img_nvg->width; x++)
{
for(int y=0; y<img_nvg->height; y++)
{
//On récupère le pixel (x,y) de l'image en niveaux de gris.
scalaire=cvGet2D(img_nvg, y, x);
//On "l'inverse"
scalaire.val[0]=255-scalaire.val[0];
//On le remplace dans l'image "inversée"
cvSet2D(img_inv, y, x, scalaire);
}
}
//Affichage des images
cvShowImage("img", img);
cvShowImage("nvg", img_nvg);
cvShowImage("inv", img_inv);
//On attend que l'utilisateur aie appuyé sur une touche pour continuer
cvWaitKey(0);
//Destruction des fenêtres et désallocation des images.
cvDestroyWindow("img");
cvDestroyWindow("nvg");
cvDestroyWindow("inv");
cvReleaseImage(&img);
cvReleaseImage(&img_nvg);
cvReleaseImage(&img_inv);
return 0;
}
|
Et voici le résultat :
Sympa non ?
Eh bien ! Voilà un premier "vrai" chapitre d'abattu...
Effectivement, c'est pas mal de boulot !
Ne vous découragez pas pour autant : dans la suite, on n'abordera pas
autant de choses nouvelles à la fois

. Avouez quand même que votre premier programme valait la peine de verser un peu de sueur

.
Si je peux me permettre un conseil, n'hésitez pas à revoir les deux derniers chapitres jusqu'à ce que vous les maîtrisiez. Du point de vue théorique, tout va reposer de manière implicite sur ce que vous avez vu ici. Quant au point de vue pratique, eh bien... ce sont vraiment les fonctions de base d'OpenCV

.
Dans les prochains chapitres, nous verrons un peu plus de théorie (nouveaux traitements) et un peu moins de nouvelles fonctions OpenCV.