IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Millie : Bibliothèque de traitement d'images (C++)

Date de publication : 5/05/2007 , Date de mise à jour : 5/05/2007

Je vais présenter dans ce petit article la bibliothèque de traitement et d'analyse des images : Millie (Multifunctional Library for Image processing). Cette bibliothèque est toujours en cours de développement

I. Description du projet
II. Opérations actuellement disponibles
III. Installation et téléchargement
III-A. Compilation Sous Windows
III-B. Compilation sous Linux
III-C. Options de compilation
IV. Quelques exemples
IV-A. Chargement et sauvegarde d'images
IV-B. Convolution
IV-C. Passage dans le domaine HSL
V. Passage dans le domaine de Fourier
V-A. Allée et retour
V-B. Un filtre passe-bas
V-C. Un filtre passe-haut
V-D. Flou gaussien dans l'espace de Fourier
V-E. Flou de bougé
VI. Filtre médian
VI. Opérateur de Nagao
VIII. Filtres morphologiques
VIII-A. Erosion
VIII-B. Dilatation
IX. Ajout de bruit
IX-A. Bruit gaussien
IX-B. Bruit uniforme
X. Filtre de Perona Malik
XI. Au pays des threads
XI-A. Convolution multithreadée
XI-B. Perona Malik
XII. Exemples de création de filtres


I. Description du projet

Millie est une bibliothèque portable de traitement et d'analyse des images réalisée en C++ et fait pour ce langage. Cette bibliothèque est sous licence GPL. Cette bibliothèque ne compte pas révolutionner le genre, mais la conception de celle-ci avait été originalement faite afin que les personnes cherchant à implémenter ou à comprendre des algorithmes de traitement des images puissent s'inspirer de ces sources. La bibliothèque se veut simple à utiliser et simple à comprendre (ce n'est pas toujours le cas).

Actuellement, je suis en train de revoir un peu la conception pour permettre l'ajout de filtre multithread à partir de la version monothread le plus simplement possible.

Pour un fonctionnement optimal, la bibliothèque nécessite pour la compilation de disposer des bibliothèques suivantes : SDL (chargement et sauvegarde de BMP), SDL_Image (chargement d'autres formats), fftw (pour appliquer des transformées de Fourier), pthread (pour le multi-thread). Il est cependant possible de les désactiver.


II. Opérations actuellement disponibles

Pour l'instant, la bibliothèque, avec toutes les bibliothèques associées, permet de :

  • Charger des images BMP
  • Sauvegarder des images BMP
  • Sauvegarder des images JPG
  • Définir des noyaux de convolution
  • Appliquer des noyaux de convolution
  • Etendre les bords des images (extension constante, extension nulle, extrapolation simple)
  • Effectuer des rotations
  • Effectuer des flips horizontaux et verticaux
  • Conversion d'image RGB en HSL ou YUV (et vice et versa)
  • Filtre de Nagao
  • Filtre de Canny
  • Filtre médian
  • Seuillage binaire
  • Génération de bruit gaussien et de bruit uniforme
Elle permet également d'effectuer des opérations dans le domaine de Fourier :

  • appliquer une transformée de Fourier à une image
  • Appliquer des filtres dans l'espace de Fourier (flou gaussien, filtre passe-bas, passe-haut, passe-bas de type butterworth, passe-haut de type butterworth)
  • convertir le module d'une image complexe en une image normale
De nombreux opérations basées sur les équations de diffusion :

  • diffusion par la méthode de Tikhonov
  • diffusion par la méthode des hypersurface
  • filtre de choc (normal, gaussienne, complexe)
  • filtre de Perona et Malik (plusieurs implémentations possibles
Ainsi que quelques opérateurs morphologiques :

  • érosion
  • dilation
  • ouverture
  • fermeture
De plus, certains opérateurs ont également des équivalents multithreadés :

  • Filtres de Perona et Malik
  • Convolution

III. Installation et téléchargement

Vous pouvez vous procurer la version en cours (pas dit qu'elle compile :p) car je n'ai pas encore fait de version officielle stable à l'adresse suivante : http://subversion.developpez.com/projets/Millie/

Sous linux/Unix, il vous est possible de tout recupèrer via la commande : svn co http://subversion.developpez.com/projets/Millie/ . dans le repértoire désiré. Il est également possible de le récuperer de cette manière sous Windows en utilisant un client SVN.


III-A. Compilation Sous Windows

Pour windows, il y a un unique fichier Code::blocks qu'il vous faudra utiliser.


III-B. Compilation sous Linux

Sous linux, il vous faut effectuer les commandes suivantes :
 
./autogen.sh
./configure
make

III-C. Options de compilation

Il est possible d'effectuer quelques règlages lors de la compilation.

Ajouter le flag _MILLIE_WITH_SDLIMAGE_ (donc -D_MILLIE_WITH_SDLIMAGE_) pour activer la bibliothèque SDL_Image

Ajouter le flag _MILLIE_WITHOUT_SDL_ pour désactiver SDL. Si vous appelez alors une fonction qui utilisant SDL (par exemple pour le chargement des images), une exception sera lançée.

Ajouter le flag _MILLIE_WITHOUT_FFTW_ pour désactiver fftw.

Ajouter le flag _MILLIE_DANGEROUS_OPTIMIZATIONS_ pour ajouter des optimisations dangereuses. Par exemple, pour les buffers, il y a des tests d'accès hors zone et lancer d'exception le cas échéant. Vous pouvez désactiver ces tests et ces exceptions en activant ce flag.


IV. Quelques exemples


IV-A. Chargement et sauvegarde d'images

Voici un premier exemple qui montre le chargement et la sauvegarde d'images BMP en niveau de gris :
 
using namespace Millie;

int main (void)
{
  ImageGray im;

 try
 {
    ImageIO::loadBMPRedComponent(im, "lenna.bmp"); //chargement de la composante rouge
 }
 catch(Exception & e)
 {
   std::cerr<<e.what()<<std::endl;
   return EXIT_FAILURE;
 }

 try 
 {
   ImageIO::saveBMP(im, "extendc.bmp");
 }
 catch(Exception & e)
 {
   std::cerr<<e.what()<<std::endl;
   return EXIT_FAILURE;
 }

 return EXIT_SUCCESS;
}
Il est également possible de charger une image couleur JPG, par exemple :
 
using namespace Millie;

int main (void)
{
  ImageRGB im;

 try
 {
    ImageIO::loadJPG(im, "lenna.jpg"); //chargement de l'image
 }
 catch(Exception & e)
 {
   std::cerr<<e.what()<<std::endl;
   return EXIT_FAILURE;
 }

 try 
 {
   ImageIO::saveBMP(im, "extendc.bmp");
 }
 catch(Exception & e)
 {
   std::cerr<<e.what()<<std::endl;
   return EXIT_FAILURE;
 }

 return EXIT_SUCCESS;
}

IV-B. Convolution

Une convolution se fait en deux étapes. Une étape où l'on définie le noyau, et une étape où on appliquer la convolution. Par exemple, si l'on souhaite appliquer un filtre de Sobel :
 
using namespace Millie;

int main (void)
{
  ImageRGB im;

 try
 {
    ImageIO::loadJPG(im, "lenna.jpg"); //chargement de l'image
 }
 catch(Exception & e)
 {
   std::cerr<<e.what()<<std::endl;
   return EXIT_FAILURE;
 }

 /**
  * Le noyau de Sobel est défini par :
  *  -1 0 1
  *  -2 0 2
  *  -1 0 1
  */
 float fKernel[] = {
      -1,0,1,
      -2,0,2,
      -1,0,1};

 /*
  * premier argument : taille horizontale
  * deuxième argument : taille verticale
  * troisième argument : centre du noyau horizontale (commence à 0)
  * quatrième argument : centre du noyau verticale (commence à 0)
  */
 Kernel k(3,3,1,1, fKernel);
 
 /*
  * appliquer la convolution
  */
 convolveOperator(save, im, k);

 try 
 {
   ImageIO::saveBMP(save, "extendc.bmp");
 }
 catch(Exception & e)
 {
   std::cerr<<e.what()<<std::endl;
   return EXIT_FAILURE;
 }

 return EXIT_SUCCESS;
}
Voici un exemple :

Qui donne en résultat :

Evidemment, il est également possible d'appliquer la fonction convolveOperator sur des images en niveau de gris.

Vous devez savoir que lorsque l'on applique une convolution, il y a des problèmes sur les bords pour calculer les valeurs de la convolution. La fonction convolveOperator que l'on a vu tronque les bords. Pour parer le problème, il faut utiliser des BorderExtender
 

 KernelMDIF kernelMDIF; /*on utilise un noyau déjà défini*/
 BorderExtenderNull extenderNull;

 /*la convolution étant l'image im par du noir pour calculer la convolution sur les bords*/
 convolveOperator(save, im, kernelMDIF, extenderNull);
 
 BorderExtenderCopy extenderCopy;
 /*on extrapole les valeurs de im, utiliser un tel étendeur est conseillé*/
 convolveOperator(save, im, kernelMDIF, extenderCopy);
 
Voici un exemple avec KernelLaplace sur l'image du papillon :


IV-C. Passage dans le domaine HSL

Voici quelques exemples de passage dans le domaine HSL (Hue saturation light)
 
 /**
  * Modification de la saturation
  */
 ImageHSL hsl;
 RGBToHSL(hsl, im);

 hsl.addToSaturation(50); //ajoute 0.01 * 50 à toute la saturation

 HSLToRGB(save, hsl); 
 
 /**
  * Modification de la teinte
  */
 ImageHSL hsl;
 RGBToHSL(hsl, im);

 hsl.addToHue(80); //en degré

 HSLToRGB(save, hsl);

V. Passage dans le domaine de Fourier


V-A. Allée et retour

Voici un exemple d'un passage dans le domaine de Fourier. De conversion du module vers une image RGB et la transformée inverse.
 			
  ImageComplex c(3); //3 canaux
  ImageRGB module;

  DFT::forwardCompute(c, im);
  ModuleImageComplexToVisibleImage(module, c);

  DFT::backwardCompute(save, c);			

V-B. Un filtre passe-bas

Voici un exemple d'application de filtre passe-bas dans le domaine de Fourier
 	
  ImageComplex c(3); //3 canaux
  ImageComplex cOut(3);

  CMaskFactoryLowPass lowpass(50); //fréquence 50

  DFT::forwardCompute(c, im);

  cfilterOperator(cOut, c, lowpass);

  DFT::backwardCompute(save, cOut);

V-C. Un filtre passe-haut

 					
  ImageComplex c(3); //3 canaux
  ImageComplex cOut(3);

  CMaskFactoryHighPass highpass(40); //fréquence 40

  DFT::forwardCompute(c, im);

  cfilterOperator(cOut, c, highpass);

  DFT::backwardCompute(save, cOut);

V-D. Flou gaussien dans l'espace de Fourier

 					
  ImageComplex c(3); //3 canaux
  ImageComplex cOut(3);

  CMaskFactoryGaussianBlur gauss(0.1); //sigma=0.1

  DFT::forwardCompute(c, im);

  cfilterOperator(cOut, c, gauss);

  DFT::backwardCompute(save, cOut);

V-E. Flou de bougé

 					
  ImageComplex c(3); //3 canaux
  ImageComplex cOut(3);

  CMaskFactoryMotionBlur motion(0.1, 1,1); //vecteur vitesse (1,1), distance 0.1

  DFT::forwardCompute(c, im);

  cfilterOperator(cOut, c, motion);

  DFT::backwardCompute(save, cOut);

VI. Filtre médian

Voici l'image d'origine :

 			
 medianFilterOperator(save, im, 2); //rayon 2		
Voici un autre exemple. On applique 3 fois le filtre médian :
 			
 medianFilterIterativeOperator(save, im, 2, 3 ); //rayon 2, 3 itérations

VI. Opérateur de Nagao

Cet opérateur s'utilise très simplement :
 			
 nagaoOperator(save, im);

VIII. Filtres morphologiques

Les filtres d'érosions, de dilatation, de fermeture et d'ouverture sont implémentés. D'origine, le noyau choisi est de la forme 0,1,0,1,1,1,0,1,0


VIII-A. Erosion

 			
 erodeOperator(save, im);

VIII-B. Dilatation

 			
 dilateOperator(save, im);

IX. Ajout de bruit


IX-A. Bruit gaussien

 				
 gaussianNoiseGenerator(save, im, 200, 0); //variance 200, mean = 0

IX-B. Bruit uniforme

 				
 uniformNoiseGenerator(save, im, 100);

X. Filtre de Perona Malik

 				
 SimplePeronaMalikFilter filter;
 filter.compute(save, im, 5, 0.1, 20); //20 itérations		
Voici un exemple face au bruit gaussien précédent :
 	
 NeighbourPeronaMalikFilter filter;
 filter.compute(save, im, 5, 0.1, 50); //50 itérations
Il existe la version équivalente à base de Solver et de Flow.
 			
	NeighbourPeronaMalikFlow flow(lambda);
    PeronaMalikSolver solver;
    solver.compute(out, in, flow, dt, nbiter);

XI. Au pays des threads

Actuellement, seulement quelques opérateurs ont un équivalent multithreadé. J'ai effectué des tests de performances sur des stations Sun sous Solaris. Avec un bi-processeur et un quadri-processeur. Sur le bi-processeur, avec deux threads, les opérations ont tendance à être deux fois plus rapide, ce qui est trop bon. Sur le quadri-processeur, avec quatre threads, les opérations vont à peu près 3 à 3.5 fois plus vite. Ce qui est intéressant et non négligeable.


XI-A. Convolution multithreadée

 			
MTconvolveOperator(save, im, kernel); //version avec 2 threads
MTconvolveOperator(save, im, kernel, 4); //version avec 4 threads	

XI-B. Perona Malik

 	
 MTNeighbourPeronaMalikFilter filter(5); //5 threads
 filter.compute(save, im, 5, 0.1, 50); //50 itérations
Ou la version équivalente à base de Solver
 			
	NeighbourPeronaMalikFlow flow(lambda);
    MTPeronaMalikSolver solver(3); //3 threads 
    solver.compute(out, in, flow, dt, 50);

XII. Exemples de création de filtres

Voici un exemple de création du filtre de Flip vertical pour voir comment ça fonctionne :
 			
void Millie::verticalFlipOperator(Image& out, const Image& in)
{
  /*on vérifie que out et in ont le même nombre de canaux
   * sinon, l'opération n'a aucun sens, on lance une exception
   */
  if(out.getNumComponents() != in.getNumComponents())
    throw IllegalArgument("verticalFlipOperator");

  /*
   * on redéfini la taille de l'image de sortie
   */
  out.resize(in.getWidth(), in.getHeight());

  int largeur = out.getWidth();
  int hauteur = out.getHeight();

  /* on parcout tous les canaux et on effectue l'opération de flip*/
  for(int canal=0; canal< out.getNumComponents(); canal++)
    for(int j = 0; j< hauteur; j++)
      for(int i = 0; i< largeur; i++)
       out.setPixel(i,hauteur-j, canal, in.getPixel(i,j, canal));

}


Valid XHTML 1.1!Valid CSS!

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Florent HUMBERT. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.