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.
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
- Procédure présentée sur la page : Centrage de la webcam sur une cible carrée de taille connue
Le programme
// 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);
}
Articles similaires:
- Processing – openCV : Suivi de balle colorée avec élimination des artéfacts par calcul de cohérence des paramètres de l’objet détecté.
- Processing GSVideo + openCV : Capture d’un flux webcam par GSVideo et reconnaissance et suivi d’objet (balle) avec openCV (tracking ball).
- http://web.archive.org/web/20210804223007/http://www.mon-club-elec.fr/pmwiki_mon_club_elec/pmwiki.php?n=MAIN.PYQTOpenCVFichiers
- Processing GSVideo + openCV : Capture d’un triple flux vidéo (3 webcams) par GSVideo et reconnaissance et suivi d’objet (balle) avec openCV (tracking ball) sur les 3 flux vidéos.
- 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.
Articles Liés
- Javascript : Graphique Dygraphs simple
Le Javascript est un langage de programmation très populaire et puissant qui permet aux développeurs…
- Javascript : Afficher 6 widgets graphiques fournis par une librairie graphique externe.
Le Javascript est un langage de programmation très populaire qui permet aux développeurs de créer…
- Javascript : Graphique Dygraphs : afficher date à partir unixtime
Le langage de programmation Javascript est très populaire et est utilisé pour créer des applications…