View  Edit  Attributes  History  Attach  Print  Search

ACCUEIL | ARDUINO > S'INSTALLER > DEBUTER > APPROFONDIR | PROCESSING | MECATRONIQUE | MATERIEL | OUTILS | TESTS | Plus...|
Python > Shell > ATELIERS Python + Qt > PyQt apps > PyQt+Arduino | Mes Robots | RepRap | Mes lib'Arduino | Mes shields Arduino | Mes distros | Les Mini-PC |
ATELIERS ARDUINO| VIDEOS | COMPRENDRE | REFERENCES | CODER | TECHNIQUE | GNU/LINUX | LIENS | Rien à voir |

Outils > Processing

Processing – GSVideo + openCV : Capture d'un flux webcam par GSVideo et reconnaissance et comptage de balles colorées avec openCV et annonce vocale du résultat.

Explication

  • Dans ce programme on réalise une reconnaissance de balles colorées de couleur jaune/orange, la couleur étant filtrée grâce à un algorithme appelé "mixeur de canaux" tel que l'implémente le logiciel Gimp.
  • Ici, la capture vidéo en provenance de la webcam est assurée par la librairie GSVidéo, le traitement d'image est réalisé à l'aide des fonctions Processing standards et la reconnaissance de forme est réalisée à l'aide de la fonction openCV.
  • On réalise un comptage des formes détectées valides (balle jaune ou orange). Puis, lors de l'appui sur la touche <espace>, l'annonce du résultat est réalisée par synthèse vocale à l'aide du logiciel espeak (sous Ubuntu).

Matériel et configuration utilisés

  • PC Intel Core Quad 2.33 Ghz
  • Webcam(s) USB Hercules DualPix Exchange
  • Ubuntu 10.04 LTS
  • Processing 1-5
  • Librairie GSVideo 0.9

Ressources utiles

  • librairie openCV
  • librairie GSVideo

Le programme


// Programme processing
// généré avec le générateur de code Processing
// du site www.mon-club-elec.fr
// par X. HINAULT - tous droits réservés

// Programme écrit le : 21/9/2011.

// ------- Licence du code de ce programme : GPL v3-----
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License,
//  or any later version.
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

/////////////// Description du programme ////////////
// Utilise la librairie GSVideo de capture et lecture vidéo
// Utilise la librairie OpenCV de capture vidéo et reconnaissance visuelle

// XXXXXXXXXXXXXXXXXXXXXX ENTETE DECLARATIVE XXXXXXXXXXXXXXXXXXXXXX

// inclusion des librairies utilisées

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/
// et ici : http://codeanticode.wordpress.com/2011/05/16/gsvideo-09-release

import hypermedia.video.*; // importe la librairie OpenCV qui implémente la capture vidéo et la reconnaissance visuelle pour Processing
// cette librairie doit être présente dans le répertoire modes/java/libraries du répertoire Processing (1-5)
// voir ici : http://ubaa.net/shared/processing/opencv/

import java.awt.Rectangle;
// impporte l'objet rectangle du langage Java qui est utilisé par certaines fonction de la librairie openCV
// l'objet rectangle fournit les champs x,y du centre et hauteur/largeur (height/width) du rectangle
// voir ici : http://download.oracle.com/javase/1.4.2/docs/api/java/awt/Rectangle.html

// déclaration objets

GSCapture cam1; // 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

// déclaration variables globales

int xmin, xmax, ymin, ymax; // coordonnées de la zone à tester

int comptImg=0; // variable de comptage des images pour moyenne centre
int nbImg=1; // nombre images à prendre en compte avant mouvement
long moyX=0; // pour calcul moyenne X
long moyY=0; // pour calcul moyenne Y

int comptBall=0; // variable de comptage des balles
int comptBall0=0; // variable mémorise dernier comptage complet des balles

//------ déclaration des variables de couleur utiles ----
int jaune=color(255,255,0);
int vert=color(0,255,0);
int rouge=color(255,0,0);
int bleu=color(0,0,255);
int noir=color(0,0,0);
int blanc=color(255,255,255);
int bleuclair=color(0,255,255);
int violet=color(255,0,255);

// variable pour la taille de la capture video
int widthCapture=320*2; // largeur capture
int heightCapture=240*2; // hauteur capture
int fpsCapture=20; // framerate (image/secondes) pour la capture video


// XXXXXXXXXXXXXXXXXXXXXX  Fonction SETUP XXXXXXXXXXXXXXXXXXXXXX

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

        // ---- initialisation paramètres graphiques utilisés
        colorMode(RGB, 255,255,255); // fixe format couleur R G B pour fill, stroke, etc...
        fill(0,0,255); // couleur remplissage RGB - noFill() si pas de remplissage
        stroke (0,0,0); // couleur pourtour RGB - noStroke() si pas de pourtour
        rectMode(CORNER); // origine rectangle : CORNER = coin sup gauche | CENTER : centre
        imageMode(CORNER); // origine image : CORNER = coin sup gauche | CENTER : centre
        ellipseMode(CENTER); // origine cercles / ellipses : CENTER : centre (autres : RADIUS, CORNERS, CORNER
        //strokeWeight(0); // largeur pourtour
        frameRate(20);// Images par seconde - The default rate is 60 frames per second

        // --- initialisation fenêtre de base ---
        size(widthCapture, heightCapture); // ouvre une fenêtre xpixels  x ypixels
        background(0,0,0); // couleur fond fenetre

// --- initialisation des objets et fonctionnalités utilisées ---

        //======== Initialisation Objets GSVideo (capture et/ou lecture video =========

        // GSCapture(this, int requestWidth, int requestHeight, [int frameRate], [String sourceName], [String cameraName])
        cam1 = new GSCapture(this, widthCapture, heightCapture,fpsCapture,"v4l2src","/dev/video0"); // Initialise objet GSCapture désignant webcam
        // largeur et hauteur doivent être compatible avec la webcam - typiquement 160x120 ou 320x240 ou 640x480...
        // Meilleurs résultats avec framerate webcam entre 20 et 30 et frameRate programme idem ou multiple plus grand (40 pour 20 par ex)
        // la liste des webcam installées sous Ubuntu (Gnu/Linux) est donnée par la commande : ls /dev/video*

        // cam1.play();  // démarre objet GSCapture = la webcam - version GSVideo avant 0.9
        cam1.start();  // démarre objet GSCapture = la webcam - version GSVideo après 0.9

        //======== Initialisation Objets OpenCV (vidéo et reconnaissance visuelle =========

        opencv = new OpenCV(this); // initialise objet OpenCV à partir du parent This
        opencv.allocate(widthCapture,heightCapture); // crée le buffer image de la taille voulue
        // la capture de flux multiples n'est pas possible avec openCV.
        // Ici, on utilise GSVideo pour capturer le flux video et OpenCV pour l'analyse d'image seulement
        // Au final, on abouti à une amélioration significative de la rapidité d'exécution



} // fin fonction Setup

// XXXXXXXXXXXXXXXXXXXXXX Fonction Draw XXXXXXXXXXXXXXXXXXXX

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

// Code type capture GSVideo - utilisation possible aussi de captureEvent()

  if (cam1.available() == true) { // si une nouvelle frame est disponible sur la webcam
    cam1.read(); // acquisition d'un frame
    image(cam1, 0, 0); // affiche image
    //set(0, 0, cam); // affiche image - plus rapide


    analyseImage(cam1.get(), 0, 0); // appelle fonction avec coordonnées pour afficher image resultat

    //img1=cam1.get(); // récupère l'image GS video dans Pimage
    //opencv.copy(img1); // charge l'image dans le buffer openCV

    //opencv.copy(cam1.get()); // autre possibilité - charge directement l'image GSVideo dans le buffer openCV

  } // fin if available


        // while(true); // stoppe boucle draw

} // fin de la fonction draw()

// XXXXXXXXXXXXXXXXXXXXXX Autres Fonctions XXXXXXXXXXXXXXXXXXXXXX

//--- évènement capture vidéo avec librairie GSVideo---
//void captureEvent(GSCapture cam) { // est appelée lorsqu'une capture (nouvelle frame) survient - cam quelconque
// cf doc librairie Video Processing - cf exemple Capture LivePocky
// bloque pour plusieurs webcams

   // cette fonction est appelée à chaque fois qu'une nouvelle frame est disponible, quelque soit la caméra
   // utiliser des conditions pour tester la caméra disponible

  //if (cam1.available() == true) cam1.read(); // acquisition d'une nouvelle frame

//  } // fin fonction évènement captureEvent()

PImage analyseImage(PImage imgIn, int xIn, int yIn) { // la fonction renvoie image modifiée sans modifier l'image de départ

  PImage imgOut; // image renvoyée par la fonction

  comptBall=0; // initialise variable comptage balles

  imgOut=imgIn; // les opérations de transformation seront faites sur imgOut pour ne pas modifier imgIn

          //----- 1°) application du "mixeur de canaux" avec sortie sur canal Rouge
            //---- coeff à appliquer
            float coefRouge=1.5; // % de rouge
            float coefVert=1.9; // % du vert
            float coefBleu=-3; // % du bleu
            //---------- le traitement le plus efficace qui fonctionne est à tester dans Gimp au préalable
            //--------- ici réglage pour balle de couleur orangée ----

          imgOut.loadPixels(); // charge les pixels de l'image dans le tableau pixels[]

          for (int i = 0; i < imgOut.width*imgOut.height; i++) { // passe en revue les pixels de l'image - index 0 en premier

            float r = (red(imgOut.pixels[i])*coefRouge) + (green(imgOut.pixels[i])*coefVert) + (blue(imgOut.pixels[i])*coefBleu); // la couleur rouge
            //---- fonction mixeur de canaux
            //---- le canal rouge est le canal de sortie et a pour coeff 1
            //---- auquel on ajoute du vert avec coeff vert
            //---- et du bleu avec coeff bleu

            // les deux autres canaux restent inchangés
            float g = green(imgOut.pixels[i]); // la couleur verte

            float b = blue(imgOut.pixels[i]); // la couleur bleue

             imgOut.pixels[i] = color(r, g, b); // modifie le pixel en fonction

            }

          imgOut.updatePixels();  // met à jour les pixels de l'image  
          //image(imgOut, 0,0); // debug

        //----- 2°) transformation de l'image en monochrome en se basant sur le canal rouge

          imgOut.loadPixels(); // charge les pixels de la fenetre d'affichage

          for (int i = 0; i < imgOut.width*imgOut.height; i++) { // passe en revue les pixels de l'image - index 0 en premier

            float r = red(imgOut.pixels[i]);// la couleur rouge
            float g = red(imgOut.pixels[i]); // la couleur verte

            float b = red(imgOut.pixels[i]); // la couleur bleue

             imgOut.pixels[i] = color(r, g, b); // modifie le pixel en fonction

            }

          imgOut.updatePixels();  // met à jour les pixels  
          //image(imgOut, 0,0); // debug

        //------ on applique filtre de seuillage ---
        imgOut.filter(THRESHOLD,0.8); // applique filtre seuil à la fenetre d'affichage
        // à adapter en fonction de la luminosité ambiante - plus sombre, valeur 0.7, plus lumineux, valeur 0.8 ou plus  
        //image(imgOut, 0,0); // debug

        //--- on récupère l'image transformée ---
        //imgOut=get(0,0,width,height); // récupère image à partir fenetre d'affichage


        //--- on rebascule dans OpenCV ---
        opencv.copy(imgOut); // charge l'image modifiée dans le buffer opencv

        // trouve les formes à l'aide de la librairie openCV
        // blobs(minArea, maxArea, maxBlobs, findHoles, [maxVertices]);
        Blob[] blobs = opencv.blobs( 10, imgOut.width*imgOut.height/4, 15, false, OpenCV.MAX_VERTICES*4 );


        // ---- recharge image vidéo non traitée ---
        //opencv.read(); // lecture flux vidéo via OpenCV
        //noTint();
/*         if (camIn==1) {
              cam1.read();
              image(cam1, xIn, yIn);
        }

        if (camIn==2) {
              cam2.read();
              image(cam2, xIn, yIn);
        }  
*/

       // xxxxxxxxxxxx Analyse et gestion des formes reconnues xxxxxxxxxxxxxx
      for( int i=0; i<blobs.length; i++ ) { // passe en revue les blobs (= formes détectées)

      //---- détection du "centre" de l'objet ----
      int centreX= blobs[i].centroid.x; // centroid renvoie un objet Point qui fournit x et y
      int centreY= blobs[i].centroid.y; // centroid renvoie un objet Point qui fournit x et y


      /*
      //---------- dessine un cercle autour du centre détecté -----------
      noFill();
      stroke(vert);
      strokeWeight(2);
      ellipse (centreX,centreY, 10,10);
      */



        Rectangle rectangleBlob=blobs[i].rectangle; // récupère le rectangle qui contient la forme détectée

       //---- analyse du rectangle objet ----
       int ratioWH=rectangleBlob.width/rectangleBlob.height; // calcule le ratio largeur/hauteur
       int ratioHW=rectangleBlob.height/rectangleBlob.width; // calcule le ratio hauteur/largeur

       float aireBlob=blobs[i].area; // récupère l'aire de la forme courante


       float ratioAire=aireBlob/(rectangleBlob.width*rectangleBlob.height); // calcul du ratio Aire forme / Aire rectangle
       // aire cercle = pi * r²
       // aire carré = (2r)²=4r²
       // aire cercle/aire carré = (pi * r²)/ (4*r²) = pi/4 = 3/4 env
       // un cercle occupe 3/4 du carré le contenant

       //if ((ratioWH>0.9)) { // si forme proche du carré
       if ((ratioAire>0.5) && ((ratioWH>0.8) || (ratioHW>0.8))) { // si le rapport de l'aire de la forme / rectangle contenant est compatible avec un cercle

        //fill(jaune); // couleur remplissage RGB
        noFill(); // pas de remplissage
        stroke (bleuclair); // couleur pourtour RGB

        //tracé d'un rectangle autour du blob
        rect( xIn+rectangleBlob.x, yIn+rectangleBlob.y, rectangleBlob.width, rectangleBlob.height );

        //fill(jaune); // couleur remplissage RGB
        noFill();
        stroke (rouge); // couleur pourtour RGB


        // tracé des formes détectées
        beginShape(); // début tracé forme complexe

        for( int j=0; j<blobs[i].points.length; j++ ) { // parcourt tous les points du pourtour du blob
            vertex( xIn+blobs[i].points[j].x, yIn+blobs[i].points[j].y ); // tracé des points de la forme
        }
        endShape(CLOSE); // tracé forme complexe

        //------- comptage des balles -----
        comptBall=comptBall+1; // 1 objet valide a été détecté - incrémente variable de comptage

        //--------------- gestion centre pour servomoteur ----------
        // moyenne des X et des Y
        /*
        moyX=moyX+centreX;
        comptImg=comptImg+1;

        if (comptImg>=nbImg) { // si le nombre d'image à prendre en compte dépassé

           //println("X = "+ centreX);
           //println("Total X = "+ moyX);
           //println("comptImg="+comptImg);        
           moyX=moyX/(comptImg); // calcule moyenne centre X
           //println ("Moyenne X = "+ moyX);


           comptImg=0; // RAZ comptage images
           moyX=0; // RAZ moyX
        }

      */


       } // if ratio Aire

       else { // trace avec couleurs différentes les rectangles non souhaités
 /*      

        fill(bleu); // couleur remplissage RGB
        stroke (jaune); // couleur pourtour RGB

        //tracé d'un rectangle autour du blob
        rect( rectangleBlob.x, rectangleBlob.y, rectangleBlob.width, rectangleBlob.height );

        fill(jaune); // couleur remplissage RGB
        stroke (rouge); // couleur pourtour RGB

        // tracé des formes détectées
        beginShape(); // début tracé forme complexe

        for( int j=0; j<blobs[i].points.length; j++ ) { // parcourt tous les points du pourtour du blob
            vertex( blobs[i].points[j].x, blobs[i].points[j].y ); // tracé des points de la forme
        }
        endShape(CLOSE); // tracé forme complexe

  */


       }







    } // ---- fin for blobs --------

  comptBall0=comptBall; // mémorise dernier valeur une fois comptage de balles terminé
  println("comptBall0="+comptBall0);

  return(imgOut); // renvoie image

} // fin fonction analyseImage()

//------------- Fonction d'arret de Processing ----

public void stop(){ // fonction d'arrêt de Processing

        cam1.delete(); // efface l'objet GScapture

        super.stop(); // obligatoire

} // fin fonction stop()


//------------ gestion évènement clavier ---------

void keyPressed() { // si une touche est appuyée

        if(key==' ') { // si touche espace enfoncee

            direTexte ("Je vais compter les balles jaunes ou oranges...");

             if (comptBall0>=10) {
               direTexte("Ou la la ...");  
               direTexte("Il y en a beaucoup...");
               direTexte("Bon, je me lance !");
             }

            //---- réalise le comptage vocale
            for (int i=1; i<=comptBall0; i++) {
                 direTexte(""+i);
            }

            if (comptBall0==0)  direTexte("Il n'y a aucune balle jaune ou orange! Trop facile !");

            else if (comptBall0==1)  direTexte("J'ai compté une Balles jaunes ou oranges!");

            else direTexte("J'ai compté" + comptBall0 + "Balles jaunes ou orange!");
        }


} //--- fin si touche enfoncee

//------------- fonction d'appel à espeak par ligne de commande -----

void direTexte(String texte) {
  // le paquet espeak doit etre installé sous Ubuntu (depuis la logithèque)

String[] command = new String[8];
// espeak -v fr -s 140 « lecture texte espeak »
// espeak -v fr -s 80 -p 30 \"moteur droit en marche avant\"
command[0] = "espeak";
command[1] = "-v";
command[2]="fr+m4";
// les voix sont dans /usr/share/espeak-data/voices/!v
// les variantes dispo sont : croak  f1  f2  f3  f4  fast  klatt  klatt2  klatt3  m1  m2  m3  m4  m5  m6  m7  whisper
// pour utiliser une variante faire :  espeak -ven+m3
command[3]="-s"; // la vitesse
command[4]="100"; // entre 30 et 200
command[5]="-p"; // la tonalité
command[6]="40"; // entre 0 et 99
command[7]="\""+texte+"\""; // le texte entre guillemets

// param bien : voix m4/vitesse 120/ tonalité =40
// param bien : voix m2 / vitesse 100 / Tonalité = 20

//--- exécution de la ligne de commande
try {
    Process p = exec(command); // exécute la commande

    //--- récupère la sortie de la commande dans la console de Processing
    BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
    String line = null;
    while ((line = in.readLine()) != null) {
      System.out.println(line);

    }

  }
  catch (IOException e) {
    e.printStackTrace();
  }  

}

//------------ fin fonction dire texte -------------

//XXXXXXXXXXXXXXXXXX Fin du programme XXXXXXXXXXXXXXXXX