View  Edit  Attributes  History  Attach  Print  Search


ACCUEIL | DOCUMENTATION | STRATEGIES | TESTS


Librairie JavacvPro | Strategies

Stratégies : Capture Vidéo : Eléments d'optimisation de la capture d'un flux vidéo


Par X. HINAULT - Mars 2012

1.  L'objectif

  • L'objectif dans le cas du traitement d'image d'un flux vidéo en live va être d'obtenir un traitement d'image à la fréquence maximale possible. Ainsi, les webcams standards atteignent généralement des flux de l'ordre de 30 ips (ou fps) maximum. L'objectif va donc être d'obtenir ce flux maximum.
  • Les étapes "types" de la capture, du traitement et de l'affichage d'un flux vidéo traité en live vont être :
    • capture de l'image vidéo et stockage dans un PImage
    • transfert du PImage dans le buffer principal JavacvPro pour accès aux fonctions OpenCV sous Processing
    • traitement d'image proprement dit à l'aide des fonctions JavacvPro
    • transfert de l'image traitée ou de l'info utile vers Processing
    • et affichage final Processing

2.  Les contraintes temporelles à atteindre

  • En prenant pour objectif d'atteindre le débit maximal permis par une webcam sur port USB, soit 30 fps, il faut que l'ensemble des étapes de capture, de traitement et d'affichage final de l'image soit réalisé en 1000/30 = 33 ms !
  • On peut aussi accepter des temps de traitement de l'ordre de 20 à 25 fps, tout à fait acceptables, soit des délais de traitement par image vidéo de l'ordre de 50 à 40 ms.
  • Il est donc indispensable d'optimiser au maximum la rapidité des fonctions de transfert pour libérer au maximum le temps utile de traitement : c'est un des "goulots d'étranglement" que le code interne de la librairie JavacvPro cherche à optimiser.
  • Il est également souhaitable d'obtenir la meilleure résolution possible tout en atteignant ces objectifs de vitesse de traitement.

Certaines webcams telle que la Eye PS3 sont capable de capturer à 100fps ou plus... Dans cette situation, la notion d'optimisation des délais prend tout son sens puisque l'on ne dispose que de 10 à 15ms pour traiter chaque frame. Les tests réalisés avec la librairie JavacvPro m'ont permis de réaliser un suivi de balle en 320x240 à la vitesse de 80fps en temps réel ! (Avril 2012)

3.  La luminosité ambiante

  • Un point auquel on ne pense pas forcément de prime abord : la luminosité de la scène filmée est essentielle et doit être suffisante, comme en plein jour.

A code identique, une luminosité insuffisante pourra diviser quasiment par 2 voir 3 la fréquence maximale d'image obtenue !!

4.  Résolution de l'image

  • Sous réserve d'une luminosité satisfaisante, la résolution de l'image utilisée a évidemment un impact essentiel.
  • En résolution 160x120, avec une luminosité satisfaisante, on obtient une durée totale de 6ms (dont 4ms fixes) avec les délais suivants :
    • capture et mémorisation dans PImage : 0ms
    • chargement dans buffer JavacvPro: 1ms
    • traitement d'image JavacvPro : = temps utile : variable ( 2ms pour un traitement simple)
    • transfert vers Processing : 2ms
    • affichage final Processing : 1ms
  • En résolution 320x240, avec une luminosité satisfaisante, on obtient une durée totale de 15ms dont (10ms fixes) avec les délais suivants :
    • capture et mémorisation dans PImage : 1ms
    • chargement dans buffer JavacvPro: 3ms
    • traitement d'image JavacvPro = temps utile : variable ( 4ms pour un traitement simple)
    • transfert vers Processing : 7ms - 75% des temps fixes !
    • affichage final Processing : 1ms
    • NB : le temps utile potentiel à 30 fps est de 33ms - 11 ms = 22 ms, ce qui est largement suffisant pour des séquences de traitement enchaînant plusieurs traitement de base durant chacun quelques millisecondes.
  • En résolution 640x480, avec une luminosité satisfaisante, on obtient une durée totale de 80ms (dont 67ms fixes) avec les délais suivants :
    • capture et mémorisation dans PImage : 3ms
    • chargement dans buffer JavacvPro: 14ms
    • traitement d'image JavacvPro : 18ms
    • transfert vers Processing : 33ms
    • affichage final Processing : 7ms

Noter que l'on obtient tout de même des débits de 12 images secondes en 640 x 480 ce qui peut être tout à fait acceptable dans certaines situations.

Pour atteindre un traitement d'image sur flux vidéo de 30 fps, la résolution 320x240 est celle qui donne le meilleur compromis pour le ratio résolution utile / temps de traitement de l'image

5.  Utilisation de l'évènement captureEvent()

  • En ce qui concerne la capture de l'image, l'utilisation de captureEvent() semble optimiser légèrement la rapidité par rapport à l'utilisation de la fonction available() dans la fonction draw().

6.  Eviter de répéter l'appel de la fonction get() de l'objet GSCapture

  • pour afficher l'image de capture, appeler la fonction get() de l'objet GSCapture 1 seule fois et stocker le résultat dans un PImage qui sera affiché ensuite avec la fonction Processing image() : en évitant ainsi les appels répétés de la fonction get(), on diminue les temps fixes.

7.  Eviter le "réaffichage" de l'image traitée ou l'affichage du buffer principal JavacvPro

  • Des chiffres précédents, il apparaît clairement que le temps fixe le plus long est le transfert du buffer JavacvPro vers Processing (plus de 50% des temps fixes) : donc, il faut impérativement éviter de recourir à cette fonction, notamment pour afficher l'image source vidéo avant traitement, et préférer dans ce cas la fonction get() de l'objet GSCapture.
  • Dans un certain nombre de traitement, il n'est pas nécessaire d'afficher l'image traitée dans Processing, mais seulement d'utiliser les informations utiles obtenues par le traitement d'image (position d'un objet par exemple) : on peut donc dans ce cas se passer d'un réaffichage de l'image traitée, permettant ainsi de limiter le temps fixe de transfert Javacvpro vers Processing, soit plus de 50% du temps fixe en moins.

8.  Limiter la taille totale de la fenêtre Processing

  • En phase de mise au point, on peut être tenté d'afficher plusieurs frames différentes dans une fenêtre Processing qui va être d'une taille de 2 à 4 fois la taille de la capture webcam : mais ceci a pour effet de ralentir l'exécution du code !
  • Donc en pratique, utiliser une fenêtre Processing limitée à la taille de l'image capturée c'est à dire 320x240 typiquement.
  • On peut même potentiellement pousser l'optimisation jusqu'à se passer d'un affichage de l'image si on travaille à des vitesses 100 fps ou plus... et uniquement récupérer l'info utile issue du traitement.

9.  Le mode d'exécution compilé ou interprété

  • 30% de rapidité en plus environ en mode compilé.

10.  Conclusions pour un résultat optimal :

  • bonne luminosité ambiante (lumière du jour) sous peine de diviser quasiment par 2 voir 3 la fréquence maximale d'image
  • résolution d'image en 320x240 qui permet d'atteindre des framerate de 30 fps réel tout en donnant un bon résultat de détection/analyse
  • en basant son code de capture d'image sur la fonction captureEvent()
  • appeler la fonction get() de l'objet GSCapture 1 seule fois et stocker dans un PImage à afficher avec la fonction Processing image()
  • limiter voir éviter l'utilisation de la fonction getBuffer() ou image() de l'objet OpenCV (transfert du buffer principal JavacvPro vers Processing)
  • limiter la taille de la fenêtre Processing à celle de la capture, soit typiquement 320x240.
  • utiliser le mode compilé si l'on souhaite accélérer le code au maximum

Certaines webcams telle que la Eye PS3 sont capable de capturer à 100fps ou plus... Dans cette situation, la notion d'optimisation des délais prend tout son sens puisque l'on ne dispose que de 10 à 15ms pour traiter chaque frame. Les tests réalisés avec la librairie JavacvPro m'ont permis de réaliser un suivi de balle en 320x240 à la vitesse de 80fps en temps réel ! (Avril 2012)

11.  Programme type de test des délais des différentes étapes



// Programme d'exemple de la librairie javacvPro
// par X. HINAULT - Mars 2012
// Tous droits réservés - Licence GPLv3

// Code type pour estimation des délais de traitement d'image

import codeanticode.gsvideo.*; // importe la librairie vidéo GSVideo qui implémente GStreamer pour Processing (compatible Linux)
// librairie comparable à la librairie native vidéo de Processing (qui implémente QuickTime..)- Voir Reference librairie Video Processing
// cette librairie doit être présente dans le répertoire modes/java/libraries du répertoire Processing (1-5)
// voir ici : http://gsvideo.sourceforge.net/

import monclubelec.javacvPro.*; // importe la librairie javacvPro

PImage img;

GSCapture cam; // déclare un objet GSCapture représentant une webcam
// L'objet GSCapture étend PImage - se comporte comme un conteneur des frames issues de la webcam

OpenCV opencv; // déclare un objet OpenCV principal

int widthCapture=320*2; // largeur image capture
int heightCapture=240*2; // hauteur image capture
int fpsCapture=30; // framerate de Capture

int millis0=0; // variable mémorisation millis()
int millis0b=0; // variable mémorisation millis()

boolean flagCapture=false;

boolean debug=false; // affichage message debug

void setup(){ // fonction d'initialisation exécutée 1 fois au démarrage

        //--- initialise fenêtre Processing
        size (widthCapture*2, heightCapture); // crée une fenêtre Processing de la 2xtaille du buffer principal OpenCV
        //size (img.width, img.height); // aalternative en se basant sur l'image d'origine
        frameRate(fpsCapture); // taux de rafraichissement de l'image

       //---- initialise la webcam ---
       //cam = new GSCapture(this, widthCapture, heightCapture); // forme simplifiée
       cam = new GSCapture(this, widthCapture, heightCapture,"v4l2src","/dev/video1", fpsCapture); // Initialise objet GSCapture désignant webcam - forme complète

        //--- initialise OpenCV ---
        opencv = new OpenCV(this); // initialise objet OpenCV à partir du parent This
        opencv.allocate(widthCapture, heightCapture); // initialise les buffers OpenCv à la taille de l'image

        cam.start();  // démarre objet GSCapture = la webcam

}


void  draw() { // fonction exécutée en boucle

// Code capture GSVideo
/*
  if (cam.available() == true) { // si une nouvelle frame est disponible sur la webcam

    // mettre de préférence le code dans captureEvent()

  } // fin if available
*/


} // fin draw


//----- fonction évènement nouvelle image vidéo disponible --------
void captureEvent(GSCapture cam) {

  //--- afficche délai entre 2 captures --
  println("Durée entre 2 captures =" + (millis()-millis0b)+"ms soit un framerate de " + (1000.0/(millis()-millis0b))+"fps");  
  millis0b=millis(); // mémorise millis()  

  //---- délai capture + copie dans PImage
  millis0=millis(); // mémorise millis()  
  cam.read();
  img=cam.get();
  if (debug) println("Durée read + get =" + (millis()-millis0)+"ms.");

  //---- délai chargement dans buffer JavacvPro
  millis0=millis(); // mémorise millis()  
  opencv.copy(img); // charge l'image GSVideo dans le buffer openCV
  if (debug)println("Durée chargement buffer OpenCV=" + (millis()-millis0)+"ms.");

  //---- +/- délai affichage image départ        
  millis0=millis(); // mémorise millis()  
  //--- affiche image de départ avant opération sur image ---        
  image(img,0,0); // affiche le buffer principal OpenCV dans la fenêtre Processing
  // éviter d'utiliser image() ou getBuffer() de l'objet OpenCV
  //if (debug) println("Durée affichage image source =" + (millis()-millis0)+"ms.");

  //--- délai opérations sur image ---

  millis0=millis(); // mémorise millis()  

  //-- toutes ces formes sont possibles :
  //opencv.scharr(); // applique le filtre de scharr sur le buffer principal OpenCV avec paramètres par défaut - coeff=1
  opencv.scharr(0.4); //applique le filtre de scharr sur le buffer principal OpenCV avec coeff

  //opencv.scharr(opencv.Buffer,0.5); //applique le filtre scharr sur le buffer OpenCV désigné avec paramètres

  //--- pour effet "dessin au fusain"
  //opencv.gray(); // passage en niveau de gris
  //opencv.invert(); // pour dessin au trait noir sur blanc


  if (debug) println("Durée traitement image par OpenCV=" + (millis()-millis0)+" ms.");

  //--- affiche image finale ---

  //--- délai transfert buffer JavacvPro vers Processing
  millis0=millis(); // mémorise millis()  
  img=opencv.getBuffer();
  if (debug) println("Durée transfert vers Processing =" + (millis()-millis0)+" ms.");

  //---- délai affichage PImage ---
   millis0=millis(); // mémorise millis()  
   image(img,widthCapture,0); // affiche le buffer principal OpenCV dans la fenêtre Processing        
   if (debug) println("Durée affichage Processing =" + (millis()-millis0)+" ms.");


   //flagCapture=true; // témoin capture - si utilisation available


} // fin eventCapture()