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 |

Suivi de balle par une tourelle pan (1 servomoteur) à partir d'une interface Processing

Par X. HINAULT - Page créée le : 13/06/2011.

1.  Présentation

  • Un servomoteur connecté à la carte Arduino est contrôlé par une interface Processing qui réalise une reconnaissance d'objet coloré (une balle de ping pong colorée) à partir de la capture d'un flux vidéo et traitement d'image avec la librairie opencv. L'ensemble réalise un suivi d'objet en temps réel. En fait, ici seulement mouvement Pan.
  • L'interface Processing envoie sur le port série une chaine de caractères sous la forme servoPan(000) où 000 est la valeur de l'angle de positionnement du servomoteur.
  • Le programme Arduino analyse la chaine reçue et en extrait la valeur d'angle reçue
  • Le servomoteur est positionné dans la position voulue

Ce programme utilise les fonctionnalités suivantes :

  • Utilise 1 servomoteur

Ressources utiles associées à ce programme :

Pour info, j'ai utilisé ici :

  • Intel Dual Core Quad à 2.8Ghz
  • sous Ubuntu 10.04 LTS
  • webcam : Hercules DualPix
  • Processing 1.5
  • Arduino 22

2.  Schéma fonctionnel

  • Le programme Arduino est capable de reconnaître les chaînes suivantes en provenance de Processing :
    • servoPan(000) avec 000 = angle absolu en degrés pour un positionnement immédiat
    • servoPanto(000) avec 000 = angle absolu en degrés pour un positionnement progressif avec delai vitesse
    • servoPantoR(000) avec 000 = angle relatif en degrés pour un positionnement progressif avec delai vitesse
    • vitesse(000) avec 000 = delai en millisecondes à utiliser entre 2 positions intermédiaires (10 = rapide, 50 = lent)

3.  Matériel Nécessaire

3.1  L'espace de développement Arduino

  • ... pour éditer, compiler le programme et programmer la carte Arduino.

3.2  Le matériel électronique suivant pour réaliser le montage associé

3.3  Le matériel mécanique suivant pour réaliser la mécanique associée

  • une webcam connectée au PC et montée sur la tourelle pan (fixée sur la cage easy laissée vide) :

4.  Instructions de montage

  • Enficher la carte d'extension EasyCard broche à broche sur la carte Arduino
  • Connecter le servomoteur "pan" (rotation panoramique) sur la broche 8
  • Une webcam doit également être connectée au PC sur un port USB.
  • Dans le cas d'une carte Arduino :
    • l'intensité maximale disponible sur une broche est de 40mA
    • l'intensité maximale cumulée pour l'ensemble des broches est 200mA
    • l'instensité maximale que peut fournir l'alimentation 5V de la carte est 500mA.
  • Par conséquent, avec une carte Arduino :
    • En ce qui concerne la ligne de commande du servomoteur par la carte Arduino :
      • on pourra commander directement autant de servomoteur que l'on veut (broche de commande) avec une carte Arduino, le nombre maximal étant 200mA / I commande, soit 100 environ dans le cas du Futaba S3003, ce qui est largement supérieur au nombre de broches de la carte.
    • Il n'y aura par ailleurs pas besoin d'amplification (type ULN 2803) sur la ligne de commande du servomoteur même si l'on utilise un grand nombre de servomoteurs.
    • En ce qui concerne l'alimentation principale des servomoteurs par une carte Arduino
      • on ne peut alimenter que 3 à 4 servomoteurs simultanément par l'alimentation 5V de la carte Arduino, le nombre maximal étant 500mA / I fonctionnement = 500 / 120 = 4 servomoteurs dans le cas du Futaba S3003.
      • Une alimentation externe sera indispensable dès que l'on dépassera ce nombre pour ne pas risquer de "griller" la carte Arduino.

Pour plus de détails, voir : Principes d'utilisation des alimentations avec une carte Arduino et des servomoteurs

5.  Le schéma théorique du montage

Le schéma théorique du montage (cliquer pour agrandir)

6.  Le circuit du montage

Le schéma du montage à réaliser (cliquer pour agrandir)

7.  Explication du programme

  • L'interface Processing :
    • assure la reconnaissance de forme de la balle colorée
    • extrait la position de la forme dans l'image vidéo. On se base sur plusieurs images successives pour moyenner la position de l'objet et s'affranchir du "flou" lié au déplacement de la webcam.
    • envoie sur le port série une chaine de caractères sous la forme servoPantoR(000) où 000 est la valeur de l'angle de positionnement relatif du servomoteur.
  • Le programme Arduino analyse la chaine reçue et en extrait la valeur d'angle reçue
  • Le servomoteur est positionné dans la position voulue

8.  Mise en oeuvre du programme

8.1  Préparation du montage et programmation de la carte Arduino :

  • Commencer par réaliser le montage indiqué sur plaque d'expérimentation
  • Ensuite, programmer la carte Arduino avec ce programme (en bas de page) selon la procédure habituelle

8.2  Fonctionnement

  • le servomoteur Pan bouge automatiquement afin que la balle colorée reste au centre de l'image.

9.  Le programme complet en langage Arduino

A copier/coller directement dans l'éditeur Arduino


// --- Programme Arduino ---
// Trame de code générée par le générateur de code Arduino
// du site www.mon-club-elec.fr

// Auteur du Programme : X. HINAULT - Tous droits réservés
// Programme écrit le : 12/06/2011.

// ------- Licence du code de ce programme -----
//  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/>.

// ////////////////////  PRESENTATION DU PROGRAMME ////////////////////

// -------- Que fait ce programme ? ---------
 /*Un servomoteurs connectés à la carte Arduino est contrôlé
à partir du PC (interface Processing)

Controle par chaine de la forme :
* servoPan(000) avec 000 = angle absolu en degrés pour un positionnement immédiat
* servoPanto(000) avec 000 = angle absolu en degrés pour un positionnement progressif avec delai vitesse
* servoPantoR(000) avec 000 = angle relatif en degrés pour un positionnement progressif avec delai vitesse
* vitesse(000) avec 000 = delai en millisecondes à utiliser entre 2 positions intermédiaires (10 = rapide, 50 = lent)

*/


// --- Fonctionnalités utilisées ---

// Utilise / fonctionne avec une interface Processing coté PC
// Utilise 2 servomoteurs

// -------- Circuit à réaliser ---------

// ******* ATTENTION : il est possible de connecter directement 2 ou 3 servomoteurs sur la carte Arduino
// Connecter un servomoteur PAN sur la broche 8
// Connecter un servomoteur TILT sur la broche 9


// /////////////////////////////// 1. Entête déclarative ///////////////////////
// A ce niveau sont déclarées les librairies incluses, les constantes, les variables, les objets utiles...

// --- Déclaration des constantes ---

// --- Inclusion des librairies ---

#include <Servo.h> // librairie pour servomoteur

// --- Déclaration des constantes utiles ---
const int APPUI=LOW; // constante pour tester état BP

//--- Constantes utilisées avec le servomoteur
const int ANGLE_MIN=0; // angle position MIN en degrés
const int POS_MIN=550; // largeur impulsion pour position ANGLE_MIN degrés du servomoteur
                  // POS_MIN=550 pour ANGLE_MIN=0 avec un futaba S3003

const int ANGLE_MAX=172; // angle position MAX en degrés
int POS_MAX=2400; // largeur impulsion pour position ANGLE_MAX degrés du servomoteur
                  // POS_MAX=2400 pour ANGLE_MAX=172 pour futaba S3003

// pour étalonner un servomoteur, voir la page :
//http://www.mon-club-elec.fr/pmwiki_mon_club_elec/pmwiki.php?n=MAIN.ArduinoExpertSerieDepuisPCPositionServomoteur

// --- Déclaration des constantes des broches E/S numériques ---

const int broche_servoPan=8; // Constante pour la broche 8

// --- Déclaration des constantes des broches analogiques ---


// --- Déclaration des variables globales ---

 int octetReception=0; // variable de stockage des valeurs reçues sur le port Série
 long nombreReception=0; // variable de stockage du nombre  reçu sur le port Série
 long nombreReception0=0; // variable de stockage du dernier nombre  reçu sur le port Série
 String chaineReception=""; // déclare un objet String vide pour reception chaine

int valeur=0; // variable utile
long param=0; // paramètre transmis

float angleServoPan=90; // variable de position du servo  Pan en degrés

float angleServoPan0=90; // variable de la dernière position du servo  Pan en degrés

int vitesse=35; // variable utilisée pour délai entre 2 lecture port Série

// --- Déclaration des objets utiles pour les fonctionnalités utilisées ---

//--- Création objet servomoteur
Servo servoPan;  // crée un objet servo pour contrôler le servomoteur 1


// ////////////////////////// 2. FONCTION SETUP = Code d'initialisation //////////////////////////
// La fonction setup() est exécutée en premier et 1 seule fois, au démarrage du programme

void setup()   { // debut de la fonction setup()

// --- ici instructions à exécuter 1 seule fois au démarrage du programme ---

// ------- Initialisation fonctionnalités utilisées -------  

Serial.begin(115200); // initialise connexion série à 115200 bauds
// IMPORTANT : régler le terminal côté PC avec la même valeur de transmission

//--- Initialisation Servomoteur
servoPan.attach(broche_servoPan);  // attache l'objet servo à la broche de commande du servomoteur Pan
// se positione à 90° par défaut

// ------- Broches en sorties numériques -------  
 pinMode (broche_servoPan,OUTPUT); // Broche broche_servoPan configurée en sortie

// ------- Broches en entrées numériques -------  

// ------- Activation si besoin du rappel au + (pullup) des broches en entrées numériques -------  


// ------- Initialisation des variables utilisées -------  

// ------- Codes d'initialisation utile -------  

servoPan.writeMicroseconds(angle(angleServoPan)); // crée impulsion à partir valeur angle - plus précis que write()

Serial.println("Arduino OK"); // debug

delay(200);

} // fin de la fonction setup()
// ********************************************************************************

////////////////////////////////// 3. FONCTION LOOP = Boucle sans fin = coeur du programme //////////////////
// la fonction loop() s'exécute sans fin en boucle aussi longtemps que l'Arduino est sous tension

void loop(){ // debut de la fonction loop()

//---- code type réception chaine sur le port série ---
while (Serial.available()>0) { // tant qu'un octet en réception
        octetReception=Serial.read(); // Lit le 1er octet reçu et le met dans la variable

        if (octetReception==10) { // si Octet reçu est le saut de ligne
                Serial.print ("Chaine recue="),Serial.print(chaineReception); // affiche la chaine recue

                analyseChaine(chaineReception); // appelle la fonction d'analyse de la chaine en réception

                chaineReception=""; //RAZ le String de réception
                break; // sort de la boucle while
        }
        else { // si le caractère reçu n'est pas un saut de ligne
                chaineReception=chaineReception+char(octetReception); // ajoute le caratère au String
        }

} // fin tant que  octet réception

//----- une fois que le saut de ligne est reçu, on sort du While et on se positionne ici


//chaineReception="";

//------------- fin analyse chaine ---------------


delay(vitesse);


} // fin de la fonction loop() - le programme recommence au début de la fonction loop sans fin
// ********************************************************************************


// ////////////////////////// FONCTIONS DE GESTION DES INTERRUPTIONS ////////////////////


// ////////////////////////// AUTRES FONCTIONS DU PROGRAMME ////////////////////

//------------- fonction calibrage impulsion servomoteur à partir valeur angle en degrés

//------- mieux avec float -----
float angle(float valeur_angle) {

        float impuls=0;
        impuls=map(valeur_angle,ANGLE_MIN,ANGLE_MAX,POS_MIN, POS_MAX);
        return impuls;  

} // fin fonction impulsion servomoteur

//------------- fonction d'analyse de la chaine reçue sur le port série ----


//------------ analyseChaine ---------------
void analyseChaine(String chaineRecue) { // fonction d'analyse de la chaine recue

  // ---- analyse de la chaine recue sur le port Série ----
  chaineReception=chaineReception.trim(); // enlève les espaces

  //xxxxxxxxxxxxxxxxxxx instructions sans paramètres xxxxxxxxxxxx


  //xxxxxxxxxxxxxxxxxxxx instructions avec paramètres xxxxxxxxxxxxxxx

     // info : la valeur numérique extraite par testInstruction() est stockée dans variable globale param

  //================= instructions paramètres généraux =============

  //-------------- test instruction vitesse(xxx) -----------
  if (testInstruction("vitesse(")==true) { // si instruction reçue valide


    vitesse=param; // change valeur vitesse (= durée delay en ms)
    Serial.print("vitesse = "), Serial.println(vitesse);

  } // fin test vitesse(xxx)

  //================ instructions servo Pan  =========

  //-------------- test instruction servoPan(xxx) -----------
  if (testInstruction("servoPan(")==true) { // si instruction reçue valide

    servoPan.writeMicroseconds(angle(param)); // crée impulsion à partir valeur angle - plus précis que write()

    angleServoPan0=param; // mémorise angle actuel

  } // fin test genouD(xxx)


  //-------------- test instruction servoPanto(xxx) ----------- // positionnement progressif
  if (testInstruction("servoPanto(")==true) { // si instruction reçue valide

    // void servoTo( Servo toServo, float fromAngle, float toAngle, int toVitesse, int toPas)
    servoTo( servoPan, angleServoPan0, param, vitesse, 1); //--- positionnement progressif par pas fixe de 1 degré ---

    angleServoPan0=param; // met à jour l'angle courant servo  avec valeur extraite par testInstruction()

    Serial.print("angleServoPan0 = "), Serial.println(angleServoPan0);

  } // fin test genouDto(xxx)

  //-------------- test instruction servoPantoR(xxx) ----------- // positionnement progressif
  if (testInstruction("servoPantoR(")==true) { // si instruction reçue valide

    // void servoToR( Servo toServo, float fromAngle, float toAngle, int toVitesse, int toPas)
    servoToR( servoPan, angleServoPan0, param, vitesse, 1); //--- positionnement progressif par pas fixe de 1 degré ---

    angleServoPan0=param+angleServoPan0; // met à jour l'angle courant servo  

    Serial.print("angleServoPan0 = "), Serial.println(angleServoPan0);

  } // fin test servoPantoR(xxx)





} // ---------------- fin fonction analyse chaine ----------------------

//--------------- testInstruction : test si instruction de la forme instruction(xxx) ------------

boolean testInstruction(String chaineTest) { // reçoit chaine et renvoie true si instruction valide

  long posRef=chaineTest.length();// position de référence pour analyse (xxx)

  if (chaineReception.substring(0,posRef)==chaineTest) { // si reçoit l'instruction chaineTest(000)
  // nb substring : dernier caractere exclu

    Serial.print("Arduino va executer : ");

    Serial.print(chaineTest); // affiche

    if (chaineReception.substring(posRef,posRef+1)=="-") { // si signe - présent on décale de 1 position la prise en compte du nombre xxx

      param=(-1)*stringToLong(chaineReception.substring(posRef+1,posRef+4)); // extraction valeur 3 chiffres à partir caractères et * par -1
       posRef=posRef+1; // modif valeur posRef

    } // fin if

    else { // si pas de signe -

      param=stringToLong(chaineReception.substring(posRef,posRef+3)); // extraction valeur 3 chiffres à partir caractères

    } // fin else

    Serial.print(param); // affiche

    if (chaineReception.substring(posRef+3,posRef+4)==")") { // si fermeture parenthèse = instruction valide

      Serial.println(")"); // affiche
      Serial.println("Instruction valide !"); // affiche
      return(true); // renvoie true si instruction valide

    } // fin si fermeture parenthèse

    else {

      Serial.println("Instruction invalide !"); // affiche
      return(false); // renvoie false si instruction invalide

    } // fin else

  } // fin si reçoit l'instruction chaineTest(000)

} // fin fonction testInstruction  

//------------------- fin test instruction ------------


// ---------- fonction de conversion d'un String numérique en long

long stringToLong(String chaineLong) { // fonction conversion valeur numérique String en int

    long nombreLong=0; // variable locale
    int valeurInt=0; // variable locale

    for (int i=0; i<chaineLong.length(); i++) { // défile caractères de la chaine numérique

      valeurInt=chaineLong.charAt(i); // extrait le caractère ASCII à la position voulue - index 0 est le 1er caractère
      valeurInt=valeurInt-48; // obtient la valeur décimale à partir de la valeur ASCII  

     if (valeurInt>=0 && valeurInt<=9) { // si caractère est entre 0 et 9
       nombreLong=(nombreLong*10)+valeurInt;
     } // fin si caractère est entre 0 et 9


    } // fin for défile caractères

 return (nombreLong); // renvoie valeur numérique

} // ---------- fin stringToLong ------------


//--- fonction de positionnement progressif du servomoteur par pas fixe  -----

 void servoTo( Servo toServo, float fromAngle, float toAngle, int toVitesse, int toPas) {

       //--- positionnement progressif par pas fixe de 1 degré ---

    int delta=toAngle-fromAngle; // variation d'angle

    Serial.print("delta = "), Serial.println(delta);

    if (delta>=0) { // si variation positive

      for (int i=0; i<delta; i++) { // defile n positions pour atteindre angle final dans sens positif

        fromAngle=fromAngle+1; // ajoute cran
        toServo.writeMicroseconds(angle(fromAngle)); // crée impulsion à partir valeur angle - plus précis que write()
        //Serial.print("angle courant servo = "), Serial.println(fromAngle);
        delay(vitesse); // pause entre chaque positionnement

      } // fin for

    } // fin if

    else { // si variation négative

      for (int i=-delta; i>0; i--) { // defile n positions pour atteindre angle final dans sens négatif

        fromAngle=fromAngle-1; // ajoute cran
        toServo.writeMicroseconds(angle(fromAngle)); // crée impulsion à partir valeur angle - plus précis que write()
        //Serial.print("angle courant servo = "), Serial.println(fromAngle);
        delay(vitesse); // pause entre chaque positionnement

      } // fin for

    } // fin else

 }

//--- fonction de positionnement progressif du servomoteur par pas fixe - angle relatif à la position courante  -----

 void servoToR( Servo toServo, float fromAngle, float toAngle, int toVitesse, int toPas) {

       //--- positionnement progressif par pas fixe de 1 degré ---

    int delta=toAngle; // variation d'angle correspond à l'angle transmis

    Serial.print("delta = "), Serial.println(delta);

    if (delta>=0) { // si variation positive

      for (int i=0; i<delta; i++) { // defile n positions pour atteindre angle final dans sens positif

        fromAngle=fromAngle+1; // ajoute cran
        toServo.writeMicroseconds(angle(fromAngle)); // crée impulsion à partir valeur angle - plus précis que write()
        //Serial.print("angle courant servo = "), Serial.println(fromAngle);
        delay(vitesse); // pause entre chaque positionnement

      } // fin for

    } // fin if

    else { // si variation négative

      for (int i=-delta; i>0; i--) { // defile n positions pour atteindre angle final dans sens négatif

        fromAngle=fromAngle-1; // ajoute cran
        toServo.writeMicroseconds(angle(fromAngle)); // crée impulsion à partir valeur angle - plus précis que write()
        //Serial.print("angle courant servo = "), Serial.println(fromAngle);
        delay(vitesse); // pause entre chaque positionnement

      } // fin for

    } // fin else

 }


// ////////////////////////// Fin du programme ////////////////////
 

10.  Le programme Processing

10.1  Description

  • L'interface Processing :
    • assure la reconnaissance de forme de la balle colorée
    • extrait la position de la forme dans l'image vidéo. On se base sur plusieurs images successives pour moyenner la position de l'objet et s'affranchir du "flou" lié au déplacement de la webcam.
    • envoie sur le port série une chaine de caractères sous la forme servoPantoR(000) où 000 est la valeur de l'angle de positionnement relatif du servomoteur.

10.2  Ressources utiles

  • la librairie openCV de capture vidéo et reconnaissance visuelle

10.3  Le programme complet en langage Processing

A copier/coller directement dans l'éditeur Processing

// Programme processing
// généré avec le générateur de code Processing
// www.mon-club-elec.fr
// par X. HINAULT - Juin 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


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


// 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, 240); // 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,1); // 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

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

           //--- positionnement servomoteur ---
           if (moyX<((width/2)-15)) { // si on est vers la droite

                  myPort.write("servoPantoR(002)\n");// on incrémente la position de l'angle du servomoteur
           }

           if (moyX>((width/2)+15)) { // si on est vers la droite

                  myPort.write("servoPantoR(-002)\n");// on incrémente la position de l'angle du servomoteur
           }

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



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


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

} // fin de la fonction draw()

// XXXXXXXXXXXXXXXXXXXXXX Autres Fonctions XXXXXXXXXXXXXXXXXXXXXX


        //------------- Fonction de gestion des évènements série ----
         void serialEvent (Serial myPort) { // fonction appelée lors de la survenue d'un évènement série

        // ******** Gestion de la valeur reçue sur le port série : **********

        String inString = myPort.readStringUntil('\n'); // chaine stockant la chaîne reçue sur le port Série
        // saut de ligne en marque de fin

        if (inString != null) { // si la chaine recue n'est pas vide

                // print(inString); // affiche chaine recue

        } // fin condition chaine recue pas vide


} // fin de la fonction de gestion des évènements Série