COMPUTER VISION I

tutorial de programmation
en processing

 

 

Blob détection

dessein

Ballon détection

Avant de passer à la blob détection, nous résumons à travers un code de détection du mouvement l'ensemble des matières des derniers cours.

Le code permet de manipuler des objet sur l'e cran à travers la détection du mouvement par la webcam. Ce code est une simplification d'un code de Andy Best

Au lieu d'utiliser le code de la dernier fois pour détecter le changement de pixels, nous utilisons la fonction AbsDiff() de OpenCV qui nous rend que les pixels qui ont changé entre deux frames. Ceci avec les différents filtre que nous avons abordé nous permet de rendre les pixels qui bouge en blanc.

import hypermedia.video.*;

OpenCV monOpenCV;
int w=640, h=480;

void setup(){
size(w,h);
monOpenCV= new OpenCV(this);
monOpenCV.capture(w,h);
}

void draw(){
monOpenCV.read();
monOpenCV.flip(OpenCV.FLIP_HORIZONTAL);
monOpenCV.absDiff();

monOpenCV.convert(OpenCV.GRAY);
monOpenCV.blur(OpenCV.BLUR, 3);
monOpenCV.threshold(10);
image(monOpenCV.image(), 0,0);
monOpenCV.remember(OpenCV.SOURCE, OpenCV.FLIP_HORIZONTAL);
}

Ensuite nous nous créons un class pour les objets à manipuler (des ballons), qui seront des images. Nous avons simplement besoin la position et la taille et une valeur l'acceleration. Pour le tester nous le faisons tomber d'en haut en bas.

import hypermedia.video.*;

OpenCV monOpenCV;
int w=640, h=480;

PImage ballonImg;
Ballon []ballons;

void setup(){
size(w,h);
monOpenCV= new OpenCV(this);
monOpenCV.capture(w,h);
ballons=new Ballon[20];
ballonImg=loadImage("Ballon1.png");
for (int i = 0; i< ballons.length;i++){
ballons[i]=new Ballon(int (random(0, width)), random(-400, 0), ballonImg.width, ballonImg.height, random(0.2,2));
}
}

void draw(){
monOpenCV.read();
monOpenCV.flip(OpenCV.FLIP_HORIZONTAL);
monOpenCV.absDiff();

monOpenCV.convert(OpenCV.GRAY);
monOpenCV.blur(OpenCV.BLUR, 3);
monOpenCV.threshold(10);
image(monOpenCV.image(), 0,0);
for (int i = 0; i< ballons.length; i++){
ballons[i].update();
}
monOpenCV.remember(OpenCV.SOURCE, OpenCV.FLIP_HORIZONTAL);
}

class Ballon{
int ballonX, ballonL, ballonH;
float acceleration, vitesse, ballonY;

Ballon(int x, float y, int l, int h, float acc){
ballonX=x;
ballonY=y;
ballonL=l;
ballonH=h;
acceleration=acc;
}

void update(){
vitesse+=acceleration;
ballonY+=vitesse;
image(ballonImg, ballonX, ballonY);
}}

 

Maintenant la partie la plus compliqué: faire en sorte que les ballons disparaissent quand ils rencontrent des pixels blancs. Pour faire cela, on copie une image avec les pixels blanc que nous analysons par la suite, aux endroits ou se trouve l'image du ballon. D'abord, nous examinons si l'image est dans la fenêtre, et puis si la brightness du pixel est plus grand que 127, donc blanc. S'il y a plus que 10 pixels qui bougent nous replaçons l'image en haut, en dehors de la fenêtre.

Nous remettons l'image de la caméra et utilisons l'image en noir et blanc juste pour l'analyse dans la class. Voici le code complet.

import hypermedia.video.*;

OpenCV monOpenCV;
PImage imageMouvement;
PImage ballonImg;
Ballon []ballons;
int w=640, h=480;

void setup(){
size(w,h);
monOpenCV= new OpenCV(this);
monOpenCV.capture(w,h);
imageMouvement= createImage( w, h,RGB );
ballons=new Ballon[20];
ballonImg=loadImage("Ballon1.png");

for (int i = 0; i< ballons.length;i++){
ballons[i]=new Ballon(int (random(0, width)), random(-400, 0), ballonImg.width, ballonImg.height, random(0.2,2));
}
}

void draw(){
monOpenCV.read();
monOpenCV.flip(OpenCV.FLIP_HORIZONTAL);
image(monOpenCV.image(), 0,0);
monOpenCV.absDiff();

monOpenCV.convert(OpenCV.GRAY);
monOpenCV.blur(OpenCV.BLUR, 3);
monOpenCV.threshold(10);

imageMouvement=monOpenCV.image();

for (int i = 0; i< ballons.length; i++){
ballons[i].update();

}
monOpenCV.remember(OpenCV.SOURCE, OpenCV.FLIP_HORIZONTAL);
}

class Ballon{
int ballonX, ballonL, ballonH;
float acceleration, vitesse, ballonY;

Ballon(int x, float y, int l, int h, float acc){
ballonX=x;
ballonY=y;
ballonL=l;
ballonH=h;
acceleration=acc;

}

void update(){
int NbPixelbouge=0;

for (int y =int(ballonY); y< int(ballonY)+(ballonH-1); y++){
for (int x =ballonX; x< ballonX+(ballonL-1); x++){

if ( x < width && x > 0 && y < height && y > 0 ){
if(brightness(imageMouvement.pixels[x + (y * width)]) > 127){
NbPixelbouge++;
}
}
}
}

if (NbPixelbouge>10){

ballonY=random(-300, 0);
image(ballonImg, ballonX, ballonY);
}
else{
vitesse+=acceleration;
ballonY+=vitesse;
image(ballonImg, ballonX, ballonY);
}
}

}

Blob detection

D'abord un bref description de ce qu'on appelle 'blob detection:

In the area of computer vision, 'blob detection' refers to visual modules that are aimed at detecting points and/or regions in the image that are either brighter or darker than the surrounding.

source : http://en.wikipedia.org/wiki/Blob_detection

Si on regarde l'architecture de la bibliothèque OpenCV, cad on regarde les class qui constitue la bibliothèque, pour cela on regarde dans le dossier des 'libraries' de processing. Attention, depuis la version 1.0, les bibliothèques supplementaires devront être placé dans le dossier des 'Sketches' (sur PC dans le dossier 'mes documents').

libraries/ OPENCV/source/java/hypermedia/video

On voit qu'il y deux fichiers, ou bien deux class Blob.java et OpenCV.java.

Dans un premier temps nous regardons la manière de la class Blob dans la librairie OpenCV et ensuite nous regardons ses différents paramètre.

la class Blob dans OpenCV

Pour instancier l'objet blob, on écrit

Blob[] blobliste = MonOpenCV.blobs (10, width*height/2, 100, true, OpenCV.MAX_VERTICES*4 );

Pour tous ceux qui ne sont pas très habitué à la Programmation Orienté Objet, cet commande peut paraître tordu. Avant qu'on s'occupe des arguments (10, width*height/2, 100, true, OpenCV.MAX_VERTICES*4) on clarifiera ce que signifie Blob[] blobliste = opencv.blobs(...) en terme de OOP.

Le première chose qu'on connaît est Blob[] blobliste qui signifie qu'on crée un liste qu'on nomme blobliste du type Blob. Blob est donc un class. Mais dans ce cas, on écrira du genre Blob[] blobliste = new Blob[10] pour crée un liste avec 10 emplacement pour le type Blob. En fait on peut instancier aussi un liste à travers un méthode, regardons ça avec un type primitif .

Nous créerons une méthode avec un array du type int comme valeur de retour. On utilise ensuite l'appel de cette méthode pour instancier la liste int []chiffres ou lieu d'écrire int []chiffre = new int [2];

void setup(){
int[] chiffres =numbers(5, 6);// on utilise l'appelle
println(chiffres);
}

int []numbers (int premNum, int secNum){
// un méthode avec un array du type int comme valeur de retour
int []ListeDeNumeros={
premNum, secNum };
return ListeDeNumeros;
// array comme valeur de retour
}

On résume: il est possible d'instancier un liste par un méthode tant que son valeur de retour est un liste. Dans ce cas, on peut instancier aussi un objet à travers un méthode tant que son valeur de retour est du même type (car un liste est un objet).

Dans cet exemple, on instancie un objet de type OpenCV de la manière traditionnel. Dans la class OpenCV se trouve un méthode qui a comme valeur de retour un objet du type Blob. Donc on instancie l'objet blob de la class Blob à travers la méthode Blob( on a raccourci les arguments pour une raison de lisibilité) qui se trouve dans la class OpenCV. C'est elle qui instancie l'objet blob. Les arguments de la méthode correspond en effet au argument du constructeur de la class Blob. Un peu tordu, l'avantage est que la class Blob 'reste fermé' et on accède seulement à travers la class OpenCV

void setup(){
OpenCV monOpenCV=new OpenCV();
Blob blob=monOpenCV.methodeBlob(1.2, true);
println(blobs);
}

class Blob{
Blob( float area, boolean isHole ){// constructeur
}
}

class OpenCV{
OpenCV(){
//le constructor qu'on ne s'occupe pas ici
}

Blob methodeBlob(float areaM, boolean isHoleM){
Blob blobi=new Blob(areaM, isHoleM);
return blobi;
}
}

On y presque. Revenons sur le depart

Blob[] blobliste = MonOpenCV.blobs (10, width*height/2, 100, true, OpenCV.MAX_VERTICES*4 );

La méthode ne s'appelle pas methodeBlob mais seulement blobs. Deuxième différence est qu'il s'agit d'un liste de type Blob, ce n'est pas un seule objet.


void setup(){
OpenCV monOpenCV=new OpenCV();
Blob []blobs=monOpenCV.methodeBlob(1.2, true);
}

class Blob{
Blob( float area, boolean isHole ){
// constructeur
}
}

class OpenCV{
Blob[]blobListe=new Blob[10];
//pour simplifier on fixe la liste a 10 emplacement
OpenCV(){
}

Blob[] méthode(float areaM, boolean isHoleM){
Blob bob=new Blob(areaM, isHoleM);
// on instancie l'objet bob de la class Blob
Blob[]blobListe=new Blob[10];
// on crée un liste de type Blob (simplifié)
blobListe[0]=bob;
//on met l'objet blobi dans la liste (simplifié)
return blobListe;
// et on retourne la liste
}
}

Voila ce qui donne un liste de type Blob instancie à travers la méthode blob de l'objet monOenCV qui appartient à la class OpenCV ufffff...
Blob[] blobliste = MonOpenCV.blobs (10, width*height/2, 100, true, OpenCV.MAX_VERTICES*4 );

 

Les arguments du blob

Quand on instancie la liste de blob, il faut renseigner les arguments dans la méthode. Voici les arguments:

1. minArea int : the minimum area to search for (total pixels)
2. maxArea int : the maximum area to search for (total pixels)
3. maxBlobs int : the maximim number of blobs to return
4. findHoles boolean : accept blobs fully inside of other blobs
5. maxVertices (optionnel) int : the maximum number of points used to define the contour

Les premiers deux arguments limite la recherche de blob à un minimum de pixels et un maximum de pixels. Ceci permet de 'filtrer ' du bruit. La boolean findHoles permet ou interdit de trouver des blobs dans un blob. Le dernier argument qui est optionnel définit le nombre de vertices qui OpenCV utilise pour dessiner le contour. Un vertice est un suite de coordonné, (minimum 3) qui définit un contour d'un forme. cf vertex
Par défaut, ce nombre est limiter à 1024, mais peut être augmenter avec quelques précautions. cf blobs

 

Les champs (fields)

La class blob a enfin un serie de propriété qui nous permet de savoir d'avantage sur nous blobs. Le code ensuite montre 3 images, le premiers est l'image de départ, le deuxième après le traitement d'image (il faut éventuellement adapté le threshold) sur lequel on cherche les blob's et le troisième est l'image restauré avec les résultat de recherche de nos blobs.

rectangle, centroid

D'abord on cherche des propriétés lié au coordonnées du blob: Il y a deux champs rectangle et centroid. Le premier indique le rectangle dans lequel se trouve le blob et le deuxième indique le centre du rectangle.

Dans Java il y a un objet qui s'appelle Rectangle. Il a les mêmes propriétés que notre fonction rect(), donc une position x, y, un largeur et hauteur. On instancie au nom de contourRect un objet Rectangle pour chaque blob dans notre blobListe. Pour acceder ensuite aux valeurs, stocké dans notre objet contourRect on utilise le dot syntax. C'est la même chose pour le

import hypermedia.video.*;

OpenCV monOpenCV;

int w=320;// largeur de captation
int h=240;
// hauteur de captation

void setup(){
size(w*3,h);
monOpenCV=new OpenCV(this);
monOpenCV.capture(w, h);
noFill();
stroke(255,0,0);
}

void draw(){
background(0);
monOpenCV.read();
image(monOpenCV.image(),0,0);
monOpenCV.blur(OpenCV.BLUR,13);
// traitement d'image pour le 2. image
monOpenCV.convert(OpenCV.GRAY);
monOpenCV.threshold(50);
image(monOpenCV.image(),w,0);
Blob[] blobListe = monOpenCV.blobs(20, w*h/3, 2, false);
// instanciation pour 3 objet de blob
monOpenCV.restore();
// restauration d'image
image(monOpenCV.image(),w*2,0);

translate(w,0);// on recherche dans le 2ème image
for (int i = 0; i<blobListe.length; i++){
// on regarde blob par blob
Rectangle contourRect = blobListe[i].rectangle;
// instanciation d'un objet du type Rectangle auquel on
//affecte les valeurs de champs rectangle de la class Blob
Point blobCenter= blobListe[i].centroid;// même chose pour le centre des Blob avec le type Point
rect(contourRect.x+w, contourRect.y, contourRect.width, contourRect.height);
// on accède au valeurs par la dot syntaxe
croix(blobCenter.x+w, blobCenter.y, 8);//dessine un croix au centre du blob
}
}

void croix(int posX, int posY, int taille){// methode pour dessiner un croix
strokeWeight(2);
stroke(0,255,0);
line (posX-taille/2, posY+taille/2, posX+taille/2, posY-taille/2);
line (posX-taille/2, posY-taille/2, posX+taille/2, posY+taille/2);
}

La creation d'un objet type Rectangle n'a pas été nécessaire, mais plus confortable, on aurait pu accèder directement aux propriétés de rectangle. ça aurait donné:

rect(blobListe[i].rectangle.x+w, blobListe[i].rectangle.y, blobListe[i].rectangle.width, blobListe[i].rectangle.height);

Area, lenght

Il y a plusieurs valeurs qui nous renseignent sur les tailles du blob. Le nombre de pixels area , le circonférence du blob, lenght (ne pas confondre avec le nombre de blob dans la liste des blobs - blobListe.lenght)

Pour afficher le nombre de pixel ainsi que sa circonférence, on se crée une font et l'afffiche à coté du rectangle.

Voici le code qui a changé, il faut d'abord créer dans le setup() un variable font du type PFont.

for (int i = 0; i<blobListe.length; i++){// on regarde blob par blob
float combienPix_InArea = blobListe[i].area;
float circonference = blobListe[i].length;
Point blobCenter= blobListe[i].centroid;
Rectangle contourRect = blobListe[i].rectangle;
noFill();
stroke(255,0,0);
rect(contourRect.x+w, contourRect.y, contourRect.width, contourRect.height);

fill(255);
text(combienPix_InArea, contourRect.x+w+contourRect.width, contourRect.y);//
affiche le nombre de pixel qui sont dans le blob
fill(50,50,255);
text(circonference, contourRect.x+w+contourRect.width, contourRect.y+20);
// affiche la circonférence du blob
}

Points

Les points ont été déjà abordé lors des arguments. Les points donne es coordonnées pour les vertices qui entourent le blob

Blob[] blobListe = opencv.blobs( 10, width*height/2, 50, true, OpenCV.MAX_VERTICES*4 );
for( int i=0; i<blobListe.length; i++ ) {
beginShape();
for( int j=0; j<blobListe[i].points.length; j++ ) {
vertex( blobListe[i].points[j].x, blobListe[i].points[j].y );
}
endShape(CLOSE);
}

 


Exercice