Logo Mon Club Elec

Processing – OpenCV et reconnaissance visuelle : Suivi de balle et calcul coordonnées centre balle.

La reconnaissance visuelle est un domaine en pleine croissance qui offre de nombreuses possibilités dans le domaine de l’intelligence artificielle. Les technologies de traitement d’image et de vision par ordinateur sont de plus en plus utilisées pour résoudre des problèmes complexes. Dans cet article, nous allons nous concentrer sur l’utilisation de Processing et OpenCV pour le suivi de balle et le calcul des coordonnées du centre de la balle. Nous verrons comment ces technologies peuvent être utilisées pour résoudre ce problème et comment elles peuvent être appliquées à d’autres domaines.

Processing – OpenCV et reconnaissance visuelle : Suivi de balle et calcul coordonnées centre balle.

Processing – OpenCV et reconnaissance visuelle : Suivi de balle et calcul coordonnées centre balle. 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 (moyX10,moyY,moyX+10,moyY); // trait horizontal
        line (moyX,moyY10,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=heightyOut;
  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);

}

 

Noter cet article

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Archive Mon Club Elec

Articles populaires

Newsletter

Inscrivez-vous maintenant et bénéficiez d'un soutien continu pour réaliser vos travaux électriques en toute sécurité.