J’ai déjà montré auparavant (voir l’article ici) la construction de ma caméra, qui permet d’imager la sortie des 49 fibres optiques récoltant le signal lumineux dans les deux longueurs d’onde, sur une surface donnée. À présent, mon objectif était de faire le traitement du flux vidéo en temps réel, afin de sortir un graphique de l’intensité lumineuse recueillie en fonction du temps, et ce, pour tous les canaux.
Mon choix d’environnement de programmation s’est porté sur Processing (https://processing.org/), parce qu’il a été conçu spécialement pour des artistes en art visuel et en multimédia, avec une foule de librairies et une communauté en ligne. Il est open source, le langage ressemble énormément à celui d’arduino, ce qui n’est pas étonnant puisqu’ils partagent la parenté de leurs créateurs. Étant donné que mon projet est assez complexe, passant du traitement d’image à l’implémentation du calcul du SpO2 et finalement la représentation 3D du résultat en temps réel, avoir un environnement de programmation épuré était essentiel. Et finalement, pour obtenir éventuellement (peut-être, un jour) un affichage des données esthétiquement attrayant, chose dans laquelle je suis assez novice, je me suis dit que Processing était la meilleure option, vu la quantité surprenante de tutoriels sous toutes les formes.
J’ai donc téléchargé la dernière version, et j’ai trouvé le tutoriel suivant, qui m’a permis d’afficher la sortie de ma caméra à l’écran de mon ordinateur :
Cette vidéo est très exhaustive, et j’ai réussi aisément à non seulement faire fonctionner ma caméra, mais en plus, à comprendre assez bien la philosophie qui dicte l’architecture de programmation de Processing : l’utilisation de la fonction void draw()
et des interrupts de capture déjà implémentés. Le tout est vraiment simple et sympathique d’utilisation.
Pour la partie traçage d’un graphique en temps réel, après quelques recherches, j’ai fini par tomber sur le code disponible ici. Rien de bien compliqué, et cela utilise la libraire Grafica que l’on peut télécharger avec le gestionnaire de librairies inclus dans Processing.
Ma seule contribution provenant des entrailles de mon cerveau fut, pour l’instant, la création d’une fonction convertissant une image de 720×480 pixels en 49 valeurs d’intensité correspondant à chacun de mes canaux. La voici :
float[] getsignal() {
float[] ch = new float[49];
for (int xi = 0; xi <= 6; xi++) {
for (int yi = 0; yi <=6; yi++) {
int xin = 67*xi + 185;
int yin = 72*yi + 25;
int n = xi + 7*yi;
for (int x = xin-dc; x <= xin+dc; x++) {
for (int y = yin-dc; y<= yin+dc; y++) {
int loc = x + y * video.width;
float intensity = brightness(video.pixels[loc]);
if (intensity > 50) ch[n] = ch[n] + intensity;
}
}
}
}
return ch;
}
Il s’agit de 4 boucles for nichées les unes dans les autres. Les deux premières permettent de sauter d’une région d’intérêt à l’autre, selon une formule empirique en pixels donnant la position du centre de chaque fibre sur l’image. Les deux autres passent sur chaque pixel à l’intérieur d’un carré de largeur définie par 2*dc (j’ai mis dc=20 dans le programme, donc la région d’intérêt est un carré de 40×40 pixels centré sur la sortie de chaque fibre). On a vu dans l’article précédant que le signal d’intensité saturait et débordait sur une largeur variable de pixels, j’ai donc dû faire un compromis et définir des zones que j’estimais pouvoir être remplies seulement par la lumière d’une seule fibre. La vitesse d’exécution n’a pas été ma priorité, j’avoue que ce traitement d’image pourrait largement être optimisé, l’important étant simplement d’en faire une preuve de concept, j’utilise un ordi de bureau tout de même, ce qui me donne une bonne puissance de calcul. Donc à l’intérieur du carré, j’extrais, pour chaque pixel, la valeur d’intensité lumineuse à l’aide de la fonction brightness, qui extrait cette composante de la couleur dans l’espace HSB, à partir d’un pixel RGB (oui ma caméra est noir et blanc pour l’infrarouge et les faibles luminosités, mais le convertisseur analogique/digital que j’utilise numérise les pixels en RBG, même si concrètement, toutes les valeurs correspondent simplement à des niveaux de gris). J’ai ensuite rajouté un treshold de 50 sur cette valeur, afin d’éliminer les pixels noirs du calcul d’intensité, et de bien circonscrire le cercle lumineux produit par la fibre. Je fais une somme brute de toutes les valeurs d’intensité de tous les pixels d’intérêt, afin de produire une seule valeur représentative (on l’espère) de l’intensité lumineuse dans la fibre, et donc, de la quantité de lumière diffusée par l’organe qui a été rétroréfléchie.
Comme on peut le voir à la figure suivante, cette approche permet effectivement de tracer le signal lumineux en temps réel, sur le canal de notre choix. L’axe des x est le temps en nombre de points, puisqu’on a 30 images/s, cela fait 30 points/s donc la plage couvre une quinzaine de secondes. L’intensité est une valeur arbitraire, en première approximation linéairement proportionnelle à la quantité de lumière dans la fibre. Je l’ai vérifié rapidement, de manière qualitative, en observant ce que je crois être les pulsations cardiaques! (À vérifier, bien entendu!) Une courte vidéo montre le résultat. Cette partie du travail du projet a été effectué du 26 au 28 avril dernier, mais je n’ai pas pris le temps de le documenter auparavant. D’autres développements ont été faits et je les posterai sous peu!