Vous trouverez la derni�re version de ce plugin sur la page de mes plugins Gimp.
R�mi Peyronnet - F�vrier 2002
Gimp est un logiciel de traitement d'image issu du monde libre, et de plus en plus employ�. OpenSource, il profite de nombreuses contributions, en particulier gr�ce � de nombreux plugins qu'il est possible de d�velopper.
Je me suis pos� la question de savoir quels avantages il �tait possible de tirer en manipulant les images dans une espace fr�quentiel, c'est � dire apr�s transformation de Fourier. J'imaginais en effet qu'il serait possible d'exploiter ce mode pour obtenir des effets int�ressants, en gommant une partie du spectre, en appliquant les effets classiques au spectre (blur, noise, ...) ou en cachant du texte dans le spectre (st�ganographie). La fa�on la plus simple pour r�pondre � toutes ces questions �tait d'exp�rimenter, voila donc pourquoi j'ai entrepris de r�aliser ce plugin.
Imaginant que cela serait plus int�ressant de travailler sur une transform�e de Fourier d'une image repr�sent�e en YUV plut�t qu'en RGB, j'ai �galement d�velopp� auparavant un plugin YUV.
I. 1. Choix de la biblioth�que de FFT
Il existe sur Internet de nombreux codes disponibles pour r�aliser une transform�e de Fourier, et ceci du plus simple au plus perfectionn�. Les programmes les plus simples (qui tiennent souvent en moins d'une centaine de lignes) ont souvent des performances m�diocres, et ne savent op�rer que sur des images aux dimensions �gales � une puissance de deux. Il est �videmment possible pour contourner cette limite de remplir par des z�ros de mani�re � arrondir � la puissance de deux sup�rieure.
Cependant, une librairie semble s'imposer dans ce domaine : FFTW comme Fastest Fourier Transform in the West. Cette biblioth�que est disponible sur de nombreuses plateformes. Outre ses performances incroyables, elle permet de s'adapter automatiquement � des tailles non puissances de 2. Elle propose aussi de nombreuses options, comme les transform�es r�elles, en place,... J'ai donc opt� pour cette librairie, d'utilisation relativement facile.
I. 2. Programmation d'un Plug-in GIMP
L'int�gration dans GIMP n'a pas �t� la chose la plus facile du projet, car la documentation est assez elliptique sur ce sujet. Heureusement, il existe beaucoup de plugins pour GIMP OpenSource, ce qui m'a permis de comprendre le fonctionnement normal d'un Plugin.
Le plugin poss�de un code d'initialisation, qui se charge d'enregistrer la fonction dans GIMP, en inscrivant par exemple une entr�e dans un menu. Lorsque ce menu est appel�, la fonction principale du plugin est appel�e, en donnant en param�tre un pointeur vers l'image. Ce plugin a besoin, pour effectuer la transform�e de Fourier, de conna�tre toute l'image en m�me temps. La m�thode optimis�e de traitement par blocs n'est donc pas possible. Pour effectuer ces op�rations, l'image est recopi�e int�gralement en m�moire, puis un deuxi�me tableau est allou�, pour effectuer la transform�e de Fourier.
II. Un probl�me majeur : la taille d'un octet
Cela parait ridicule � rappeler, mais un octet ne peut prendre que 256 valeurs diff�rentes. Il faut bien remarquer que lorsque nous effectuons la transformation de Fourier, nous travaillons sur un type de donn�es 'flottant', c'est � dire capable de stocker des nombres r�els avec une pr�cision honorable. Or l'image elle ne peut contenir dans un mode RGB uniquement 256 niveaux par plans de couleurs.
Le probl�me qui se pose avec la transform�e de Fourier est que les nombres en sortie ne sont pas du tout r�partis de la m�me fa�on en valeur. Nous avons en effet g�n�ralement de tr�s importantes valeurs pour les basses fr�quences, et des valeurs d�croissantes ensuite, et nous devons stocker ces r�sultats :
J'ai donc tent� plusieurs strat�gies. Pour tous les tests qui vont suivre, l'image source utilis�e est un simple carr� blanc, centr�, et l'illustration comporte soit l'image source, soit la transform�e de Fourier, et la transform�e inverse.
II. 1. Conversion brutale en un octet
Ma premi�re tentative a �t� de convertir brutalement le r�sultat en un octet. Le r�sultat fut vraiment atroce car, arrondi, 255.5 donne 0. En deuxi�me, j'ai donc arrondi en tronquant tout ce qui �tait sup�rieur � 128 � 255, et inf�rieur � -128 � 0. Le r�sultat fut meilleur :
Cela donne alors une premi�re id�e d'effet possible, trouv�e gr�ce � ce probl�me : un effet de pseudo d�tection de bords, simplement en effa�ant les basses variations de couleurs, donnant un r�sultat relativement artistique. Regardons en effet le r�sultat appliqu� sur une image un peu plus quelconque :
II. 2. Mise � l'�chelle avant conversion
L'id�e logique venant � la suite de cela est de mettre � l'�chelle en prenant la valeur maximum et en divisant toutes les valeurs par ce nombre avant de les stocker. Cette m�thode donne des r�sultats abominables, principalement car elle ne conserve que les tr�s basses fr�quences. Les fr�quences plus �lev�es sont ensuite toutes �limin�es car le r�sultat de la division est proche de z�ro et arrondi � z�ro. Cette m�thode est donc � abandonner. Voici un r�sultat :
La troisi�me �tape est de consid�rer une �chelle logarithmique. Pour tenir compte des valeurs n�gatives, nous utilisons la fonction suivante :
inline guchar get_glog (double d) { int i; i = (int) ( ((d>0)?log(d+1):-log(-d+1))*128 ); return (guchar) (i>128)?255:((i<-128)?0:i+128); }
Les r�sultats sont tr�s satisfaisants :
Pour pouvoir utiliser de fa�on optimale la plage 0-255 disponible, nous devons mettre � l'�chelle les valeurs logarithmiques, en divisant par la valeur la plus �lev�e. Cette valeur doit donc �tre stock�e pour pouvoir permettre le traitement inverse. N'ayant pas trouv� de m�thode satisfaisante, la version actuelle enregistre cette information dans le dernier pixel de l'image de la transform�e de Fourier.
Ce plugin est distribu� soit sous forme de fichier source � compiler, soit en un binaire pour windows, � placer dans le r�pertoire 'plugins' de GIMP. Il faut alors relancer GIMP, et les entr�es 'FFT Directe' et 'FFT Inverse' sont ajout�es dans le menu Filters/Rendu. Il suffit juste de cliquer sur une de ces entr�es pour lancer le plugin.
En sortie, l'image contient le r�sultat de la transformation.
III. 2. Ordonnancement des composantes
Aucun r�ordonnancement des composantes n'est effectu�. L'ordre est donc conforme � celui de la transform�e utilis�e. D'autre part, les parties imaginaires sont actuellement trait�es de la m�me mani�re que les parties r�elles. Une grande partie des progr�s � faire pour une prochaine version se situera donc dans ce domaine, pour rendre plus compr�hensible le r�sultat (de m�me que le centrage en 0).
L'ordre des composantes utilis� par FFTW est d�crit sur le sch�ma suivant :
Pour repr�senter le r�sultat d'une transformation de Fourier 2D, les quatre quadrants sont souvent invers�s, comme d�crit dans le sch�ma qui suit :
FFT et JPEG
Compression
St�ganographie
Retouche d'image
Visions Artistiques
Impossible de faire une transform�e de Fourier et de ne pas penser au JPEG. Le JPEG utilise en effet une DCT sur des blocs de 8x8 pixels, puis multiplie le r�sultat par une matrice dont le but est d'att�nuer les faibles fr�quences, pour que cette matrice contienne le plus de z�ros possibles et puisse se comprimer le mieux possible.
Un des freins majeurs � appliquer une transformation de Fourier de mani�re globale � l'image �tait la puissance requise. Maintenant que les processeurs sont beaucoup plus performants, et que des librairies puissantes et gratuites existent, cet argument est � revoir. Cependant l'approche par blocs est int�ressante, car dans les images des blocs sont souvent compos�s de couleurs proches. Etant donn� la taille des images actuelles, des blocs de 8x8 sont peut-�tre un peu petits, et il serait int�ressant de pouvoir augmenter leur taille, ou m�me de pouvoir pr�voir une taille variable selon les r�glages de l'utilisateur, ou selon la taille de l'image.
Essayez donc de compresser la transform�e de Fourier par ce plug-in d'une image dans un format non destructif. Vous pourrez constater que la r�duction est impressionnante. (11ko pour un fichier source de 300ko en jpeg) Cependant cette diff�rence s'explique tr�s simplement par l'importante alt�ration de la qualit� d'image qu'impose la quantification aux valeurs 0-255 d'un octet.
Une des questions initiale m'ayant pouss� � faire ce plug-in �tait de savoir s'il �tait possible d'utiliser l'espace fr�quentiel pour y int�grer des informations, et de tester leur r�sistance aux alt�rations de l'image.
Testons donc ceci sur une image. Une fois dans l'espace fr�quentiel, nous pouvons utiliser n'importe quel effet � notre disposition pour y int�grer l'information que nous voulons. Pour notre test, nous prendrons du texte, que nous inclurons avec un calque, en mettant par exemple la transparence � 6%, juste pour que le texte reste lisible, mais pour affecter le moins possible. Cependant lors de la transform�e inverse les d�g�ts sont consid�rables :
Il semblerait donc que la st�ganographie ne soit pas envisageable par cette m�thode. Cependant il existe s�rement des moyens plus efficaces et discrets de repr�senter l'information que comme nous l'avons fait, avec du texte qui affecte beaucoup de fr�quences d'un coup.
Il est tout � fait envisageable d'utiliser ce plugin pour retoucher des images, et gommer des d�fauts. Le plus classique est par exemple un filtre passe-bas, qui coupe les moyennes et hautes fr�quences. En pratique, il s'agit de gommer toutes ces fr�quences (c'est � dire toute l'image sauf les coins) avec du gris neutre (RGB=128,128,128).
Effets sp�ciaux
G�n�ration d'images
Ce plugin peut �tre utilis� pour appliquer des effets sp�ciaux aux images, soit pour modifier une image, soit pour en cr�er une depuis une page blanche. Tout d'abord il faut consid�rer que l'on est dans l'espace de Fourier, c'est � dire que le 0 est constitu� par du gris (RVB = 128,128,128). Les valeurs inf�rieures seront consid�r�es comme n�gatives, et sup�rieures comme positives. Il faut faire tr�s attention au pixel sensible permettant la normalisation pour la transform�e inverse. Celui-ci est situ� environ � droite de l'image � mi-hauteur. Si par hasard vous modifiez cette valeur le r�sultat peut �tre amusant. Par exemple, voici ce que donne la transform�e inverse apr�s avoir enregistr� la transform�e de Fourier en jpeg (qui a donc "liss�" le pixel, et lui a attribu� une valeur sup�rieure, surexposant l'image) :
D�tection de bords
Boursouflures
Pliss�
Gondol�
Tremblant
IV. 5. a. a. D�tection de bords
Il est possible de faire une d�tection de bord relativement artistique. Il s'agit en fait de profiter de la saturation des valeurs au dessus de 256 pour �liminer les basses fr�quences (de valeur plus �lev�e). Un plugin est d�di� � cette action. Voici deux r�sultats possibles :
Il est aussi possible de le faire � la main. Pour cela il suffit d'effacer (colorier par du gris RVB=128,128,128) les zones de basses fr�quences, c'est � dire en haut � gauche et en haut � droite.
Essayons d'appliquer un tr�s l�ger flou � l'image. Le d�calage entre les parties r�elles et imaginaires va �tre estomp�, et donc les phases modifi�es. Il en r�sulte un effet de "boursouflrues" de l'image :
En s�lectionnant l'outil doigt (qui correspond � "�taler et m�langer les couleurs existantes, un peu comme on �tale la peinture fra�che sur un tableau avec un doigt") et en tra�ant un trait oblique rapide en haut � droite, nous obtenons :
En ajoutant un petit rond dans le coin sup�rieur gauche, nous obtenons un effet de "gondol�" :
En �liminant les fr�quences moyennes et interm�diaires, c'est � dire en s�lectionnant l'ensemble de l'image sauf deux triangles en haut � gauche et en haut � droite, puis en remplissant cette zone de gris neutre (RGB=128,128,128), nous obtenons l'effet suivant :
IV. 5. b. G�n�ration d'images
Il est �galement possible de g�n�rer des images compl�tement nouvelles, et ceci est d'ailleurs un fonctionnement assez int�ressant du plugin. Pour ceci, il est recommand� de commencer avec l'image remplie de la couleur gris neutre (RGB=128,128,128), puis de travailler � partir de cela, en ajoutant des points en appliquant des effets,...
Voici un exemple tout simple, compos� de peu de points (obtenus soit avec l'outil pinceau, soit avec l'outil accentuer) :
Ceci peut �tre utile par exemple pour g�n�rer des motifs qui pourront �tre r�p�t�s, et servir de base � l'application d'autres effets.
Comme vous ave pu vous en rendre compte, ce plug-in est actuellement tr�s basique, et les m�thodes utilis�es massacrent beaucoup le r�sultat. Il sera donc bon, de proc�der � quelques am�liorations possibles :
A propos du support 48bits (16bits par canal de couleur pour un pixel), je me suis pos� la question de savoir � quel point cela am�liorerait les choses. J'ai donc cr�� un plugin (FFT 16b Simulation) qui simule une telle transformation en 16bits. Pour cela, j'effectue successivement la transform�e directe puis la transform�e inverse, en intercalant entre les deux le syst�me de codage qui simule la d�perdition li�e au stockage sur un mot (deux octets), soit l'utilisation d'une plage enti�re de 0 � 65535. Le r�sultat a �t� � la hauteur de mes esp�rances, puisque je ne note aucune diff�rence visible entre l'orignal et le r�sultat de la simulation :
/** * (c) 2002 R�mi Peyronnet * Plugin GIMP : Fourier Transform */ #include <stdio.h> #include <stdlib.h> #include <math.h> #include <time.h> #include <string.h> #include "gtk/gtk.h" #include "libgimp/gimp.h" // Uses the brillant rfftw lib #include <rfftw.h> /** defines ***********************************************************/ #define PLUG_IN_NAME "plug_in_fft" #define PLUG_IN_VERSION "Februar 2002, 1.0" /** Plugin interface *********************************************************/ void query(void); void run(char *name, int nparams, GimpParam *param, int *nreturn_vals, GimpParam **return_vals); inline guchar get_guchar (int i) { return (guchar) (i>=255)?255:((i<0)?0:i); } inline guchar get_gchar128 (int i) { return (guchar) (i>=(int)128)?255:((i<=(int)-128)?0:i+128); } inline guchar get_glog (double d) { int i; i = (int) ( ((d>0)?log(d+1):-log(-d+1))*128 ); return (guchar) (i>128)?255:((i<-128)?0:i+128); } inline double get_flog (int i) { double d; if (i > 128) { d = exp((i-128.0)/128.0)-1; } else { d = -exp(i/128.0)+1; } return d; } inline double abslog(double d) { if (d > 0) { return log(d+1); } else { return -log(-d+1); } } inline double absexp(double d) { if (d > 0) { return exp(d)-1; } else { return -exp(-d)+1; } } GimpPlugInInfo PLUG_IN_INFO = { NULL, /* init_proc */ NULL, /* quit_proc */ query, /* query_proc */ run /* run_proc */ }; MAIN() void query(void) { /* Definition of parameters */ static GimpParamDef args[] = { { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" }, { GIMP_PDB_IMAGE, "image", "Input image (unused)" }, { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" } }; static GimpParamDef *return_vals = NULL; static int nargs = sizeof(args) / sizeof(args[0]); static int nreturn_vals = 0; gimp_install_procedure( "plug_in_fft_dir", "Transform the image with the FFT", "This plug-in applies a FFT to the image, for educationnal or effects purpose.", "R�mi Peyronnet", "R�mi Peyronnet", PLUG_IN_VERSION, "<Image>/Filters/Render/FFT Directe", "RGB*", GIMP_PLUGIN, nargs, nreturn_vals, args, return_vals); gimp_install_procedure( "plug_in_fft_inv", "Transform the image with the FFT", "This plug-in applies a FFT to the image, for educationnal or effects purpose.", "R�mi Peyronnet", "R�mi Peyronnet", PLUG_IN_VERSION, "<Image>/Filters/Render/FFT Inverse", "RGB*", GIMP_PLUGIN, nargs, nreturn_vals, args, return_vals); } void run(char *name, int nparams, GimpParam *param, int *nreturn_vals, GimpParam **return_vals) { /* Return values */ static GimpParam values[1]; gint sel_x1, sel_y1, sel_x2, sel_y2, w, h; gint img_height, img_width, img_bpp, cur_bpp, img_has_alpha; GimpDrawable *drawable; GimpPixelRgn region; GimpRunModeType run_mode; GimpPDBStatusType status; gint progress, max_progress; gint row, col; rfftwnd_plan p; fftw_real * fft_real, v, m, mean, norm; fftw_complex * fft_complex; int fft_inv=0; if (strcmp(name,"plug_in_fft_inv") == 0) { fft_inv = 1; } *nreturn_vals = 1; *return_vals = values; status = GIMP_PDB_SUCCESS; if (param[0].type!= GIMP_PDB_INT32) status=GIMP_PDB_CALLING_ERROR; if (param[2].type!=GIMP_PDB_DRAWABLE) status=GIMP_PDB_CALLING_ERROR; run_mode = param[0].data.d_int32; drawable = gimp_drawable_get(param[2].data.d_drawable); img_width = gimp_drawable_width(drawable->id); img_height = gimp_drawable_height(drawable->id); img_bpp = gimp_drawable_bpp(drawable->id); img_has_alpha = gimp_drawable_has_alpha(drawable->id); gimp_drawable_mask_bounds(drawable->id, &sel_x1, &sel_y1, &sel_x2, &sel_y2); w = sel_x2 - sel_x1; h = sel_y2 - sel_y1; if (status == GIMP_PDB_SUCCESS) { guchar buf[]={128,128,128,128}; guchar * img_pixels; gimp_tile_cache_ntiles((drawable->width + gimp_tile_width() - 1) / gimp_tile_width()); gimp_progress_init("Apply Fourier transform..."); // Process gimp_pixel_rgn_init (®ion, drawable, sel_x1, sel_y1, w, h, FALSE, FALSE); img_pixels = g_new (guchar, w * h * img_bpp ); //printf("%d %d %p\n", w * h * (img_bpp / 8),img_bpp,img_pixels); gimp_pixel_rgn_get_rect(®ion, img_pixels, sel_x1, sel_y1, w, h); gimp_pixel_rgn_init (®ion, drawable, sel_x1, sel_y1, w, h, TRUE, TRUE); // FFT ! fft_real = g_new(fftw_real, (h+2) * w); //norm = w*h; norm = sqrt((double)w*h); if (fft_inv == 0) { max_progress = /*w*h*/img_bpp*4; } else { max_progress = /*w*h*/img_bpp*3; } progress = 0; for(cur_bpp=0;cur_bpp<img_bpp;cur_bpp++) { if (fft_inv == 0) { p = rfftw2d_create_plan(w, h, FFTW_REAL_TO_COMPLEX, FFTW_ESTIMATE | FFTW_IN_PLACE); for(col=0;col<w;col++) { for(row=0;row<h;row++) { fft_real[col*(h+2)+row]= (fftw_real) (double)img_pixels[(row*w+col)*img_bpp+cur_bpp] / 256; } //progress += h; //gimp_progress_update((double) progress / max_progress); } progress += 1; gimp_progress_update((double) progress / max_progress); rfftwnd_one_real_to_complex(p, fft_real, NULL); progress += 1; //progress += h*w; gimp_progress_update((double) progress / max_progress); m = 0; for(col=0;col<w;col++) { for(row=0;row<h;row++) { v = abslog(fft_real[col*(h+2)+row] / norm); if ( fabs(v) > m) { m = fabs(v); } } //progress += h; //gimp_progress_update((double) progress / max_progress); } progress += 1; gimp_progress_update((double) progress / max_progress); for(col=0;col<w;col++) { for(row=0;row<h;row++) { v = abslog(fft_real[col*(h+2)+row] / norm) / m; //v = fft_real[col*(h+2)+row] / norm / m; //img_pixels[(row*w+col)*img_bpp+cur_bpp] = get_gchar128( (int) ((v/m)*128) ); //img_pixels[(row*w+col)*img_bpp] = get_glog( (int)(v) ); img_pixels[(row*w+col)*img_bpp+cur_bpp] = get_gchar128( (int)(v*128.0) ); //printf ("%f %f %d ; ",fft_real[col*(h+2)+row] / norm, v, get_gchar128( (int)(v*128.0))); } //progress += h; //gimp_progress_update((double) progress / max_progress); } progress += 1; gimp_progress_update((double) progress / max_progress); img_pixels[(h*w/2-1)*img_bpp+cur_bpp] = get_guchar( m * 10 ); } else { p = rfftw2d_create_plan(w, h, FFTW_COMPLEX_TO_REAL, FFTW_ESTIMATE | FFTW_IN_PLACE); m = (float)img_pixels[(h*w/2-1)*img_bpp+cur_bpp] / 10; img_pixels[(h*w/2-1)*img_bpp+cur_bpp] = 128; // Elimine _grosse_ perturbation. //printf("%f \n",m); //getchar(); for(col=0;col<w;col++) { for(row=0;row<h;row++) { fft_real[col*(h+2)+row]= (fftw_real) absexp( ((float)img_pixels[(row*w+col)*img_bpp+cur_bpp]-128.0) / 128.0 * m); //printf ("%d %f ; ",img_pixels[(row*w+col)*img_bpp+cur_bpp], fft_real[col*(h+2)+row] / norm); //fft_real[col*(h+2)+row]=(fftw_real) (img_pixels[(row*w+col)*img_bpp+cur_bpp]-128) / 128.0 * m; //fft_real[col*(h+2)+row]=(fftw_real) get_flog(img_pixels[(row*w+col)*img_bpp]); } //fft_real[col*(h+2)+h]=0; //fft_real[col*(h+2)+h+1]=0; //progress += h; //gimp_progress_update((double) progress / max_progress); } progress += 1; gimp_progress_update((double) progress / max_progress); //fft_real[(w/2-1)*(h+2)+h-1]=0; rfftwnd_one_complex_to_real(p, (struct fftw_complex *) fft_real, NULL); //progress += h*w; progress += 1; gimp_progress_update((double) progress / max_progress); for(col=0;col<w;col++) { for(row=0;row<h;row++) { v = fft_real[col*(h+2)+row] / norm; img_pixels[(row*w+col)*img_bpp+cur_bpp] = get_guchar((int)( (double)v*256.0)); } //printf("%f ",v*256); //progress += h; //gimp_progress_update((double) progress / max_progress); } progress += 1; gimp_progress_update((double) progress / max_progress); } } rfftwnd_destroy_plan(p); g_free(fft_real); // Flush gimp_pixel_rgn_set_rect(®ion, img_pixels, sel_x1, sel_y1, (sel_x2-sel_x1), (sel_y2-sel_y1)); g_free (img_pixels); gimp_drawable_flush(drawable); gimp_drawable_merge_shadow(drawable->id, TRUE); gimp_drawable_update (drawable->id, sel_x1, sel_y1, (sel_x2-sel_x1), (sel_y2-sel_y1)); gimp_displays_flush(); } values[0].type = GIMP_PDB_STATUS; values[0].data.d_status = status; gimp_drawable_detach(drawable); }
Voici donc un plugin Gimp qui permet d'avoir un premier aper�u des possibilit�s d'une transform�e de Fourier d'une image. Il est encore tr�s loin d'�tre compl�tement fonctionnel, et ne le sera sans doute pas avant longtemps. Il peut cependant servir simplement pour mener quelques exp�riences sans pr�tention sur la manipulation et la transformation d'images, et l'application d'une FFT globalement � l'image et non localement comme dans le JPEG.
Pour terminer sur une note d'espoir, notons que l'�cueil actuel principal est la limitation des r�sultats � 256 valeurs, ce qui est tr�s destructif et handicapant pour la qualit� de l'image restitu�e. Cependant, avec le support prochain des pixels de 48 bits (3x16 bits) par GIMP, cette conversion ne sera plus limitante.