\documentclass[a4paper,10pt]{article} % \listfiles % pour le debug \usepackage[french]{babel} \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} % XXX \usepackage{lipsum} \usepackage{makeidx} \usepackage{listings} \usepackage{babel} \usepackage{pifont} % caractères rigolos \usepackage{enumitem} \setitemize[1]{label={\ding{82}}} \frenchbsetup{CompactItemize=false} % \usepackage{color} % \usepackage{url} \usepackage{xspace} \usepackage[verbose]{layout} \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 ? \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. \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. % ------------------------------------------------------------------- \tableofcontents \pagebreak % ------------------------------------------------------------------- \section{Premier example}\index{example}\label{example} \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 moyes. Nous allons donc directement rentrer au cœur du problème. \vspace{1em} Pour commencer par quelques 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. \vspace{1em} Tout d'abord, nous devons déclarer et garnir quelques variables pour gérer la machinerie interne. \begin{verbatim} int width = 640, height = 480; char *fname = "exemple.fimg"; FloatImg fimg; \end{verbatim} 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{verbatim} 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{verbatim} 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}. \vspace{1em} 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.} \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 Debain et dérivées}~: libv4l2, libpnglite, libtiff, libnetpbm, et probablement d'autres choses. \vspace{1em} 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. \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} source de la cible par défaut du make. \vspace{1em} 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}. \vspace{1em} Le script \texttt{install.sh}, à la racine du projet, est censé faciliter un peu la chose. Il prend également en compte la copie des outils (cf. page \pageref{outils}) dans le répertoire prévu à cet effet : \texttt{/usr/local/bin}. % ------------------------------------------------------------------- \section{Utilisation coté codeur}\label{codaz} Classiquement, il y a un fichier à inclure dans chacun de code 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é-processur} et les prototypes des fonctions utilisables par vos logiciels. \vspace{1em} 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{verbatim} /* in memory descriptor */ typedef struct { int width; int height; int type; float fval; int count; float *R, *G, *B, *A; int reserved; } FloatImg; \end{verbatim}\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}. \begin{verbatim} #define FIMG_TYPE_GRAY 1 #define FIMG_TYPE_RGB 3 #define FIMG_TYPE_RGBA 4 \end{verbatim} 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. \vspace{1em} 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... \vspace{1em} 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$. \vspace{1em} \textsl{Il est vraiment dommage que ces deux données auxiliaires ne puisse être stockée dans les dumps d'image. \index{XXX}} \subsection{lib/}\index{lib/} Première chose, la gestion dynamique de la mémoire occupées par tous ces pixels flottants est faite par ces deux fonctions~: \begin{verbatim} int fimg_create(FloatImg *fimg, int w, int h, int type); int fimg_destroy(FloatImg *fimg); \end{verbatim} Les types d'images actuellement gérés sont les trois grands classiques : gray, rgb et rgba. Les constantes adéquates sont dans \texttt{floatimg.h} et expliquées quelques lignes plus haut. Les codes d'erreur sont disparates et non documentés. \vspace{1em} Bon, vous avez une image latente, et vous souhaitez dessiner dessus (ou dedans ?) avec vos encres flottantes ? Il y a une fonction pour ça. \begin{verbatim} int fimg_plot_rgb (FloatImg *head, int x, int y, float r, float g, float b); \end{verbatim} 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 a d'ailleur été la base de la seconde génération de la photographie\index{photographie} en cumul\index{cumul}. \subsubsection{Contraste}\index{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. 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. \begin{verbatim} /* 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{verbatim} 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. \vspace{1em} 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 faite pour ça. C'est actuellement la méthode utilisée par l'outil qui sert à faire les modifications de contraste (page \pageref{fimgfx}). \vspace{1em} 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{funcs/}\index{funcs/}\label{funcs} Une bonne partie de ces fonctions 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. \vspace{1em} On y trouve pêle-mêle de l'import/export de fichiers, de l'analyse de chaines de caractères, du tracé de choses bizarres\dots Plein de trucs qu'il faudra bien expliquer un jour\footnote{Mais il fait trop chaud dans le dd2\index{dd2}}. \subsection{Exemple de fonction}\index{example} Nous allons écrire une fonction qui 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. \begin{verbatim} int fimg_example(FloatImg *s, FloatImg *d, float value) { int size, index; if ( s->type!=FIMG_TYPE_RGB || d->type!=FIMG_TYPE_RGB) { 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{verbatim} 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. % ------------------------------------------------------------------- \section{Les outils}\label{outils} \textsl{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. \vspace{1em} Ces machins ont en commun deux options bien pratiques~: \texttt{-h} pour avoir un résumé des options disponibles et \texttt{-v} qui augmente la puissance de bavardage. Dans un avenir incertain, il existera des pages de man\index{man}. \subsection{mkfimg}\index{mkfimg}\label{mkfimg} Création d'un fichier contenant une image de « teinte » constante (ou pas). Cette notion de teinte est assez inconsistante pour le moment, \begin{verbatim} tth@debian:~/Devel/FloatImg/tools$ ./mkfimg -v -h Usage: mkfimg [options] quux.fimg width height -k N.N give a float parameter -t bla howto make the pic black, drand48... -v increase verbosity *** FloatImg library, alpha v73 (Sep 28 2019, 23:34:29) \end{verbatim} \subsection{png2fimg}\index{png2fimg}\label{png2fimg} Grosse panne à 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} \textit{En cours de création\index{XXX}. Attention, je vais expérimenter un parsing un peu étrange sur les arguments de la ligne de commande. coredump expected.} \vspace{1em} À l'heure actuelle\footnote{décembre 2019, vers 15:30}, 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 \end{verbatim} \subsection{fimgops}\index{fimgops}\label{fimgops} Quelques opérations diverses entre deux images, qui doivent être de la même taille, et du même type \textsl{pour le moment, uniquement RGB}. \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 \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. \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é. Peut-être en page \pageref{funcs}\dots \vspace{1em} D'un autre coté, écrire un greffon d'import/export pour Gimp\index{Gimp} ou Imagemagick\index{Imagemagick} ne devrait pas être trop difficile. Des volontaires ? \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. \vspace{1em} 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. \vspace{1em} \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}. \end{itemize} % ------------------------------------------------------------------- \section{Exemples pour yusers}\index{example} Nous allons \textsl{essayer d'improviser} un exemple presque réel, avec un peu de rache\index{rache} dedans. Ce qui est autorisé dans les exemples. \vspace{1em} Nous savons générer une image contenant des pixels aux valeurs probablement aléatoires (drand48\index{drand48}). Que se passe-t-il si nous faisons la somme de plusieurs centaines\footnote{Des erreurs toxiques ?} de ces images ? \begin{verbatim} #!/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{verbatim} Voilà, si les choses se passent mal, vous allez découvrir que votre \texttt{drand48} n'est pas si drand que ça. Séquence angoisse. \subsection{Scripts}\index{scripts}\label{scripts} :wq % ------------------------------------------------------------------- \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. \vspace{1em} 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, 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. \begin{verbatim} tth@debian:~/Devel/FloatImg/v4l2$ ./grabvidseq -h options : -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} et \texttt{.pnm} sont reconnus. La conversion en gris (option \texttt{-g}) mérite un peu plus de travail, et une paramétrisation plus facile. 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. \vspace{1em} \textbf{Là, il manque un schéma\dots} \subsubsection{shoot.sh}\index{shoot.sh}\label{shoot.sh} \hspace{4cm}XXX\index{XXX} \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 sois 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. % ------------------------------------------------------------------- \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 \vspace{1em} L'idée est donc de construire un appareil autonome, basé sur un Raspi et une webcam \textsc{usb}\index{USB}, alimenté par batterie et permettant d'aller faire des images au bord d'un lac ou dans la campagne de l'Ariège. % ------------------------------------------------------------------- \printindex \end{document}