\documentclass[a4paper,10pt]{article} % \listfiles % pour le debug \usepackage[french]{babel} \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} \usepackage{makeidx} \usepackage{listings} % \lstset{frame=single} % dessin d'un cadre autour du listing \lstset{basicstyle=\ttfamily\small} \lstset{aboveskip=0.333em,belowskip=0.666em} \usepackage{babel} \usepackage{graphicx} % for gnuplot ylabel rotate \usepackage{pifont} % caractères rigolos \usepackage{enumitem} \setitemize[1]{label={\ding{87}}} \frenchbsetup{CompactItemize=false} % \usepackage{color} % \usepackage{url} \usepackage{xspace} \usepackage[verbose]{layout} \setlength \parskip {0.333em} \makeatletter % exlpication de ce truc ? \def\verbatim@font{\normalfont\ttfamily\small} \makeatother \setlength{\hoffset}{0em} \setlength{\textheight}{640pt} \setlength{\textwidth}{422pt} \setlength{\marginparwidth}{10pt} \makeindex % ------ a few new commands \newcommand{\interparagraphe { \vspace{60pt} } } % ------------------------------------------------------------------- \title{Floating images processing} \author{tTh} \begin{document} \maketitle \section{Image flottante ?} Mais de quoi parle-t-on exactement ? % XXX XXX XXX\vspace{1em} Traditionnellement, les valeurs des pixels dans les images informatiques sont mémorisées sur 8 bits, un octet\index{octet}, soit 256 valeurs différentes. Ceci dit, on trouve parfois des images codées sur 16 bits par composante, mais c'est loin d'être le cas général. J'ai donc souhaité aller plus loin, et coder chaque canal de chaque pixel en virgule flottante sur 32bits, le type \texttt{float}\index{float} du langage C. Ce qui correspond à la norme IEEE 754-1985. % XXX XXX XXX\vspace{1em} Attention, tout le code que nous allons voir ensemble est en perpétuelle évolution\footnote{voir page \pageref{TODO}}, et sa fiablité (surtout sur certains aspects mathématiques) reste à démontrer\index{valgrind}. Mais le service après-vente est assez réactif. Du moins pour ceux qui suivent le canal \texttt{\#tetalab} sur le réseau IRC de Freenode. \textbf{Attention !} ce document commence par une bonne rafale de technique parfois hardue\footnote{gni?}. Vous avez parfaitement le droit de sauter directement à la page \pageref{outils} pour quelque chose de plus concret. % ------------------------------------------------------------------- \setlength \parskip {0em} \tableofcontents \pagebreak \setlength \parskip {0.40em} % \layout \pagebreak % ------------------------------------------------------------------- \section{Théorie}\index{théorie} Pour le moment, seule la quête de l'empirisme absolu a été visée. Les justifications mathématiques attendront le retour du schmod777. Ceci dit, rien ne nous empêche d'aller consulter Wikipedia~: \begin{quotation} An IEEE 754 32-bit base-2 floating-point variable has a maximum value of $(2 - 2^{-23}) \times 2^{127} \approx 3.4028235 \times 10^{38}$. All integers with 7 or fewer decimal digits, and any $2^{n}$ for a whole number $-149 \leq n \leq 127$, can be converted exactly into an IEEE 754 single-precision floating-point value. In the IEEE 754-2008 standard, the 32-bit base-2 format is officially referred to as binary32; it was called single in IEEE 754-1985. \end{quotation} Ce qui nous conduit à estimer qu'il est possible de cumuler environ quelques milliers d'images standard à 256 niveaux, sans trop avoir à se soucier des éventuelles pertes de précision. Mais ça demande à être confirmé par des esprits supérieurs. \subsection{Dynamique}\index{dynamique} Dynamique, précision et macheps. \subsection{Pixel négatif ?} Il est très difficle d'imaginer une lumière négative. % ------------------------------------------------------------------- \section{Premier exemple}\index{exemple}\label{exemple} \textsc{FloatImg} a débuté sous la forme de quelques fonctions basiques en C, gérant la structure des données d'image en mémoire et sur disque. Ça a été imaginé de façon presque empirique, mais nous sommes tous là pour améliorer les choses, dans la mesure de nos moyens. Nous allons donc directement rentrer au cœur du problème, en écrivant quelques lignes de code. Pour commencer par quelque chose de simple, nous allons créer une image RGB\index{RGB} complètement noire, puis l'enregistrer dans un fichier \texttt{.fimg}\index{.fimg}, un format complètement inconnu, puisque je viens de l'inventer à l'instant même. Tout d'abord, nous devons déclarer et garnir quelques variables pour gérer la machinerie interne. \begin{lstlisting} int width = 640, height = 480; char *fname = "exemple.fimg"; FloatImg fimg; \end{lstlisting} Ensuite, nous enchainerons trois étapes : création de l'image en mémoire centrale, initialisation des valeurs de chaque pixel à 0.0, et pour conclure, enregistrement dans un fichier\footnote{Au format ésotérique, mais très véloce.} binaire. \begin{lstlisting} foo = fimg_create(&fimg, width, height, FIMG_TYPE_RGB); if (foo) { fprintf(stderr, "create floatimg -> %d\n", foo); exit(1); } fimg_clear(&fimg); foo = fimg_dump_to_file(&fimg, fname, 0); if (foo) { fprintf(stderr, "dump fimg -> %d\n", foo); exit(1); } \end{lstlisting} Une fois ce code enrobé dans un \texttt{main()}, compilé et exécuté, nous pouvons entrevoir, grâce au logiciel \texttt{fimgstats} (voir page \pageref{fimgstats}), le résultat sous forme de chiffres divers, et/ou inutiles~: \begin{verbatim} $ ./fimgstats quux.img ----------- numbers from 'quux.img' : 640 480 3 0x7f3718c4f010 0x7f3718d7b010 0x7f3718ea7010 surface 307200 mean values: R 0.000000 G 0.000000 B 0.000000 A 0.000000 max value 0.000000 \end{verbatim} Nous avons donc sous la main une mécanique qui ne demande qu'à faire des trucs futiles et des images qui clignotent. La suite vers la page \pageref{codaz}. Vous trouverez dans le répertoire \texttt{tools/}\index{tools/} d'autres exemples de mise en œuvre des fonctions disponibles sous formes d'outils en ligne de commande, lesquels sont décrits en page \pageref{outils}. % ------------------------------------------------------------------- \section{Installation} \textit{Attention, ça devient un peu gore. Myrys, punk, toussa\dots} \subsection{Prérequis} Vous devez, en dehors des outils classiques (bash, gcc, make\dots), avoir quelques bibliothèques installées\footnote{Les \texttt{-dev} pour Debian et dérivées}~: \textsf{libv4l2, libpnglite, libtiff, libnetpbm, libz}, éventuellement avec le \textsf{-dev} correspondant, et probablement d'autres choses. Il est même quasiment certain que Bash soit indispensable, tout comme \textsc{gnu}/make\index{make}. Une connaissance de base de l'utilisation du shell\index{shell} et de l'écriture de Makefile's sera un plus. Il faut aussi savoir où trouver le code. \subsection{Compilation} Un script \texttt{build.sh} permet de construire approximativement le bouzin. Il est loin d'être parfait\footnote{Il doit être possible de faire un Makefile récursif, mais\dots}. Dans chacun des répertoires à traiter, ce script devrait trouver un Makefile et un fichier \texttt{t.c} qui est le source de la cible par défaut du make. Pour le moment, la procédure d'installation est un peu rude, pour ne pas dire clairement sommaire. Si le résultat de l'étape compilation vous semble correct, vous pouvez copier les deux fichiers \texttt{floatimg.h} et \texttt{libfloatimg.a} dans un emplacement approprié, par exemple \texttt{/usr/local/include} et \texttt{/usr/local/lib}. Le script \texttt{install.sh}, à la racine du projet, est censé faciliter un peu la chose. Il prend également en compte la copie des divers binaires du dossier \texttt{tools/} (cf page \pageref{outils}) dans le répertoire prévu à cet effet : \texttt{/usr/local/bin}. Il reste enfin quelques exemples d'utilisation des outils de la ligne de commande depuis un shell dans le répertoire \texttt{scripts/}. Ils sont décrits plus en détail page \pageref{scripts}, et c'est à vous de les adapter à votre \textsl{usecase}. Faites-en ce que vous voulez. % ================================================================= \section{Utilisation coté codeur}\label{codaz} Classiquement, il y a un fichier \texttt{.h} à inclure dans chacun de vos codes source, \texttt{floatimg.h}, généralement logé dans \texttt{/usr/local/include} contenant un certain nombre de définition de structures, de macros, de constantes\footnote{À l'ancienne, via le pré-processeur} et les prototypes des fonctions utilisables par vos logiciels. Au niveau du code source, ces fonctions sont approximativement classées en deux catégories : \texttt{lib/} et \texttt{funcs/}. La première contient les choses qui sont relativement figées, et la seconde celles qui risquent de bouger. Cette classification est en fait arbitraire. \subsection{Structures, macros\dots} Les pixels flottants d'une image résidant en mémoire centrale sont décrits par un ensemble de données (certains appelent ça des \textsl{metadatas}) regroupées dans une jolie structure que nous allons examiner dès maintenant. \begin{lstlisting} /* in memory descriptor */ typedef struct { int width; int height; int type; float fval; int count; float *R, *G, *B, *A; int reserved; } FloatImg; \end{lstlisting}\index{FloatImg} Les deux premiers champs sont \textsl{obvious}. Le troisième est le type d'image : pour le moment, il y en a trois qui sont définis\footnote{et plus ou moins bien gérés\dots} : gris, rgb et rgba\index{rgba}. Les constantes adéquates sont dans \texttt{floatimg.h} \begin{lstlisting} #define FIMG_TYPE_GRAY 1 #define FIMG_TYPE_RGB 3 #define FIMG_TYPE_RGBA 4 #define FIMG_TYPE_RGBZ 99 \end{lstlisting} Un peu plus loin, nous avons les pointeurs vers les différents \textsl{pixmaps} de l'image. En principe l'organisation interne de ces zones est improbable, puisque elle dérive d'idées approximatives. C'est cette utilisation constructive de larache qui fait que seuls les champs documentés de cette structure ne sont pas explosifs. Mais revenons aux choses sérieuses\dots Les deux champs suivants (fval et count) sont à la disposition du \textsl{yuser} qui peut jouer avec à loisir pour faire, par exemple, ce genre de chose : imaginons un périphérique de capture qui nous fournisse des images en gris sur 4 bits. Et que nous voulions cumuler\index{cumul} quelques images... Le champ \textsl{count} sera mis à 0 et le champ \textsl{fval} sera initialisé à 15.0 (qui est la valeur maximale que peut renvoyer le capteur). Ensuite, dans la boucle capture/cumul, \textsl{count} sera incrémenté à chaque passe, et nous aurons donc, en finale, toutes les informations nécessaires pour exploiter au mieux la dynamique de notre image dans les étapes ultérieures, puisque la valeur maximale théorique est égale à $fval * count$. La fonction \texttt{fimg\_printhead(FloatImg *h)} affiche sommairement le contenu de ce descripteur, et \texttt{fimg\_describe(FloatImg *head, char *txt)} propose un affichage plus détaillé. Ça aide parfois. Une bonne partie des fonctions que nous allons voir est indéterministe. Ce qui veut dire, en langage de tous les soirs, que ça risque de ne pas être la même chose dans l'avenir. % ---------------------------------- \subsection{Les fondations}\index{lib/} La première chose que nous devons absolument voir est la gestion dynamique de la mémoire qui sera occupée par tous ces pixels flottants, ce qui est un sujet parfois délicat\footnote{GC or not GC ?}. Elle est donc faite, à la base, par ces deux fonctions~: \begin{lstlisting} int fimg_create(FloatImg *fimg, int w, int h, int type); int fimg_destroy(FloatImg *fimg); \end{lstlisting} L'appelant doit lui-même gérer le descripteur d'image (une structure C décrite plus haut) en le considérant comme un type semi-opaque dont la forme peut varier. Certains membres de cette structure sont documentés dans ce document, et les autres sont dangereux à toucher. Les types d'images actuellement gérés sont les trois grands classiques : gray, rgb et rgba. et expliquées quelques lignes plus haut. Comme vous allez le voir plus loin, il y a plein de fonctions qui prennent en argument deux images: une source et une destination. \begin{lstlisting} int fimg_images_compatible(FloatImg *a, FloatImg *b); \end{lstlisting} C'est bien beau d'être enfin résident en mémoire centrale, mais pouvoir aussi exister à long terme en étant stocké dans la matrice est tout aussi pertinent. Il y a deux opérations qui supportent le reste des transits ram/ps. \begin{lstlisting} int fimg_dump_to_file(FloatImg *fimg, char *fname, int notused); int fimg_load_from_dump(char *fname, FloatImg *where); \end{lstlisting} Recharger une image depuis un fichier nécessite que celle-ci et l'image de destination en mémoire ait précisément les mêmes caractéristiques (taille, type...), donc l'image en ram doit être pré-allouée. On peut connaitre ces valeurs en appelant \texttt{int fimg\_fileinfos(char *fname, int datas[3])}. Si tout s'est bien passé (valeur retournée égale à 0), on va trouver la largeur dans \texttt{datas[0]}, la hauteur dans \texttt{datas[1]} et le type dans \texttt{datas[2]}\footnote{La fonction \texttt{fimg\_type\_is\_valid(int type)} peut vous aider\dots}. Je sais aussi que certains d'entre vous aiment la facilité, aussi je vais vous révéler l'existence d'un nouveau truc bien plus simple, une fonction qui enchaine ces deux actions (allocation, puis lecture), et s'utilise comme ça : \begin{lstlisting} FloatImg head; memset(&head, 0, sizeof(FloatImg)); foo = fimg_create_from_dump("lena.fimg", &head); \end{lstlisting} Si la valeur retournée est différente de 0, c'est que quelque chose s'est mal passé. Certains messages sont parfois parfois explicites. % _________ \subsection{Dessiner} Bon, vous avez une image latente, et vous souhaitez dessiner dessus (ou dedans ?) avec vos encres flottantes ? Il y a des fonctions pour ça, par exemple~: \begin{lstlisting} int fimg_plot_rgb(FloatImg *head, int x, int y, float r, float g, float b); \end{lstlisting} Les paramètres sont explicites, mais leur validité doit être sévèrement controlée par l'appelant. Il y a une fonction soeur, \texttt{fimg\_add\_rgb}\index{fimg\_add\_rgb}, qui ajoute du rgb à un pixel, laquelle fonction a d'ailleurs été à la base de la seconde génération de la photographie\index{photographie} en cumul\index{cumul}. Inversement, la fonction \texttt{fimg\_get\_rgb(FloatImg *head, int x, int y, float *rgb)} permet de lire les valeurs des trois canaux d'un pixel donné. Là aussi, il n'y a aucun contrôle sur la validité des valeurs $x$ et $y$ de la demande. % ---------------------------------- \subsection{Contraste}\index{contraste}\label{contraste} Certaines opérations d'ajustement du contraste d'une image semblent cohérents avec la notion d'image flottante. Certains d'entre eux, les plus simples, sont disponibles. Les autres sont à imaginer. \begin{figure}[h] \input{cos01.tex} % XXX XXX XXX \caption{Correcteur cos01} \end{figure} Ils prennent chacun trois paramètres, d'abord les images source et destination (\texttt{* FloatImg}), et le troisième est un nombre en double précision donnant la valeur maximale \textsl{supposée} de l'image source, valeur qui peut être déterminée de plusieurs manières. \begin{lstlisting} /* source in lib/contrast.c */ int fimg_square_root(FloatImg *s, FloatImg *d, double maxval); int fimg_power_2(FloatImg *s, FloatImg *d, double maxval); int fimg_cos_01(FloatImg *s, FloatImg *d, double maxval); int fimg_cos_010(FloatImg *s, FloatImg *d, double maxval); \end{lstlisting} Si vous souhaitez rajouter votre propre méthode de modification de contraste, il y a quelques explication en page \pageref{exemplefunc}. \begin{figure}[h] \input{cos010.tex} % XXX XXX XXX \caption{Correcteur cos010} \end{figure} Rappelons qu'il est possible pour un logiciel applicatif comme \texttt{grabvidseq} (cf page \pageref{grabvidseq}) de renseigner deux champs du descripteur d'image avec des données pertinentes. Ces deux champs sont \textit{fval} et \textit{count}. Dans ce cas particulier, le premier indique la valeur maximale du capteur, et le second sert à compter le nombre de capture\footnote{Et c'est bien géré aussi dans l'upscaling.} effectuées. La fonction \texttt{fimg\_normalize(FloatImg *fi, double maxima, int notused);} tente de gérer ce cas d'utilisation. Son ajout au captureur d'images floues sera probablement le bienvenue. Je me suis bien rendu compte à l'usage\footnote{Une histoire de \textit{fonderie}, un logiciel censé faire des films flous à partir d'images floues} en situation festive qu'il manquait des données dans la chaine de traitement. L'autre façon de procéder est d'explorer notre image à la recherche de la valeur maximale. La fonction \texttt{float fimg\_get\_maxvalue(\&fimg)} est prévue pour ça de façon sommaire. C'est actuellement la méthode utilisée par l'outil qui sert à faire les modifications de contraste (page \pageref{fimgfx}). On pourra aussi envisager d'utiliser \texttt{fimg\_get\_minmax\_rgb(FloatImg *head, float mmvals[6])}, qui permet un contrôle bien plus fin des dérives. La prochaine étape consistera à trouver une façon de faire une égalisation\index{égalisation} par histogramme\index{histogramme} qui respecte, dans toute sa futilité, le concept\index{concept} de pixel flottant. % ---------------------------------- \subsection{Géométrie}\index{géométrie}\label{geometrie} Très prochainement, le retour du blitter\index{blitter}. Et pour attendre, un truc improbable, voire même inutile, en fait l'inverse de l'upscaling. \begin{lstlisting} int fimg_halfsize_0(FloatImg *src, FloatImg *dst, int notused); \end{lstlisting} Attention lors de l'appel, le descripteur \texttt{dst} ne doit pas contenir d'image, et doit être effacé avec un bon \texttt{memset(\&result, 0, sizeof(FloatImg));} bien senti. Et le résultat est très moyen : il n'y a pas d'interpolation. \begin{lstlisting} int fimg_extract_0(FloatImg *src, FloatImg *dst, int x, int y); \end{lstlisting} Contrairement à la fonction précédente, celle-ci demande absolument une image de destination initialisée aux dimensions (largeur et hauteur) désirées. \begin{lstlisting} int fimg_rotate_90(FloatImg *src, FloatImg *dst, int notused); \end{lstlisting} Rotation\index{rotation} de 90 degrés dans le sens horlogique\footnote{ou trigonométrique,le code et la doc ne semblent pas d'accord.} d'une image RGB. L'image de destination peut être soit vierge, soit pré-allouée aux bonnes dimensions (échange W et H). % ---------------------------------- \subsection{Exportation \& Importation}\index{export}\label{export} Notre format de fichier étant totalement inconnu, il nous faut bien exporter nos images en quelque chose de plus connu. Bien entendu, c'est toujours affaire de compromis entre précision de valeurs et taille des fichiers. Et dans le sens inverse, il serait bien de savoir importer le monde extérieur dans nos sombres caves à pixel. Il faut quand même reconnaitre que c'est un peu la jungle dans les formats de fichiers d'image, ce qui explique le retard dans ce domaine\dots \subsubsection{Vers PNM}\index{PNM} Nous avons ici 16 bits par composante, soit 65536 valeurs différentes, ce qui est bien au-delà de ce que peuvent percevoir vos yeux. Hélas, c'est au prix d'une taille énorme sur les fichiers. D'un autre coté, l'utilisation du codage \textsc{ascii}\index{ascii} (alors qu'on pourrait mettre du binaire, plus compact) y est pour quelque chose. \begin{lstlisting} int fimg_save_as_pnm(FloatImg *head, char *fname, int flags); \end{lstlisting} Le bit \texttt{0} du paramètre \texttt{flags} mis à \texttt{1} demande à la fonction de faire la mise à l'échelle avec le couple \textsl{fvalue/count} décrit plus haut dans cette doc. Et si il est à zéro, c'est la fonction de recherche de valeur maximale (cf page \pageref{contraste}) qui est utilisée. Le bit \texttt{1} permettra bientôt\index{vaporware} de demander l'enregistrement de métadonnées\index{metadata} pertinentes, telle que l'epochtime de l'enregistrement. Les autres bits ne sont pas utilisés et doivent être à zéro. \subsubsection{Vers PNG}\index{PNG} Actuellement, on peut enregistrer uniquement en mode RGB, 8 bits par composante, mais on a quand même une bonne compression, ça compense. J'utilise \textsl{libpnglite} avec qui j'ai un peu de mal à suivre. Mais je me soigne. Le mode 16 bits va bientôt arriver. On peut aussi songer à l'export de metadatas. \begin{lstlisting} int fimg_save_as_png(FloatImg *src, char *outname, int flags); \end{lstlisting} Tous les flags doivent être à zéro. Sinon, ça foire parfois. \subsubsection{Vers/depuis TIFF}\index{TIFF} Le format canonique de la PAO\index{PAO} du siècle dernier. Il permet de gérer une foultitude de formats numériques. C'est aussi un format classique proposé par les gros scanners corporates. To be done\index{XXX} \subsubsection{Vers FITS}\index{FITS} Ce format est essentiellement utilisé pour stocker des images d'astronomie. To be done\index{XXX} \subsection{Utilitaires} Commençons par quelques petits trucs pour nous faciliter la vie dans des domaines annexes, tels que l'interprétation d'arguments dans la ligne de commande ou un fichier de configuration. \begin{lstlisting} int parse_WxH(char *str, int *pw, int *ph) int parse_double(char *str, double *dptr) \end{lstlisting} La fonction \texttt{int format\_from\_extension(char *fname)} examine un nom defichier tel que \texttt{lena.xxx}, et retourne, si la partie \texttt{xxx} un éventuel nombre positif, dont les valeurs sont dans floatimg.h le valeureux. Les extensions actuellement connues sont : fimg, png, pnm et tiff. To be continued\index{XXX}\dots \subsection{Effets}\index{sfx} Quelques routines qui servent futilement à \textsl{brotcher} les images. \begin{lstlisting} int fimg_killcolors_a(FloatImg *fimg, float fval); int fimg_killcolors_b(FloatImg *fimg, float fval); \end{lstlisting} % ---------------------------------- \subsection{Filtrages}\index{filtrage} Pour commencer, il faut que je réfléchisse au traitement des bordures des images. Ensuite que je débuggue\index{bug} ces deux fonctions~: \begin{lstlisting} int fimg_lissage_2x2(FloatImg *img); int fimg_killborders(FloatImg *img); \end{lstlisting} Bon, oké, ça marche ? Passons à l'tape suivante. La convolution avec une matrice 3x3, c'est possible. Et pas trop compliqué à faire. Bon, il reste le souci avec les bordures, souci qui ne peut être que temporaire, mais ésotérique à fixer. Passons maintenant aux choses sérieuses, et définissons la description d'un filtre 3x3. \begin{lstlisting} typedef struct { float matrix[9]; float mult; float offset; } FimgFilter3x3; \end{lstlisting} L'usage des champs \texttt{mult} et \texttt{offset} n'est pas clairement défini. Le prototype de la fonction de filtrage non plus, mais assez simpe quand même. Source et destination ne peuvent désigner la même image, et le champ \texttt{matrix} du filtre doit contenir des valeurs cohérentes. \begin{lstlisting} int fimg_filter_3x3(FloatImg *src, FloatImg *dst, FimgFilter3x3 *filtr) \end{lstlisting} Comme dans la plupart des cas, la gestion des valeurs négatives de pixel est laissé au hasard. Quoique, il doit bien exister quelques solutions de contournement : clamping ou shift ? \textsl{To be continued\index{XXX}\dots} % ---------------------------------- \subsection{Exemple de fonction}\index{exemple}\label{exemplefunc} Nous allons maintenant écrire une fonction intégrable dans le répertoire \texttt{funcs/}. Elle aura donc accès aux \textsl{internals}% \footnote{que je peux décider de changer n'importe quand} de \textsc{FloatImg}, une chose qui est en principe interdit aux programmes \textsl{enduser}. Soyez prudents. Cette fonction va faire quelque chose à partir d'une image source et d'une valeur, et écrire le résultat dans une image de destination. Pour simplifier les choses, nous n'allons traiter que les images de type \textsc{FIMG\_TYPE\_RGB}, de loin le plus répandu par les temps qui courent. \begin{lstlisting} int fimg_example(FloatImg *s, FloatImg *d, float value) { int size, index; if ( s->type!=FIMG_TYPE_RGB || d->type!=FIMG_TYPE_RGB) { perror("fimg_example"); return -1; } size = s->width * s->height; for (idx=0; idxR[idx] = s->R[idx] - value; d->G[idx] = s->G[idx] - value; d->B[idx] = s->B[idx] - value; } return 0; } \end{lstlisting} Je vous laisse imaginer les dégats que peut faire cette fontion en utilisation réelle. Mieux, je vous propose d'essayer par vous-même. En particulier tout le reste du code qui suppose qu'un pixel ne peut \textbf{pas} être négatif. Vous pouvez aussi remarquer qu'il n'y a pas de controle de cohérence sur les dimensions des deux images, malgré l'existence de fonctions prévues à cet effet.. % ------------------------------------------------------------------- \section{Les outils}\label{outils} \textsf{3615mavie} : sur des projets comme celui-ci, qui travaillent in-fine sur des objets que l'on peut considérer comme « physiques », il est important de passer à une utilisation normale\footnote{Il y a une vie en dehors de git.} et construire des trucs qui mettent en action le code primitif. Ces machins ont en commun quelques options bien pratiques~: \texttt{-h} pour avoir un résumé des options disponibles, \texttt{-v} qui augmente la puissance de bavardage, et \texttt{-K nn.nn} pour un paramètre flottant. Dans un avenir incertain, il existera des pages de man\index{man}. % --------------------- \subsection{mkfimg}\index{mkfimg}\label{mkfimg} Propose la création d'un fichier contenant une image de « teinte » constante (ou pas). Cette notion de teinte est assez inconsistante pour le moment, mais ça n'est pas si grave que ça. \begin{verbatim} tth@debian:~/Devel/FloatImg/tools$ ./mkfimg -h Usage: mkfimg [options] quux.fimg width height -k N.N give a float parameter -t type howto make the pic black, drand48... -v increase verbosity \end{verbatim} La plupart des types d'image générée prennent un paramètre flottant qui devra être donné avec l'option \texttt{-k F.F} avec une valeur par défaut à $1.0$. \begin{description} \index{XXX} \item [black/gray/grey:] efface avec 0.0 (black) ou avec la valeur \texttt{-k} (gray). \item [drand48:] beaucoup de bruit dans chacun des canaux. \item [hdeg/vdeg:] dégradé du noir au blanc (relatif à \texttt{-k}). \end{description} % --------------------- \subsection{png2fimg}\index{png2fimg}\label{png2fimg} Grosse panne\index{bug} à réparer. \begin{verbatim} tth@debian:~/TMP/floatimg$ png2fimg A.png foo.fimg error in 'fimg_create_from_png' : read png -> -1 File error png2fimg : err -1, abort. \end{verbatim} Il faut peut-être envisager le passage à \texttt{libpng}\index{libpng}. \subsection{fimgstats}\index{fimgstats}\label{fimgstats} Affichage de quelques valeurs calculées à partir du contenu d'un fichier \texttt{.fimg}\index{.fimg}. \begin{verbatim} usage : fimgstats [options] file.fimg -c make a machinable csv -v increase verbosity \end{verbatim} À vrai dire, je ne sais pas encore quelles métriques seront utiles en première approche, alors commençont par le plus simple, les valeurs moyennes de chaque composante. Puis nous rajouterons\footnote{Les patchs sont les bienvenus} le calcul de la variance\index{variance}. Les compétences de \texttt{schmod777} sont attendues au dd2\index{dd2}. % --------------------- \subsection{fimgfx}\index{fimgfx}\label{fimgfx} Ce programme, \textit{en cours de création\index{XXX}}, applique un effet spécial à une image. À l'heure actuelle\footnote{janvier 2019, vers 13:37}, nous avons déja quelques ajustements basiques de contraste, qui ne tiennent pas vraiment compte du contenu de l'image. \begin{verbatim} tth@daubian:~/Devel/FloatImg/tools$ ./fimgfx -v -h --- fimg special effects --- cos01 cos010 pow2 sqrt gray0 xper \end{verbatim} Certaines de ces opérations ont besoin d'un paramètre flottant. Celui-ci peut être fixé avec l'option \texttt{-k}. Une liste détaillée des opérations possibles sera lisible avec le sélecteur \texttt{-L}. \begin{description} \item [Ajustements de contraste:] cos01 cos010 pow2 sqrt \item [Distorsions chromatiques:] gray0 \item [Déformations géométriques:] r90 \end{description} \subsection{fimgops}\index{fimgops}\label{fimgops} Quelques opérations diverses entre deux images, qui doivent être de la même taille, et uniquement du type \textsl{RGB}. Certaines de ces opérations peuvent avoir un effet étrange sur vos images, par exemple si un pixel se retrouve avec une valeur négative. \begin{verbatim} usage: fimgops [options] A.fimg B.fimg operator D.fimg operators: add 1 sub 2 mix 3 mul 4 mini 5 maxi 6 options: -g convert output to gray -k N.N set float value -v increase verbosity -X explosive action \end{verbatim} Pour des operateurs paramétrable (comme \texttt{mix}), le paramêtre flottant doit être fourni en utilisant l'option \texttt{-k}. La véracité mathématique n'est pas garantie. Et n'oubliez pas que les valeurs négatives peuvent être la cause de \textsl{glitches} de qualitay. % ------------------------- \subsection{fimg2png, fimg2pnm, fimg2tiff} \index{fimg2png}\label{fimg2png} \index{fimg2pnm}\label{fimg2pnm} \index{fimg2tiff}\label{fimg2tiff} Quelques petits proggies pour exporter notre format\index{.fimg} secret vers des choses plus directement utilisables. À condition que le code soit écrit et documenté. D'un autre coté, écrire un greffon d'import/export pour Gimp\index{Gimp} ou ImageMagick\index{ImageMagick} ou Krita\index{krita} ne devrait pas être trop difficile. Des volontaires ? \textsl{D'ailleurs, pourquoi $n$ logiciels indépendants alors q'un seul devrait être nécessaire ?} \subsection{fimg2gray}\index{fimg2gray}\label{fimg2gray} Nous avons vu dans ce document que chaque image flottante pouvait avoir plusieurs plans de réalité. Il ne faut en négliger aucun. Il faut quand même deviner que pour passer de l'espace RGB\index{RGB} à une abstraction linéaire mono-dimensionnelle, il existe une foultitude de méthodes, toutes plus légitimes que les autres. % ------------------------------------------------------------------- \section{TODO}\index{TODO}\label{TODO} Il reste plein de choses à faire pour que ce soit vraiment utilisable, surtout dans un contexte artistique à grande porosité. C'est par ces frottements de techniques ayant du sens que les choses seront acquises. \begin{itemize} \item Import/export au format \textsc{tiff}\index{TIFF}. \item Remplacer le « fait-maison » par \textsc{libnetpnm}\index{pnm}. \textsl{[en cours]}. \item Compléter les traitements mathémathiques (eg le gamma\index{gamma}). \item Formaliser les codes d'erreur. \textbf{Urgent}. \item Faire une passe complète de Valgrind\index{valgrind}. \end{itemize} % ------------------------------------------------------------------- \section{Exemples pour yusers}\index{exemple} Nous allons \textsl{essayer d'improviser} un exemple presque réel, avec un peu de rache\index{rache} dedans, et beaucoup de simplification. Ce qui est autorisé dans les exemples, mais dans la vrai vie, il ne faut jamais négliger le traitement des éventuelles erreurs. Nous savons générer une image contenant des pixels aux valeurs probablement aléatoires, avec la commande \texttt{mkfimg}, qui utilise le \texttt{drand48}\index{drand48} de \textsc{posix}\index{POSIX}. Maintenant, posons-nous une question de statisticien : ue se passe-t-il si nous faisons la somme de plusieurs centaines de ces images ? \begin{lstlisting} #!/bin/bash ACCU="quux.fimg" TMPF="tmp.fimg" DIMS="320 240" mkfimg $ACCU $DIMS for i in {0..1000} do mkfimg -t drand48 ${TMPF} ${DIMS} fname=$( printf "xx%04d.pnm" $i ) fimgops $ACCU $TMPF add $ACCU fimg2pnm -v -g $ACCU $fname done convert -delay 10 xx*.pnm foo.gif \end{lstlisting} Voilà, si les choses se passent mal, vous allez découvrir que votre \texttt{drand48} n'est pas si "drand" que ça. Et ce n'est pas à moi d'en tirer les conclusions... \subsection{Scripts}\index{scripts}\label{scripts} Le script bash\index{bash} \texttt{scripts/shoot.sh} est un front-end encore un peu rudimentaire vers le programme de capture d'image décrit page \pageref{grabvidseq}. Il utilise deux fichiers dans le répertoire de travail~: \textit{reglages} et \textit{compteur}. Le premier est, en fait, un bout de shell affectant quelques variables, ou plutôt, les surchargent. \begin{lstlisting} OPTIONS="${OPTIONS} -v -c pow2 " SHOW="yes" NBRE=1000 PERIOD=0 OFORMAT="p_%04d.png" \end{lstlisting} La première ligne demande, en plus des options par défaut, plus de bavardage, et un changement de contraste. La seconde demande l'affichage de la photo. Les deux suivantes demandent la capture de 1000 images à la cadence méga-blast. La dernière est moins simple~: \texttt{man sprintf}\index{printf} pour comprendre. Quand au second fichier, il contient un compteur (stocké en ascii) qui est incrémenté après chaque capture réussie. Et ce compteur est utilisable par la variable \texttt{OFORMAT} que nous avons vue quelques lignes plus haut. \lstinputlisting[language=sh]{../scripts/shoot.sh} \subsection{Fonderie}\index{fonderie}\label{fonderie} Ce projet externe\footnote{... pour le moment, j'ai des soucis sur l'architecture du \textbf{pipdeprod} à adopter\dots} est destiné à la confection de films flous\index{film} à partir de photos floues. Le script \texttt{scripts/echomix.sh} est une première expérimentation en bash, utilisant deux outils en \textsc{cli}, le premier pouvant salement brotcher une image, et le second capable de mélanger harmonieusement deux images, la balance est équilibrée. Il s'agit donc d'un petit programme écrit en Bash\index{bash}, un langage dont la connaissance est, pour moi, indispendable à qui veut faire des images kitchies\index{kitchy}. Mais ne vous inquiétez pas, c'est en fait assez simple à comprendre. Et comprendre, c'est apprendre. Voici donc le script, décomposé et expliqué : \begin{verbatim} #!/bin/bash SRCDIR="Fist" DSTDIR="Pong" FTMP="/dev/shm/tmp.fimg" FDST="/dev/shm/foo.fimg" # count the nomber of picz in the source directory NBRE=$(ls -1 ${SRCDIR}/*.fimg | wc -l) # compute the echo picz offset OFFS=$(( NBRE / 4 )) \end{verbatim} Dans ce préliminaire logiciel, nous avons nommés le répertoire \textsc{srcdir} contenant les captures d'image au format fimg, le répertoire \textsc{dstdir} dans lequel seront rangées les images calculées, et l'emplacement de deux fichiers de travail. Les quelques lignes suivantes, qui semble bien magiques, ne sont en fait que de la magie Unix\index{Unix}. Elles nous permettent d'avoir \textsc{nbre}, le nombre d'images à traiter, et \textsc{offs}, un décalage dépendant du nombre d'image. Muni de toutes ces informations, nous pouvons rentrer dans le lard du sujet, la boucle qui travaille. \begin{verbatim} # MAIN LOOP for idx in $(seq 0 $NBRE) do # build the two input filenames ... # imgA=$(printf "$SRCDIR/%04d.fimg" $idx) vb=$(( $(( idx + OFFS )) % NBRE)) imgB=$(printf "$SRCDIR/%04d.fimg" $vb) # ... and the output filename # dst=$(printf "%s/%05d.png" ${DSTDIR} $idx) \end{verbatim} Dans cette première partie de la boucle nous avons construit plusieurs noms de fichier à partir du rang de la boucle en cours d'exécution, des deux valeurs \textsc{nbre} et \textsc{offs} calculées en préambule. \begin{verbatim} # trying to autocompute the mixing coefficient # compute=" s(${idx} / 16) " K=$(echo $compute | bc -l) printf " %25s => %8.3f\n" "$compute" $K \end{verbatim} Cette seconde partie sert à calculer avec la commande \texttt{bc}\index{bc} un coefficient variable en fonction du temps : $sin(idx/16)$ afin d'avoir une oscillation du coefficient entre -1.0 et 1.0, deux valeurs probablement glitchantes. \begin{verbatim} # do the hard floating computation # fimgfx -v cos010 ${imgB} ${FTMP} fimgops -k ${K} ${FTMP} ${imgA} mix ${FDST} \end{verbatim} Étape suivante, étape cruciale : le brassage d'une multitude de pixels flottants. Tout d'abord, nous faisons subir à l'image-echo (\texttt{imgB}, définie au début du script) un distorsion chromatique de type \textsl{cos010}, le résultat étant écrit dans un fichier temporaire. Ensuite, nous mixons l'image primaire et son echo en utilisant le rapport de mixage calculé quelques lignes plus haut. \begin{verbatim} # write the output as PNG for video encoding # fimg2png ${FDST} ${dst} done \end{verbatim} Et en fin de boucle, nous convertissons le résultat de nos savants au format PNG, et écrivons le fichier dans le répertoire de destination fixé au début. C'est le moment de passer la main à ffmpeg\index{ffmpeg}. C'est juste une POC\index{POC}, et une implémentation bien plus complète écrite en \textbf{C}\index{C} est déja en chantier, avec une complexité prévue à un niveau assez réjouissant. % ------------------------------------------------------------------- \section{Video for Linux}\index{v4l2} Donc, maintenant, nous savons un peu tripoter ces images flottantes. Et nous devons nous poser une question fondamentale\footnote{primitive ?} sur la provenance de ces données prétendant être des images. En fait, notre désir secret est la découverte des choses cachées du monde qui nous entoure. Nous voulons des images du \textbf{réel} et pour cela, l'outil le plus commun, le plus répandu, est la webcam\index{webcam}. L'universelle webcam. Et l'incontournable v4l2. \subsection{grabvidseq}\index{grabvidseq}\label{grabvidseq} Un logiciel en évolution (trop ?) lente, qui permet déja la capture d'images en \textsl{longue pose} selon la méthode du cumul\index{cumul}, et devrait bientôt retrouver sa capacité à enregistrer des séquences d'images. \begin{verbatim} tth@debian:~/Devel/FloatImg/v4l2$ ./grabvidseq -h options : -c quux contrast adjustement -d /dev/? select video device -g convert to gray -n NNN how many frames ? -O ./ set Output dir -o bla set output filename -p NN.N delay between frames -s WxH size of capture -u try upscaling... -v increase verbosity -X arg Xperiment option \end{verbatim} La plupart de ces options ont un usage quasi-évident. L'option \texttt{-s} doit correspondre à une des résolutions possibles de votre capteur. Le type du fichier en sortie (option \texttt{-o}) est déterminé par l'extension, actuellement seulement \texttt{.fimg}, \texttt{.pnm} et \texttt{.png} sont reconnus. La conversion en gris (option \texttt{-g}) mérite un peu plus de travail, et une paramétrisation plus facile. L'ajustement de contraste (option\texttt{-c}) est vaguement expliqué page \pageref{contraste}. L'option \texttt{-X} me permet d'intégrer des \textit{fritures} expérimentales dans le binaire, et ne doit donc pas être utilisée dans des scripts si on a des visions à long terme. \subsubsection{Upscaling}\index{upscaling}\label{upscaling} La fonction que j'ai appelée \textsl{upscaling} est un petit hack qui permet de doubler artificiellement la résolution de l'image, en profitant du fait que l'on est capable de prendre $N$ images en rafale. Pour être rigoureux dans la prise de vue, ce $N$ doit être un multiple de 4, surtout si le nombre de capture est faible. N'hésitez pas à faire des essais, le résultat est parfois aléatoire, surtout avec une caméra qui bouge. \textbf{Là, il manque un schéma\dots} \subsection{video-infos}\index{video-infos}\label{video-infos} Que contient, que peut faire mon périphérique \textsl{àlc} ? Quelles sont ses possibilités de réglage ? \begin{verbatim} tth@debian:~/Devel/FloatImg$ v4l2/video-infos -h Options : -d select the video device -K nnn set the K parameter -l list video devices -T bla add a title -v increase verbosity \end{verbatim} Je me sens obligé d'avouer qu'il reste quelques points mystérieux dans l'\textsc{api} de \textsc{v4l2}, et donc, que ce que raconte ce logiciel doit être pris avec des pincettes. En particulier la liste des résolutions disponibles. \subsection{nc-camcontrol} Ajustement \textsl{Brightness Contrast Saturation Hue\dots} % ------------------------------------------------------------------- \section{À l'extérieur} \subsection{ImageMagick}\index{ImageMagick} Pour afficher notre format .fimg exotique avec \texttt{display}, vous devez mettre ce bout de XML\index{XML} dans le fichier \texttt{\$HOME/.magick/delegates.xml}~: \begin{lstlisting} \end{lstlisting} C'est juste un hack rapide, qui ne fonctionne pas très bien avec d'autres commande de IM, comme identify, qui a tendance à raconter un peu n'importe quoi... Je compte donc sur le bouquin de \textsl{Brunus} pour avancer... \subsection{Gimp}\index{Gimp} Mmmmm... Ça semble un peu plus compliqué. D'un autre coté, il faut faire ça en \textbf{C}, ce qui ne peut être négatif. \subsection{Et encore ?}\index{krita}\index{geeqie} Il y a d'autres logiciels pour lesquels écrire une fonction d'importation serait bien~: \textsl{Geeqie}, un visualiseur d'image fort pratique, ou \textsl{Krita} qui semble avoir les faveurs de dessinateurs de talent. % ------------------------------------------------------------------- \section{Et pour la suite ?} En fait, je fait de la photo par la méthode du « cumul »\index{cumul} depuis plusieurs années. Une webcam\index{webcam}, un Linux\index{Linux}, et ça \textsl{juste marche}. Sauf que c'est quand même un peu galère à déplacer, il faut avoir un shell pour déclencher, c'est pas facile à utiliser en mode portnawak\dots L'idée est donc de construire un appareil autonome, basé sur un Raspi et une webcam \textsc{usb}\index{USB}, pilotable par \textsc{lirc}\index{LIRC}, alimenté par une (grosse) batterie et permettant d'aller faire des images au bord d'un lac ou dans la campagne de l'Ariège. % ------------------------------------------------------------------- \printindex \end{document}