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 – OpenCV et reconnaissance visuelle : Suivi de balle et calcul coordonnées centre balle.

Explication

  • Ce programme réalise la détection d'une balle et extrait les coordonnées du centre de la balle au sein de la cible réelle en cm.
  • Ce programme se base sur le principe de centrage de la webcam sur une cible décrit ici : Centrage de la webcam sur une cible carrée de taille connue
  • Ce programme est une étape préparatoire à un programme de préhension d'objet par bras robotisé dans une zone cible.
  • Le centre de la balle est tracé et la valeur est affichée dans la console, en brut (pixels à l'écran) et en cm (position réelle sur la cible en cm)
  • En pratique, la précision obtenue est bonne, de l'ordre du 0.5 mm, sauf aux limites de la cible, de l'ordre de 1 ou 2mm en raison des aberrations chromatiques de l'objectif de la webcam.

Ressources utiles

  • librairie openCv

Procédure d'étalonnage

Le programme


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

/////////////// Description du programme ////////////
// Utilise un/des objets PImage (conteneur d'image .jpeg .gif .png et .tga)
// Utilise la librairie OpenCV de capture vidéo et reconnaissance visuelle

// ce programme réalise tout d'abord un traitement détecter l'objet de couleur voulu
// puis on utilise un calcul du ratio de l'aire de la forme / aire rectangle contenant
// pour améliorer la discrimination de l'objet
// le suivi d'objet final obtenu est très bon et les formes "aberrantes" sont ignorées
// les coordonnées du centre de l'objet sont récupérées et affichées dans la console



// XXXXXXXXXXXXXXXXXXXXXX ENTETE DECLARATIVE XXXXXXXXXXXXXXXXXXXXXX

// inclusion des librairies utilisées

import processing.serial.*; // importe la librairie série processing

import hypermedia.video.*; // importe la librairie vidéo et reconnaissance visuelle OpenCV
// cette librairie doit être présente dans le répertoire /libraries du répertoire Processing
// 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

// --- port Série ---
Serial  myPort; // Création objet désignant le port série

PImage img1, img2; // déclare un/des objets PImage (conteneur d'image)

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=2; // nombre images à prendre en compte avant mouvement
long moyX=0; // pour calcul moyenne X
long moyY=0; // pour calcul moyenne Y

//------ 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);

//--- variables globales utiles
int tailleCroix=50; // 1/2 largeur bras croix

//--- pour étalonner ces valeurs, poser une règle devant la webcam
// et lire sur l'image vidéo la largeur en cm vue par la webcam

float largeurImageCm=30.5; // largeur en cm de l'image capturée par la webcam
float hauteurImageCm=23.0; // hauteur en cm de l'image capturée par la webcam


float tailleZoneUtileCm=18; // taille de la zone centrale carrée de travail utilisée
// idéalement cette zone ne doit pas dépasser 2/3 de la taille totale de l'image car les bords de l'image sont déformés par l'objectif

float xPixel, yPixel, xCm, yCm; // variables utiles

float[] xPixelCoin=new float[4]; // tableau pour les x des coins de la zone
float[] yPixelCoin=new float[4]; // tableau pour les y des coins de la zone

//--- correspondance fenêtre pixel / image cm
float Xmin=0, Xmax=30.5; // abscisses limites du graphe
float Ymin=0, Ymax=23.0; // ordonnées limites du graphe
// pour un tracé équilibré, il faut que Xmin-Xmax=Ymin-Ymax


// 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(255,255,0); // couleur remplissage RGB
        stroke (255,0,0); // couleur pourtour RGB
        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
        //smooth(); // lissage des formes

        // --- initialisation fenêtre de base ---
        size(320*2, 240*2); // ouvre une fenêtre xpixels  x ypixels
        background(0,0,0); // couleur fond fenetre

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

        //------------- initialisation port série ----
        println(Serial.list()); // affiche dans la console la liste des ports séries
        // Vérifier que le numéro du port série utilisé est le meme que celui utilisé avec  Serial.list()[index]
        myPort = new Serial(this, Serial.list()[0], 115200); // Initialise une nouvelle instance du port Série
        //myPort = new Serial(this, "/dev/ttyACM0", 115200); // Initialise une nouvelle instance du port Série
        myPort.bufferUntil('\n'); // attendre arrivée d'un saut de ligne pour générer évènement série


//---- Initialisation des objets PImage ---

        //img1 = createImage(100, 100, RGB); // initialise l'objet PImage (conteneur d'image)
        //image(img1, 0, 0, width/2, height/2); // affiche l'image

        //img2 = loadImage("monimage.jpg"); // charge un fichier image dans l'objet PImage (conteneur d'image)
        // ce fichier doit être présent dans le répertoire du programme ou à l'emplacement indiqué
        //image(img2, 0, 0, width, height);// affiche l'image

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

        opencv = new OpenCV(this); // initialise objet OpenCV à partir du parent This
        opencv.capture(width,height,0); // initialise capture flux vidéo
        // width et height sont les valeurs de la taille de la fenêtre processing

//------------------ initialisation programme Arduino ----
        myPort.write("vitesse(005)\n");// fixe la vitesse de positionnement du servomoteur par l'Arduino


//------ calcul des coordonnées des coins de la zone utile centrée sur le centre de l'image ---

        //--- point angle sup gauche ---
        xCm=(largeurImageCm/2)-(tailleZoneUtileCm/2);
        println("xCm="+xCm);

        xPixelCoin[0]=map(xCm,0,largeurImageCm,0,width);
        println("xPixel 0="+xPixelCoin[0]);

        yCm=(hauteurImageCm/2)-(tailleZoneUtileCm/2);
        println("yCm="+yCm);

        yPixelCoin[0]=map(yCm,0,hauteurImageCm,0,height);
        println("yPixel 0="+yPixelCoin[0]);

        //--- point angle sup droit ---
        xCm=(largeurImageCm/2)+(tailleZoneUtileCm/2);
        println("xCm="+xCm);

        xPixelCoin[1]=map(xCm,0,largeurImageCm,0,width);
        println("xPixel 1="+xPixelCoin[1]);

        yCm=(hauteurImageCm/2)-(tailleZoneUtileCm/2);
        println("yCm="+yCm);

        yPixelCoin[1]=map(yCm,0,hauteurImageCm,0,height);
        println("yPixel 1 ="+yPixelCoin[1]);

        //--- point angle inf gauche ---
        xCm=(largeurImageCm/2)-(tailleZoneUtileCm/2);
        println("xCm="+xCm);

        xPixelCoin[2]=map(xCm,0,largeurImageCm,0,width);
        println("xPixel 2="+xPixelCoin[2]);

        yCm=(hauteurImageCm/2)+(tailleZoneUtileCm/2);
        println("yCm="+yCm);

        yPixelCoin[2]=map(yCm,0,hauteurImageCm,0,height);
        println("yPixel 2="+yPixelCoin[2]);

        //--- point angle inf droit ---
        xCm=(largeurImageCm/2)+(tailleZoneUtileCm/2);
        println("xCm="+xCm);

        xPixelCoin[3]=map(xCm,0,largeurImageCm,0,width);
        println("xPixel 3="+xPixelCoin[3]);

        yCm=(hauteurImageCm/2)+(tailleZoneUtileCm/2);
        println("yCm="+yCm);

        yPixelCoin[3]=map(yCm,0,hauteurImageCm,0,height);
        println("yPixel 3="+yPixelCoin[3]);


} // fin fonction Setup

// XXXXXXXXXXXXXXXXXXXXXX Fonction Draw XXXXXXXXXXXXXXXXXXXX

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


        opencv.read(); // lecture flux vidéo via OpenCV

        img1=opencv.image(); // récupère Image opencv dans Processing




        //traitement de l'image de capture openCV dans Processing

        image( img1, 0, 0 );   // affichage image video

        //----- 1°) application du "mixeur de canaux" avec sortie sur canal Rouge
            //---- coeff à appliquer
            float coefRouge=1; // 100% de rouge
            float coefVert=1.5; // 80% du vert
            float coefBleu=-2; // - 200% 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 ----

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

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

            float r = (red(pixels[i])) + (green(pixels[i])*coefVert) + (blue(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(pixels[i]); // la couleur verte

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

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

            }

          updatePixels();  // met à jour les pixels  

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

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

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

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

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

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

            }

          updatePixels();  // met à jour les pixels  

        //------ on applique filtre de seuillage ---
        filter(THRESHOLD,0.7); // 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  

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


        //--- on rebascule dans OpenCV ---
        opencv.copy(img2); // 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, width*height/4, 5, false, OpenCV.MAX_VERTICES*4 );


        // ---- recharge image vidéo non traitée ---
        opencv.read(); // lecture flux vidéo via OpenCV
        noTint();
        image( opencv.image(), 0, 0 );   // affichage image video


       // 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( 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



        //--------------- gestion centre pour servomoteur ----------
        // moyenne des X et des Y

        moyX=moyX+centreX;
        moyY=moyY+centreY;
        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);

           //println("Y = "+ centreY);
           //println("Total Y = "+ moyY);
           //println("comptImg="+comptImg);        
           moyY=moyY/(comptImg); // calcule moyenne centre X
           println ("Moyenne Y = "+ moyY);

           xCm=map(moyX,0,width,0,largeurImageCm)-(largeurImageCm/2);
           println ("X Cm= "+ xCm);

           yCm=map(moyY,0,height,0,hauteurImageCm)-(hauteurImageCm/2);
           println ("Y Cm= "+ yCm);



        //--- dessine croix centrale centre balle
        stroke(bleu);
        line (moyX-10,moyY,moyX+10,moyY); // trait horizontal
        line (moyX,moyY-10,moyX,moyY+10); // trait vertical

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



       }
       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 --------

        //--- dessine croix centrale
        stroke(rouge);
        line ((width/2)-tailleCroix,height/2,(width/2)+tailleCroix,height/2); // trait horizontal
        line (width/2,(height/2)-tailleCroix,width/2,(height/2)+tailleCroix); // trait vertical


        //---- dessine les points des angles par calcul ---

        stroke(jaune);
        fill(violet);

        for (int i=0; i<4; i++) {

                  ellipse (xPixelCoin[i],yPixelCoin[i],5,5);  // trace point      

        }        


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

} // fin de la fonction draw()

// XXXXXXXXXXXXXXXXXXXXXX Autres Fonctions XXXXXXXXXXXXXXXXXXXXXX

//pour les fonctions de conversion, on considère que le point 0,0 est le coin inf gauche de la fenêtre
// important dans le cas de y

//----- Conversion xCmtoPixel() -----

float xCmtoPixel(float xIn) {

  float xOut;
  xOut=map(xIn,Xmin,Xmax,0,width);
  return (xOut);

}


//----- Conversion yCmtoPixel() -----

float yCmtoPixel(float yIn) {

  float yOut;
  yOut=map(yIn,Ymin,Ymax,0,height);
  yOut=height-yOut;
  return (yOut);

}


//----- Conversion xPixeltoCm() -----

float xPixeltoCm(float xIn) {

  float xOut;
  xOut=map(xIn,0,width,Xmin,Xmax);
  return (xOut);

}

//----- Conversion yPixeltoCm() -----

float yPixeltoCm(float yIn) {

  float yOut;
  yIn=height+yIn;
  yOut=map(yIn,0,height,Ymin,Ymax);
  return (yOut);

}