Accueil du site > Programmation > Saisie d’images avec Video For Linux

Saisie d’images avec Video For Linux

jeudi 1er septembre 2005, par Pierre-Luc Bacon

Dans certains cas, l’ajout d’un système de vision à un robot s’avère un élément essentiel à son contrôle. Cependant, l’implantation des techniques d’analyse de l’image peut exiger une programmation complexe ou l’usage de technologies spécialisées. L’article qui suit traite du développement d’un programme de traitement vidéo sous Linux - ou plus précisément de l’étape de capture de l’image - un choix qui, vous l’aurez deviné, allie performance, stabilité et facilité de mise en oeuvre.

Voir en ligne : Programme de test effectué selon les explications de cet article

PNG - 107.4 ko
Seuillage du vert
Programme d’analyse vidéo en phase de développement.

Sommaire

  Ouverture de la caméra  
  Fermeture de la caméra  
  Requête sur le dispositif  
  Capture en tampon double  
  Saisie des images et copie dans un "buffer"  
   Lecture des données de l'image RGB et écriture d'un fichier PPM   
   Qu'est-ce qu'est la MMAP ?  

Avant toute chose, assurez-vous d’avoir à la fois Video For Linux installé sur votre système ainsi que le pilote en charge de votre camera web utilisée pour ce programme. S’il vous faut faire un choix concernant l’achat de matériel, vous pouvez opter simplement pour la Quickcam Express de Logitech, peu coûteuse et bien supportée par le SE. À ce sujet, consultez l’article de ce site « Installation de la Quickcam Express sous Linux » pour obtenir des informations sur son installation. Pour ce qui est de V4L (Video For Linux), il serait fort étonnant que votre distribution ne l’intègre pas dans son noyau. Si toutefois tout semble indiquer que ce support est absent de votre système, la dernière version est disponible à l’adresse http://linux.bytesex.org/v4l2/

Ouverture de la caméra

Considérant que la caméra que nous souhaitons utiliser se situe en /dev/videoX, son ouverture se fera par un appel à la fonction open(const char *chemin, int flags) avec ce chemin en paramètre ainsi qu’un drapeau indiquant le mode d’accès.


descrpFichier=open("/dev/video0", O_RDWR);
if(descrpFichier < 0)
  {
  printf("\aAucun peripherique detecte\n");
  printf("Verifiez les droits d'acces a /dev/video0\n");
  exit(-1);
  }

Si l’opération est réussie, la fonction open retournera le descripteur du fichier ouvert. Dans le cas contraire, un message d’erreur s’affichera. Les causes peuvent être multiples, mais la plupart du temps, le problème proviendra des permissions accordées à sur ce dispositif.

Fermeture de la caméra

Intuitivement, la fermeture de la caméra se fera par une fonction close, prenant en paramètre le descripteur de fichier obtenu par la fonction open.


close(descrpFichier);

Requête sur le dispositif

De manière à rendre l’application plus flexible devant le matériel utilisé, nous allons d’abord nous informer de ses possibilités. Cette opération se fera en effectuant un appel de type ioctl VIDIOCGCAP qui retournera le résultat dans une structure video_capability.


struct video_capability vidcap;
if (ioctl (descrpFichier, VIDIOCGCAP, &vidcap) < 0)
   exit (-1);

Les informations que contiendra la structure vidcap sont les suivantes :

struct video_capability
NomDescriptionCommentaires
name[32]Nom[32]
typeType Supporte la capture vidéo ou non
channelsLe nombre de canals Si applicable (télévision, radio)
audiosAudioSon disponible
maxheightHauteur maximale (pixels)
maxwidthLargeur maximale (pixels)
minheightHauteur minimale (pixels)
minwidthLargeur minimale (pixels)

Pour nous assurer d’avoir accès à un dispositif supportant la saisie d’images, nous allons utiliser un drapeau spécialement défini dans video for linux pour le comparer au champs type.


if (!(vidcap.type && VID_TYPE_CAPTURE))
   {
     printf ("Le peripherique detecte ne supporte pas la capture\n");
     exit (-1);
   }

D’autres conditions pourraient être testées pour s’assurer du bon fonctionnement du programme. Pour ce faire, V4L offre beaucoup d’autres drapeaux (flags) qu’on a d’ailleurs documentés adéquatement. [1]

Paramètres de l’image

La zone de capture doit également être contenue dans une structure, cette fois, de type video_window. VIDIOCSWIN servira à fixer la valeur de ses champs. Nous nous intéresserons seulement qu’à ceux-là :

struct video_window
ChampsCommentaire
xPosition en X
yPosition en Y
widthLargeur de l’image
heightHauteur de l’image

struct video_window fenetre;
fenetre.x = 0;
 fenetre.y = 0;
 fenetre.width = WIDTH;
 fenetre.height = HEIGHT;
 fenetre.clipcount = 0;
 fenetre.chromakey = 0;


 if (ioctl (descrpFichier, VIDIOCSWIN, &fenetre) < 0)
   {
     perror ("VIDIOCSWIN");
     exit (-1);
   }

Si VIDIOCSWIN a pu s’exécuter, cela ne signifie pas pour autant que les valeurs passées ont été supportées. N’oubliez pas de vérifier le résultat de l’opération ! Pour connaître la valeur des paramètres qui y ont été définis, nous appelleront VIDIOCGWIN par ioctl de nouveau.


if (ioctl (descrpFichier, VIDIOCGWIN, &fenetre) < 0)
   exit (-1);

Aspect de l’image

Tout comme la fonction VIDIOCSWIN permet de régler certains paramètres de l’image, VIDIOCSPICT déterminera l’ajustement et son aspect : contraste, couleur, profondeur etc. Voyons l’organisation de la structure correspondante :

struct video_picture
ChampsCommentaire
brightnessIntensité
hue
color
contrast
whitenessRéglage des blancs
depthProfondeur
paletteMode de représentation des couleurs

Le code qui permettra de modifier ces valeurs sera court :


image.hue = hue;
 image.colour = colour;
 image.contrast = contrast;
 image.brightness = brightness;
 image.whiteness = whiteness;
 image.depth = depth;
 image.palette = PALETTE;
 if (ioctl (descrpFichier, VIDIOCSPICT, &image) < 0)
   {
     perror ("VIDIOCSPICT");
     exit (-1);
   }

Comme expliqué plus-haut, nous nous assurerons d’avoir pu imposer ces valeurs avec la fonction VIDIOCGPICT :


if (ioctl (descrpFichier, VIDIOCGPICT, &image) < 0)
   {
     perror ("VIDIOCGPICT");
     exit (-1);
   }

Par la suite, l’affichage des valeurs chargées peut se faire simplement ainsi :


printf ("Hue: %d\n", image.hue);
 printf ("Color: %d\n", image.colour);
 printf ("Contrast: %d\n", image.contrast);
 printf ("Brightness: %d\n", image.brightness);
 printf ("Whiteness: %d\n", image.whiteness);
 printf ("Depth: %d\n", image.depth);
 printf ("Taille du buffer: %d octets\n", mbuf.size);

Capture en tampon double

Nous voilà finalement rendu à l’étape quasi finale du processus : l’obtention de l’image depuis le pilote de la caméra. Comme le temps d’occupation du processeur est une denrée préciseuse en informatique, nous tacherons ici à optimiser les opérations en implantant un système d’espace mémoire à tampon double de manière à éviter de perdre des images. Notez aussi que les données ne transiteront pas ici directement depuis le framebuffer de la carte vidéo mais passeront plutôt par un espace tampon.

L’idée du principe de capture à tampons multiples est de fournir en tout temps au pilote du périphérique un espace mémoire pour déposer son image. Cela se fait donc d’abord en exécutant une série de requêtes VIDIOCMCAPTURE qui seront mises en file par le pilote. Un appel VIDIOCSYNC permettra ensuite d’attendre jusqu’à la libération de l’image. La séquence des requêtes, placée dans un boucle infinie, devrait être la suivante :


VIDIOCMCAPTURE(0)
VIDIOCMCAPTURE(1)
VIDIOSYNC(0) /*Attends l'arrivée des données pour le buffer 0*"
[traitement des données du buffer 0]
VIDIOCMCAPTURE(0)/*Le buffer 0 est remis dans la file*/
VIDIOSYNC(1)/*Le buffer 1 est en remplissage*/
[traitement des données du buffer 1]
VIDIOCMCAPTURE /*Le buffer 1 est remis dans la file*/

L’image peut ensuite être retrouvée en mémoire en effectuant la somme du pointeur sur la MMAP plus l’élément offsets de la sturcture video_mbuf.

Ex :


unsigned char *buf;
buf=mmapptr+mbuf.offsets[num_image]

Saisie des images et copie dans un "buffer"

La fonction présentée ci-dessous tire partie d’un système à base de pointeurs de fonction. En effet, un pointeur sur une fonction capable d’effectuer le traitement de l’image sera passé en argument. Ce pointeur pourra ensuite être utilisé pour appeller la fonction appropriée lors du passage de l’image au programme.

Puisque nous utilisons l’interface MMAP, nous devrons d’abord obtenir des informations sur la taille du buffer en MMAP et la position des images dans cet espace.


if(ioctl(descrpFichier, VIDIOCGMBUF, &mbuf)<0)
{
perror("VIDIOCGMBUF");
exit(-1);
}

Nous obtiendrons effectivement cet espace en MMAP par la suite de cette manière :


ptr = (unsigned char*)mmap(0, mbuf.size, PROT_READ|PROT_WRITE, MAP_SHARED,descrpFichier,0);
if(ptr==(unsigned char*) -1){
perror("mmap");
exit(-1);
}

Nous voyons donc ici, plus-haut, que l’élément size de la structure mbuf informée plus tôt par l’appel VIDIOCGMBUF est utilisé pour déterminer l’espace nécessaire pour un tampon accessible en lecture et en écriture sur notre périphérique, déterminé par descrpFichier.

Vient maitenant une étape nécessaire avant d’effectuer nos appels aux fonctions de capture de V4L : nous devons renseigner une structure de type video_mmap dans ses champs height, width, et format. Cette étape est sans commentaires spéciaux, à l’exception du conseil suivant : il est profitable pour une question d’efficacité d’utiliser des macros (defines) pour donner la valeur à certains paramètres tels que la taille de l’image, le format etc. Ainsi, les mots en majuscules dans le bout de code suivant sont définis dans le fichier d’entête par les valeurs souhaitées.


 mapbuf.height = HEIGHT ;
 mapbuf.width = WIDTH;
 mapbuf.format = PALETTE;

Comme expliqué dans la section « capture en tampon double », nous effecturons une série d’appels VIDIOCMCAPTURE selon le nombre de buffers supportés par le pilote. Ceux-ci seront placés en file avant d’être synchronisés par la fonction VIDIOCSYNC.


for(image=0; image<mbuf.frames; image++)
   {
     mapbuf.frame = image;
     if(ioctl(descrpFichier, VIDIOCMCAPTURE, &mapbuf))
  {
    perror("VIDIOCMCAPTURE");
    exit(-1);
  }
   }
image=0;

La variable image, aura été déclarée plus-haut pour indiquer le nombre d’images pouvant être capturés par le pilote : c’est l’indicateur du nombre de buffers. La valeur de cette varible est ensuite changée à 0 car nous la réutiliserons comme argument à la fonction VIDIOCSYNC. Dans une boucle, nous mettons en application le principe expliqué dans le paragraphe précédent :


while(nbcapture<rep)
   {
        i=-1;
     while(i<0){ /*Tant qu'on obtient une erreur, l'appel doit etre repete. */
  /*On verifie que la fonction a reussi et qu'il n'y a pas eu de signals qui ont interrompu l'appel.*/
  i=ioctl(descrpFichier, VIDIOCSYNC, &image);
  if(i < 0 || errno == EINTR)
    {
      if(i<0)
        {
     perror("VIDIOCSYNC");
     printf("Le programme doit quitter\n\a");
     exit(-1);
        }
      continue;
    }
                  break; /*La sync. s'est bien deroule. On sort de la boucle*/
                }
 
     posImg=ptr+mbuf.offsets[image];
     /*La fonction de traitement peut maintenant etre appellee avec posImg comme argument*/
     printf("Adresse image: %p\n", posImg);
     ptrfonction(posImg,HEIGHT,WIDTH);
     /*On remet le buffer a la disposition du driver*/

     mapbuf.frame = image;
     if(ioctl(descrpFichier, VIDIOCMCAPTURE, &mapbuf)<0)
           {
    perror("VIDIOCMCAPTURE");
    printf("L'application doit quitter\n\a");
    closeCam();
    exit(-1);
          }
     image++;

     if(image>mbuf.frames)image=0;

     nbcapture++;

   }

Nous effectuons d’abord dans un premier passage la synchronisation de l’image 0 avec VIDIOCSYNC placé dans une boucle conditionelle qui vérifie que la fonction soit bel et bien parvenue à effectuer sa tâche sans avoir été interrompue par un signal ou un problème quelconque. Nous pouvons alors ensuite recueillir notre première image par le simple calcul suivant :


posImg=ptr+mbuf.offsets[image];

Suite à cette opération, le pointeur de fonction utilisé en paramètre de la fonction de capture peut être utilisé de la sorte :


ptrfonction(posImg,HEIGHT,WIDTH);

Nous pouvons maintenant remettre le buffer courant à la disposition du driver en utilisant de nouveau VIDIOCMCAPTURE.


mapbuf.frame = image;
     if(ioctl(descrpFichier, VIDIOCMCAPTURE, &mapbuf)<0)
           {
    perror("VIDIOCMCAPTURE");
    printf("L'application doit quitter\n\a");
    closeCam();
    exit(-1);
          }
     image++;

     if(image>mbuf.frames)image=0;


Le champs frame de la structure mapbuf de type video_mmap est défini à la l’indicateur de l’image venant d’être utilisée. Finalement, la dernière ligne s’assure de remettre la valeur de image à 0 si l’on a dépassé le nombre d’images en attente supporté.

Lecture des données de l'image RGB et écriture d'un fichier PPM

Une image saisie en format RGB24 peut être facilement interprétée : un pixel étant décrit par un triplet de valeurs dans l’ordre BGR. Le pointeur de notre image, appelons-le posImg, pointe donc sur la valeur B du premier pixel. Pour effectuer cette opération sur la totalité de l’image, deux boucle for seront utilisées : une première lisant tous les pixels de 1 à n pour une ligne et une seconde boucle passant d’une ligne à l’autre.


void extractPix(unsigned char* imgbuffer, int hauteur, int largeur)
{
printf("En cours d'ecriture de l'image...\n");
fp = fopen("capturecam.ppm", "w");
if(fp==NULL)
{
printf("Impossible d'ouvrir le fichier\n");
exit(-1);
}

fprintf(fp, "P6\n%d %d\n255\n",largeur, hauteur);
for(y=0;y<hauteur; y++)
{       
        for(x=0; x<largeur; x++)
        {
        b=imgbuffer[p];p++;
        g=imgbuffer[p];p++;
        r=imgbuffer[p];p++;
        putc(r, fp);
        putc(g, fp);
        putc(b, fp);       
        }
}
fflush(fp);
fclose(fp);

Ce code montre l’ouverture d’un fichier de type PPM [2] et de la scrutation de l’image en mémoire par une double boucle copiant les valeurs RGB dans l’ordre inverse dans les variables correspondantes de type unsigned char. Portez attention ici au prototype de cette fonction qui respecte la déclaration du pointeur de fonctions dans l’étape de capture de l’image.

Qu'est-ce qu'est la MMAP ?

MMAP signifie "memory map" en langue anglaise. Cette fonction sert à représenter de manière localisée (ou "projetter") un fichier dans une zone mémoire selon une indication de longueur de l’espace requis et de positionnement par rapport au descripteur d’un fichier. Vous remarquerez donc ici qu’en la définition de "fichier" sous système UNIX, nous entendons également "périphérique". La MMAP est donc un moyen de recuillir les données d’un certains flux dans une zone mémoire de manière organisée.

Portfolio

Seuillage du vert

Documents joints

  • camera.c (C source - 5.5 ko)
    Librairie de fonctions utilisées dans cet article. Pierre-Luc Bacon, 2005. GNU/GPL
  • Fichier d’entête (C header - 1.7 ko)
    Fichier d’entête de camera.c

Notes

[1] Voir Video4Linux Kernel API Reference. Une version du document est accessible sur ce site : www.aqra.ca/pierre-luc/camapp/API.html

[2] Pour plus de renseignements sur la manipulation d’images en format PPM, référez-vous à l’article 35 sur ce site intitulé "Conversion d’images couleur en niveaux de gris". http://www.aqra.ca/article.php3 ?id_article=35

Enregistrer au format PDF
Marquer cet article: Delicious Technorati

Répondre à cet articleRépondre à l'auteur:Pierre-Luc BaconRecommander à un ami

24 Messages de forum