Aller au menu - Aller au contenu

[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)
Avatar
Auteur : NoHaR
Visualisations : 4 979

Plus d'informations Plus d'informations
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 :D ). 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 !
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

[Théorie] Images numériques : premières notions

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 p \in \mathbb{N} 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") I :

I = \begin{bmatrix}p_{0,0} & p_{0,1} & p_{0,2} & \cdots & p_{0,w}\\p_{1,0} & p_{1,1} & p_{1,2} & \cdots & p_{1,w}\\p_{2,0} & p_{2,1} & p_{2,2} & \cdots & p_{2,w}\\\vdots & \vdots & \vdots & \ddots & \vdots\\p_{h,0} & p_{h,1} & p_{h,2} & \cdots & p_{h,w}\end{bmatrix}.


Dans cette situation, nous avons une image de w+1pixels de large et h+1 pixels de haut, chacun portant un nombre entre 0 et 255 appelé niveau de gris.
Le niveau de gris correspond à la luminosité du pixel :

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 :

Image utilisateur

Ainsi, on peut accéder aux pixels simplement en donnant leurs coordonnées \left( x_p, y_p \right) 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 :D .

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

I = \begin{bmatrix}p_{0,0} & p_{0,1} & p_{0,2} & \cdots & p_{0,w}\\p_{1,0} & p_{1,1} & p_{1,2} & \cdots & p_{1,w}\\p_{2,0} & p_{2,1} & p_{2,2} & \cdots & p_{2,w}\\\vdots & \vdots & \vdots & \ddots & \vdots\\p_{h,0} & p_{h,1} & p_{h,2} & \cdots & p_{h,w}\end{bmatrix}.

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 I, chaque scalaire p a deux indices : ceux-ci désignent respectivement le numéro de ligne, et le numéro de colonne.

Par exemple : p_{38,12} 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 I.

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 p de coordonnées (x_p, y_p) (par rapport au repère image) correspondra au scalaire p_{y_p,x_p} de la matrice I 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 !

[OpenCV] Manipulation d'images et accès aux pixels

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 à :
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 :

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 :

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 :

Convertir une image en niveaux de gris / Retourner une image verticalement



Il existe plusieurs fonctions d'OpenCV pour faire tout ça :D , 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 :

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 :D .
Donc on peut convertir une image couleur en une autre image couleur ?

Parfaitement ! :D
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 :
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 ;) .

[ TP 1 ] Inversion d'une image

Nous y sommes ! Notre premier TP ! :D

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 :

noharu.png

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 :D .
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.
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 :

Image utilisateur


Sympa non ? ;)

Q.C.M.

Complétez cette phrase :
"Le pixel est à une image ce que..."
Où se situe l'origine conventionnelle du repère pixellique ?
Question "moins facile" (on ne l'a vu qu'en TP).

Que fait le code suivant ?

Code : C++
1
2
3
4
5
6
int flip=0;
if(img->origin!=IPL_ORIGIN_TL)
{
    flip=CV_CVTIMG_FLIP;
}
cvConvertImage(img, img_nvg, flip);

Statistiques de réponses au QCM


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 :D . 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.
Chapitre précédent Sommaire Chapitre suivant
Retour en haut Retour en haut


Créé : le 03/05/2008 à 20:57:08
Modifié : le 20/09/2008 à 12:20:00
Avancement : 100%
Licence : Creative Commons BY-SA

8 commentaires

Changer de design | En savoir plus | Plan du site | Politique d'accessibilité | Règles | RSS tutoriels | RSS news
Édité par Simple IT SARL : Nous contacter | Notre blog | Revue de presse | Publicité

Y'a plus rien à lire, faut remonter maintenant !

Hébergement web - Correction de tutoriels - Créer un site
Vous souhaitez apparaître ici ? Contactez-nous.

Nombre de connectés 421 Zéros connectés | Requêtes SQL 8 requêtes | Temps de génération de la page : Total (SQL) 0.0409s (0.0285s)