Traitement d'images
Traitement d'images
1) but
Les objectifs du traitement d'images sont :
Sélectionner des images prises par la caméra;
Dans chacune des images,
Trouver la position ainsi que l'orientation du robot;
Trouver la position des objets à ramasser (cibles);
Trouver la position ainsi que les dimensions des obstacles;
Faire le traitement le plus rapidement possible pour palier à toutes les éventualités (nouvel obstacle dans le périmètre, imprécision des mouvements du robot, etc.).
2) Réalisation du projet
Le traitement : le code Visual C++
void Imaging::Create(CWnd *_hWnd)
La première fonction de traitement d'images est appelée dès l'ouverture de l'application : il s'agit de la fonction Create(CWnd *_hWnd). Cette fonction permet la création d'une fenêtre qui affichera les images prises à l'aide de la caméra. De plus, il enlève toute couleur des tampons (buffers) qui serviront à l'affichage des images traitées. Elle alloue aussi par défaut tous les tampons nécessaires aux applications (l'affichage, par exemple). Finalement, elle appelle la fonction AllocBuffer().
void Imaging::AllocBuffer()
C'est dans la fonction AllocBuffer() que sont alloués tous les tampons qui seront utilisés dans le traitement des images. C'est également dans cette fonction que sont définies les images (ou parties d'image) qui seront utilisées pour la reconnaissance de formes.
void Imaging::Affichage()
Ceci fait, on peut afficher l'image en traitement avec la fonction Affichage(). Dans le fonctionnement normal, la fonction Affichage() est appelée par la fonction Grab(). La seulement condition est qu'un tampon MilDisplay ait été alloué (dans la fonction Create). L'affichage est rendu impossible si un autre logiciel de Matrox fonctionne en même temps que l'application.
void Imaging::Grab()
La fonction Grab, appelée par un bouton du même nom dans l'interface, démarre l'acquisition et le traitement d'image. Un tampon à deux dimensions permettant la saisie d'une image est tout d'abord alloué. La fonction MdigGrab permet au numériseur spécifié d'échantillonner une image de la caméra et de placer cette image dans le tampon alloué précédemment. Les données du tampon sont par la suite copiées dans le tampon MilImage pour être utilisées ultérieurement. La fonction InitialUpdate est appelée pour le traitement de l'image et cette image traitée est par la suite affichée dans l'interface. Il ne reste plus qu'à libérer le tampon qui a été alloué spécialement pour cette fonction.
Traitement de la perspective : void Imaging::Perspect()
La caméra étant placée en angle dans le fond de la pièce, les images obtenues de cette dernière renferment un effet de perspective. L'effet de perspective cause certains problèmes pour la reconnaissance de formes(les objets loin de la caméra sont différents des objets plus près de la caméra) et la mesure des distances pour le positionnement des différents objets (obstacles, cibles et robot). Ces différents problèmes sont éliminés en traitant la perspective avec des fonctions avancées de traitement d'image dans le but d'éliminer cette dernière.
La fonction de traitement de perspective est basée sur la fonction MimWarp de MIL. Cette fonction peut opérer des transformées spécifiques autant que des altérations complexes. La fonction traite l'altération en associant chaque pixel du tampon de destination, (xd, yd), avec un point spécifique (pas nécessairement un pixel) dans le tampon source, (xs, ys). La valeur du pixel en (xd, yd) est déterminée par une interpolation autour des points sources associés. Dans le cas d'une transformation en perspective, les points de destination sont associés aux points sources à l'aide de " look-up tables " (LUTs).
Une matrice de 3X3 est utilisée pour produire une transformation de perspective qui lie un quadrilatère quelconque à un rectangle. Pour obtenir cette matrice de coefficients, il faut spécifier les coordonnées du quadrilatère et du rectangle de la figure précédente et les passer à la fonction MgenWarpParameter sous forme d'un tableau. Comme les coefficients n'ont pas besoin d'être sauvegardés, les LUTs sont générées lors du premier appel à la fonction MgenWarpParameter.
Perspec [0] = 127;
Perspec [1] = 42;
Perspec [2] = 501;
Perspec [3] = 45;
Perspec [4] = 615;
Perspec [5] = 294;
Perspec [6] = 18;
Perspec [7] = 293;
Perspec [8] = 0;
Perspec [9] = 0;
Perspec [10] = 639;
Perspec [11] = 479;
MbufPut1d(PArray,0,12,Perspec);
MgenWarpParameter(PArray, XLutBuf, YLutBuf, M_WARP_4_CORNER + M_FIXED_POINT+6, M_DEFAULT, 0.0, 0.0);
Les LUTs étant générées, on peut maintenant les passer à la fonction MimWarp. Le mode d'interpolation utilisé est le " Nearest-neighbor ". Ce mode détermine la plus proche valeurs associée à un point et copie cette valeur à sa position assignée. Ce mode d'interpolation est en général le plus rapide.
MimWarp(MilSrcImage, MilSrcImage, XLutBuf, YLutBuf, M_WARP_LUT + M_FIXED_POINT+6, M_NEAREST_NEIGHBOR);
Pour terminer la transformation, on ajoute une opération pour permettre de fixer les bonnes dimensions du rectangle. Ainsi, l'image passe 640X480 pixels à une image de 360X480 pixels. Pour faire cette modification sur les dimensions, l'interpolation bilinéaire est utilisée. Les dimensions de l'image sont par la suite fixées pour être utilisées par d'autres fonctions.
MimResize(MilSrcImage, MilDestImage, (double)360.0/639.0, (double)1, M_BILINEAR);
SizeX = 360;
SizeY = 480;
Un exemple de résultat obtenu du traitement de perspective est illustré dans la figure ci-dessous.
void Imaging::FindRobot()
La fonction FindRobot() a pour utilité de rechercher le robot RugWarrior dans l'environnement créé pour lui dans le local 3120 et d'informer le reste du programme de sa position et son orientation. Pour cela, une reconnaissance de forme est réalisée à partir d'un modèle. Étant donné que le RugWarrior est plutôt uniforme et que son orientation n'est pas très discernable à la caméra, la reconnaissance de forme est faite sur une flèche installée sur le robot. Ainsi, en retrouvant la flèche, il est facile de connaître la direction et l'emplacement du robot.
Afin de faciliter la reconnaissance de forme, FindRobot() fait appel aux fonctions de la librairie MIL (Matrox Imaging Library). Grâce à la fonction MpatAllocModel(), on spécifie le modèle à utiliser à partir d'un segment d'image prédéfini.
Afin d'accélérer le traitement, la précision sur le positionnement est fixé à ±0.20 pixels en utilisant MpatSetAccuracy() et la variable M_LOW. Dans le même but, la fonction MpatSetSpeed() est fixée à M_HIGH, ce qui permet à l'algorithme de traitement de prendre tous les raccourcis raisonnables afin de retrouver le modèle dans l'image saisie.
Étant donné que le robot peut être positionné n'importe où dans l'image et que sa direction n'est pas connue au départ, il faut fixer les paramètres de recherche de telle sorte que le robot soit retrouvé, quelque soit son orientation sur 360° . Pour ce faire il existe la fonction MpatSetAngle() qui défini les paramètres de la recherche angulaire et la rend disponible. Encore une fois, par souci de rapidité d'exécution, la recherche ne s'effectue pas directement sur 360° . Elle est plutôt exécutée sur un intervalle de ±90° , en partant de sa dernière position, sachant que le robot n'effectuera pas de rotation de plus de 90° par itération. La nouvelle orientation est alors calculée pour la prochaine itération.
Finalement, les résultats de la recherche sont emmagasinés dans la liste de résultats Result.
void Imaging::FindCibles()
La fonction FindCibles() a été conçue de façon similaire à la fonction FindRobot(). Les cibles sont les objets que le RugWarrior doit trouver et ramasser dans son environnement. Puisqu'il n'est pas pertinent de trouver l'orientation des cibles, les cibles qui ont été choisies sont de forme circulaire. L'algorithme de recherche n'a donc pas besoin de considérer la recherche angulaire, ce qui améliore beaucoup la vitesse de recherche (comparativement à celle du robot).
Encore une fois, afin de faciliter la reconnaissance de forme, FindCibles() fait appel aux fonctions de la librairie MIL. Grâce à la fonction MpatAllocModel(), on spécifie le modèle de cible à utiliser à partir d'un segment d'image prédéfini. Il est donc possible, par la suite, de changer le modèle de cible, à notre gré.
Afin de ne pas être déjoué par le bruit de l'image et les différentes taches du plancher, la précision sur le positionnement est fixée à ±0.05 pixels en utilisant MpatSetAccuracy() et la variable M_HIGH. Dans le même but, la fonction MpatSetSpeed() est fixée à M_LOW, ce qui permet à l'algorithme de faire un traitement plus minutieux afin de retrouver le modèle dans l'image saisie. Étant donné qu'il est possible que plusieurs cibles soit présentes dans l'aire de jeu de RugWarrior, la fonction MpatSetNumber() doit être utilisée avec la constante M_ALL afin de retrouver toutes les cibles.
Enfin, les résultats de la recherche sont emmagasinés dans la liste de résultats ResultCible, afin de les utiliser ultérieurement.
void Imaging::FindGravityCenter()
La fonction FindGravityCenter() est la fonction qui permet d'identifier les obstacles que le RugWarrior doit éviter. Étant donné que les obstacles ne sont pas de forme prédéfinie et que leur orientation n'est pas précisée, la reconnaissance de forme ne peut s'appliquer. Une autre technique doit être utilisée, cette technique est appelée BLOB Analysis. Elle consiste à identifier les pixels connexes qui ont la même valeur logique, 0 ou 1.
Pour réaliser cette analyse, il faut tout d'abord mettre l'image saisie sous forme binaire, en pensant aux critères à respecter. Dans le cas présent, on doit identifier tout ce qui n'est pas le plancher donc, potentiellement, un obstacle. Pour ce faire, l'intervalle de couleur de plancher doit être établi, ce qui donne, dans ce cas-ci, [102;173] en ton de gris. La fonction MimBinaraze() permet de fixer à 1, grâce à la constante M_IN_RANGE, les couleurs de l'image qui sont dans l'intervalle spécifié. Ensuite, il est nécessaire d'éliminer les contours superflus et connus en écrivant des 1 dans l'image pour les couvrir afin qu'ils ne soient pas vus comme des obstacles. Ceci se fait à l'aide de la fonction MgraRect().
Ensuite, les fonctions MimClose() et MimOpen() éliminent les pixels seuls et regroupent les pixels qui forment une masse.
Une fois que les Blobs sont définis, on sélectionne ceux qui semblent bons, grâce à la fonction MblobSelect(). Les paramètres utilisés indiquent que tout amas de pixels plus grand que 250 est un obstacle. Il est ensuite possible de connaître les coordonnées de chacun de ces obstacles.
void Imaging::InitialUpdate()
La fonction InitialUpadate est le coeur de la librairie de traitement d'image. Cette fonction est appelée à chaque fois qu'un traitement sur une image est effectué. De plus, c'est elle qui appelle toutes les autres fonctions (Perspect, FindRobot, FindGravityCenter, etc.) nécessaires au déroulement du traitement. Ces différentes fonctions retournent des tableaux et variables qui seront utiles pour l'analyse des données (obstacles, cibles et robot).
Le traitement d'une image débute par le traitement de la perspective et se poursuit par une recherche du robot, des objets cibles et des obstacles, si le bouton traitement dans l'interface est enfoncé. Une analyse des variables retournées par la fonction FindRobot, en utilisant la fonction MpatGetResult, permet d'obtenir les coordonnées du robot ainsi que son orientation et le degré de ressemblance. Ces données sont utilisées pour tracer, sur l'image sans perspective, un rectangle noire vide sur ce qu'il a identifié comme étant le robot. Le même principe est utilisé pour l'analyse et le traçage des cibles.
Dans le cas des obstacles, l'analyse qui est effectuée permet d'obtenir un tableau dans lequel on retrouve l'ensemble des obstacles, mais également le robot qui a lui aussi été identifié comme un obstacle. Comme la position du robot a été préalablement analysée, il ne reste plus qu'à retrouver les coordonnées du robot dans le tableau des obstacles et les retirer de la liste. Les données de cette liste sont utilisées pour tracer, sur l'image sans perspective, des rectangles blancs vides autour des objets qui ont été identifiés comme étant des obstacles.
L'ensemble des variables et tableaux de données (cibles, obstacles et position du robot) générés par cette fonction sont par la suite utilisés pour les déplacements du robot et algorithmes de chemin le plus court.
Les petits à-côtés
On peut également ouvrir une image dans un format obtenu avec Intellicam avec la fonction OpenFile(). De plus, on peut sauvegarder une image à partir de l'application avec la fonction Save(). Pendant leur exécution, ces fonctions empêchent le grab. Elles sont accessibles par l'interface usager.
void Imaging::GrabEnd()
Cette fonction est utilisée pour arrêter l'acquisition d'images et le traitement sur ces dernières. Elle est appelée par un bouton du même nom placé dans l'interface.
En quittant l'application
Finalement, il faut désallouer tous les tampons, pour permettre à l'ordinateur de souffler un peu! On fait ceci avec le destructeur (Imaging::~Imaging()).
3) Conclusion : Les résultats
On réussit à déterminer avec une précision plus que suffisante la position ainsi que l'orientation du robot, la position des obstacles ainsi que la position des cibles. De plus, on peut repérer un obstacle qui arrive dans le périmètre après le début du grab. La seule difficulté est la dimension des obstacles. En effet, étant donné que ceux-ci sont détecter avec un certain angle, et qu'en plus, la perspective est éliminée, les dimensions qu'on voit ne sont pas les dimensions réelles de l'obstacle. Pour contrer ce problème, on ajoute un contour supplémentaire, un périmètre de sécurité, si on veut.
Ce qui ralenti le traitement est la recherche du robot, peu importe son orientation. Ceci est compréhensible puisqu'il faut parcourir l'image au complet plusieurs fois étant donné qu'on recherche la forme sous plusieurs angles différents. Malgré tout, le traitement s'effectue en moins de 2 secondes, ce qui est amplement suffisant pour nos besoins.
Ainsi, le traitement d'images fonctionne très bien.
|