Le projet consiste à développer des opérateurs de traitement d'images en C, en utilisant la bibliothèque Limace pour les entrées-sorties. Les opérateurs devront être regroupés dans une bibliothèque et les étudiants pourront alors s'échanger leurs travaux, chaque groupe faisant une série d'opérateurs différents.
Voici les différents opérateurs qu'il nous a été donnée d'implémenter, plus quelques uns par lesquels nous avons commencé, afin de prendre contact avec la bibliothèque Limace.
Si nous avions eu un peu plus de temps, nous aurions traité l'opérateur "érodée ultime" pour lequel il est nécessaire de compter le nombre d'objets présents.
Voici donc un opérateur permettant de compter le nombre d'objets d'une image binaire ou en niveaux de gris.
(Un objet est un ensemble connexe de pixels supérieurs à 0).
La programmation de cet opérateur est efficace, en une seule passe, itératif (sans récursivité sur les pixels voisins)
utilisant le masque antérieur de chaque pixel.
L'opérateur permettant de labelliser les objets étant un algorithme très similaire à celui pour compter,
nous l'avons aussi implémenté.
Le résultat d'un épaississement est une image où toutes les formes ont été propagées dans toutes les directions.
On procède d'abord à un remplissage des concavités, par un masque | 1 X X |
avec rotation du masque dans les 8 positions possibles où les pixels qui satisfont le masque sont mis à 1.
En effet, la méthode d'épaississement accentue les concavités, si elles ne sont pas traitées,
comme on peut le voir sur ces échantillons.
image d'origine | 6 itérations d'épaississement sans traiter les concavités | 6 itérations d'épaississement en remplissant les concavités de taille 1 à chaque itération |
Le traitement est efficace, mais visiblement, le traitement de concavités de taille supérieures à 1 pourrait être utile,
selon le choix de l'utilisateur.
Dans ce but, notre opérateur de remplissage de concavités peut être appelé séparément.
L'épaississement se fait par un masque | 1 1 1 |
avec rotation du masque dans les 8 positions possibles où les pixels qui satisfont le masque sont mis à 0.
Il y a huit parcours de l'image (un par masque),
car il n'a pas été possible d'appliquer les différents masques en parallèle.
L'amincissement, lui, peut être appliqué directement, sans se soucier des concavités par un masque | 1 1 1 |
avec rotation du masque dans les 8 positions possibles où les pixels qui satisfont le masque sont mis à 1. C'est l'inverse de l'épaississement. Là aussi, il y a huit parcours de l'image (un par masque), car il n'a pas été possible d'appliquer les différents masques en parallèle.
image d'origine | 6 itérations d'amincissement |
On voit que cet opérateur crée des extrémités. C'est une différence avec l'érosion, car l'amincissement ne rogne pas les extrémités.
Il y a plusieurs définitions correspondant au squelette.
Mais il nous a semblé important que notre squelette conserve les formes connexes,
car c'est une propriété du squelette fréquemment utilisée.
Notons que Aphelion ne conserve pas cette propriété, comme vu en TP.
Notre squelettisation fonctionne par amincissements successifs jusqu'à stabilité.
image d'origine | squelettisation |
On voit ici l'importance que l'amincissement ne supprime pas les extrémités.
Cette méthode est relativement efficace pour des formes minces,
mais requiert un grand nombre d'itérations sur des formes épaisses (½ diamètre).
Nos opérateurs sont regroupés dans une bibliothèque,
mais afin de pouvoir les tester, nous avons fait un programme capable d'interpréter un script,
et d'appeler les opérateurs correspondants.
L'appel se fait très facilement.
ex: D:\aphelinux>aphelie script.txt
Et voici un exemple de script :
//ma première variable Image image1 //ma deuxième variable Image im2 image1.loadFromFile("pcb.pbm") //charge l'image de ce nom image1.ImgCountObjects() //je compte le nombre d'objets de image1 image1.saveToFile("pcb2.pbm") //sauve l'image im2.loadFromFile("pcb2.pbm") image1.ImgEqual("im2") image1.ImgNot() image1.ImgEqual("im2") image1.free() //libère la mémoire allouée à image1 im2.ImgThinSkeleton() //applique la squelettisation sur l'image im2 im2.saveToFile("testSkeleton.pbm") im2.free()
L'interpréteur de script a été écrit en Lex & Yacc puis intégré au programme principal. Le choix de ces outils a été fait dans le but de pouvoir enrichir le moteur de script facilement a d'autres fonctions, et a d'autres fonctionnalités, tout en gardant beaucoup de flexibilité. En outre, le développement est beaucoup plus rapide que "à la main".
Les masques vus en cours pour l'amincissement et l'épaississement sont issus
d'une conversion de masques pour des matrices hexagonales.
Nous avons comparé plusieurs variantes et choisi des masques qui donnaient les résultats théoriques attendus.
Ce projet nous a permis de mieux comprendre les opérateurs vus en cours.
Nous avons ainsi un peu recollé avec l'implémentation,
et les problèmes d'optimisation cruciaux dans les lourdes fonctions de traitement d'image.
Cela a permis de nous sensibiliser à l'utilité d'un code propre, standard, multi-plateforme (notre programme a été compilé avec GCC, VCC, BCC sous Windows et Linux).
Vous pouvez consulter les sources (zip 122Ko)