Projet Traitement des cibles de fléchettes

Introduction

Dans le cadre de l'UE de "Introduction au traitement numérique des images", nous avons dû réaliser une application permettant de réaliser de la détection d'une cible d'un jeu de fléchettes, nous avons eu un moi afin de réaliser ce projet.

Pour traiter ce projet j'ai divisé mon travail en trois parties. La première a était de me concentrer sur le traitement de l'image afin dégager les informations nécessaires pour la deuxième partie qui est la reconnaissance de la cible. Enfin la dernière partie a été d'utiliser les données obtenues afin de créer une modélisation de la cible.

Cibles

Rendu obtenu avec l'image 1 tout automatiquement

Fonctionnement de l'application

Environnement

J'ai utilisé JAVA afin de réaliser ce projet, ainsi que l'IDE Eclipse avec le JRE 1.8. L'interaction avec l'utilisateur se fait à l'aide d'une interface graphique en JavaFX. J'utilise la classe BufferedImage de Java.awt pour réaliser les différents traitements.

Les couleurs

Pour interagir avec la classe BufferedImage j'ai créé une classe color qui permet de convertir dans la plupart des formats l'encodages des différentes couleurs. Elle permet aussi de réaliser quelques opérations comme l'ajout d'une couleur à une autre, la multiplication d'une couleur avec une autre et la multiplication d'une couleur par une variable.

Les composantes de couleurs sont stockées à l'aide de 3 composantes R, G et B correspondant aux couleurs Rouge, Vert et Bleu, ils sont représentés par des variables double comprises entre 0 et 1 afin d'indiquer le pourcentage d'intensité des composantes de couleur.

Il y a 2 constructeurs, le premier prend 3 variables double qui doivent être comprises entre 0 et 1 et sont juste initialisé. Le deuxième ne prend qu'un seul paramètre qui est un entier qui correspond à la formalisation du type TYPE-INT-ARGB qui code les 4 composantes A, R, G et B sur 32 bits (8 pour chaque composantes). Je récupère les différentes composantes à l'aide des manipulations de bits.

J'ai créé un constructeur avec TYPE-INT-ARGB car c'est le type de variable utilisé par la classe BufferedImage. Le constructeur permet de convertir le type retourner par la classe BufferedImage lorsque l'on repère un pixel, il a donc fallu créer une fonction qui permet de reconvertir dans une valeur TYPE-INT-ARGB. C'est la fonction toRGB qui permet de réaliser ce traitement.

Il y a deux autres fonctionnalités mineures que j'ai rajouté l'une permettant de retourné l'intensité de la couleur, et une qui retourne une couleur comprise entre 0 et 255 mais elle n'est pas utilisée car on ne peut pas utiliser certaines méthodes si elles sont supérieures à 1.

Traitement de l'image

Le but de cette première partie est de créer des fonctionnalités qui permettront d'améliorer les résultats obtenus par la partie de détection de la cible. Pour cela il nous faut donc créer des fonctions qui permettront de supprimer les détails inutiles et de révéler les zones importantes afin de pouvoir les détecter plus simplement. Je vais vous détailler chaque fonctionnalité que j'ai implémenté dans mon application (toute ne sont pas utilisé car elle ne donne pas un résultat satisfaisant).

Toutes les fonctionnalités sont contenues dans une classe Traitement qui possède deux variables importantes, ce sont deux BufferedImage une qui sert d'input aux fonctions de traitement et une qui sert d'output aux fonctions de traitement pour y mettre le résultat final (pour éviter de modifier des pixels dont on a besoin de garder la valeur initiale). J'ai créé une fonction appelé Copy permettant de copié le résultat output dans l'input.

Seuillage d'intensité

Le but de cette fonction est de transformer n'importe qu'elle image en noir et blanc. Elle prend une variable de seuil en entrée compris entre 0 et 1. Elle compare l'intensité de chaque pixel de l'image, si cet intensité est inférieur alors on attribue une la couleur noire au pixel si c'est supérieur ou égale la couleur blanche est attribuée au pixel.

Flou

Cette fonction de traitement d'image permet de réduire les différences entre les pixels proches d'une image. Elle fait partie des fonctions d'application d'un noyau sur l'image. Pour le filtre de flou il revient à faire la moyenne des pixels avoisinant le pixel choisi. J'utilise un noyau de taille 3*3 où tous les pixels ont le même poids.

Sobel

Afin de pourvoir réaliser une détection des contours des objets des images j'ai implémenter le filtre de Sobel. Dans le filtre de Sobel il y a 2 filtres, gx qui calcul les différences des pixels sur l'axe x et gy qui calcul les différences des pixels sur l'axe y. Le filtres fonctionne en calculant la différence entre les pixels de 2 côtés du filtre, cette différence sur les 2 filtres est ajoutée et appliquées aux pixels. On obtient donc une image qui contient l'intensité des variations au sein du filtre.

Fil de traitement

Afin de réaliser la reconnaissance de la cible il a fallu choisir une suite de fonction de transformation d'image qui pourront améliorer la reconnaissance de la cible. Dans mon cas j'ai d'abord utilisé un filtre de sobel afin de détecter les bords, ensuite une fonction de flou afin de diminuer le bruit qui a été détecté par le filtre de sobel, pour ensuite finir par un seuillage afin de supprimer encore une partie des détails et passer en image binaire noir et blanc.

Cependant un problème survient déjà dans cette partie, je n'ai pas réussi à trouver un fil de traitement qui permettent de réaliser un rendu correct pour toutes les images, car elles ont plus moins de détails, plus ou moins de bruits, etc...

Reconnaissance de la cible

Pour réaliser la reconnaissance des cibles je me suis focalisé sur les cibles qui sont face à la caméra afin de réaliser une détection plus simple et automatique de la cible. De plus à chaque étape de la reconnaissance de la cible, une partie des images qui ont subi la détection ne sont pas bien détecté, et donc ne fonctionne plus pour la suite des détections.

Reconnaissance de lignes

La première détection que j'ai réalisée est une détection de ligne grâce à la transformée de Hough, j'ai utilisé le code de Jamie Hutton et Ben Dowling. J'ai adapté se code afin de pouvoir détecter les 15 lignes les plus importantes de l'image, j'utilise ces 15 lignes afin de détecter le point le plus "intéressant" de l'image. Ce point est l'endroit où il y a le plus de lignes qui se croisent, dans le cas de la détection d'une cible, les lignes de la cible convergent tous en un point, le centre de la cible.

Cette première partie ne sert qu'à trouver le centre de la cible afin de pouvoir réaliser la suite des détections à partir de ce point, ce point est désigné par un point rouge sur l'image. Lors de cette partie, j'arrive juste à détecter le centre de la cible sur 8 images sur les 13 de bases.

A partir du centre de la cible je récupère la ligne passant par le centre ayant la valeur la plus grande, nous utiliserons cette ligne afin de trouver les autres de lignes de la cible en appliquant une rotation sur la ligne que nous avons trouvé. Pour trouver les 10 lignes qui composent la cible il faut appliquer une rotation de 18 degrés. Les 10 lignes sont affichées en vert sous l'image de droite.

Reconnaissance de cercles

Pour réaliser la transformée de Hough pour la détection des cercles j'ai de nouveau utilisé le code de Jamie Hutton et Ben Dowling. J'ai adapté ce code afin qu'il puisse détecter seulement les cercles qui ont pour centre le centre de la cible que nous avons détecté précédemment.

Pour cela nous réalisons une accumulation en un point pour tous les rayons possibles de l'image. Cette accumulation est bien plus rapide car elle ne s'applique pas sur toute l'image mais seulement sur un point de celle-ci.

De plus la fonction ne détecte que les 7 cercles qui forment les 7 anneaux d'une cible. Pour cela l'application détecte que les cercles qui sont maximum localement, c'est-à-dire que si 2 cercles ont un rayon proches l'un de l'autre ils ne pourront pas être affiché. Les 7 cercles sont affichés en rouge sur l'image de droite sur l'interface.

Cette méthode a deux problèmes majeurs, le premier est qu'il ne détecte que les cercles et non pas les ellipses, donc la cible doit être de face afin de bien pouvoir détecter les cercles. Le deuxième problème est que tous les rayons sont dans le même accumulateur, il est donc difficile de détecter les cercles avec un faible rayon car ils ont une valeur inférieure à ceux qui ont un grand rayon, la fonction est donc très sensible au bruit et aux différentes perturbations sur l'image.

A la fin de cette reconnaissance seulement 2 images sont reconnus de manière correcte sans aucune interaction de l'utilisateur afin d'aider la détection.

Modélisation des donnés

J'utilise deux fonctions de reconnaissances afin de récolter les données nécessaires pour construire la cible. Ce sont ces deux fonctions qui servent à construire la cible. L'objet cible est constitué de deux variable CentreX et CentreY qui désigne le point qui est au centre de la cible, une liste contenant 7 entier correspondant aux différents rayons de la cible, une autre liste contenant 20 entiers correspondant à l'angle entre un axe vertical passant par le centre de la cible et la droite, c'est 20 entiers correspondent aux différents quartiers de la cible, et une dernière liste qui associe a chaque quartier de la cible une valeur correspondant aux points du quartier.

L'objet cible contient une fonction importante getRegion qui prend en paramètre un point et retourne un objet Region. Cet objet contient deux variables correspondant aux rayon minimum et maximum de la région du point, et de même deux variables correspondant à l'angle minimum et maximum de la région. Ces quatre variables sont utilisées dans le cas où nous souhaitons dessiner la région ou pour comparer si deux points appartiennent à la même région etc... Par exemple j'ai réalisé une fonction permettant de colorier d'une même couleur tous les pixels appartenant à la région du point qui es passé en paramètre, c'est la fonction affRegionCible dans la classe traitement qui permet de réaliser cette opération.

Il y a aussi deux autres variables qui sont implémentés, une variable point et multiplicateur qui permettent de récupérer les points d'une région si une flèche la touche. C'est deux variables permettent de réaliser une fonction permettant de réaliser une partie. Pour cela j'ai implémenter une classe Partie prenant une cible en paramètre et contenant une méthode TireFleche prenant un point en paramètre. Cette fonction utilise et initialise trois variables de la classe partie permettant de sauvegarder les scores et les volées. Cette fonctionne respecte les règles du jeu de fléchettes 501 double out, des volées de 3 flèches, 501 points à réaliser, le but est tomber à 0 lors de la dernière flèche de volée en réalisant un double pour gagner.

Nous pouvons jouer une partie en cliquant sur l'image de droite, cependant il faut faire attention de ne pas générer qu'une seule image sinon il y a plusieurs évènements qui sont ajoutés. L'affichage du jeu se fait uniquement sur la console.