Synchronisation des DEL avec le signal vidéo

Afin de bien pouvoir séparer les signaux de ma caméra oxymétrique, je dois trouver un moyen pour synchroniser les DELs avec l’acquisition des images. L’objectif est d’alterner les prises de vue entre un éclairage avec la DEL rouge et avec la DEL infrarouge, sans que les signaux se chevauchent. Le hic, c’est que la caméra que j’utilise n’a aucun trig externe : toute l’électronique de contrôle est intégrée, laissant seulement le signal vidéo analogique en sortie.

Mon premier essai fut avec une esquisse arduinesque d’une redoutable simplicité :

void loop() {
analogWrite(5,10);
delay(33);
delayMicroseconds(325);
analogWrite(5,0);
analogWrite(6,60);
delay(33);
delayMicroseconds(325);
analogWrite(6,0);
}

La broche enable de chaque régulateur de courant des DELs est reliée aux pin 5 et 6 de l’arduino, qui fait un PWM pour éviter la surchauffe. Essentiellement, le programme allume en alternance les deux DEL, avec une période d’environ 33,333ms, pour correspondre au 30 images par secondes du flux vidéo. Il y a deux problèmes principaux à cette approche : l’horloge de l’arduino n’est synchronisée ni en phase, ni en fréquence avec celle de la caméra, ce qui fait que pendant la durée d’une prise d’image, bien souvent les deux DELs sont allumées séquentiellement, donc on se retrouve avec un signal mélangé. La légère différence en fréquence fait que ce décalage en phase se déplace au fil du temps, produisant un battement lent dans le signal qui rend celui-ci inutilisable périodiquement. Bref, ça marche, mais ce n’est vraiment pas fiable et il y a manière de faire mieux.

J’ai donc relégué l’arduino et me suis décidé à résoudre le problème de manière purement électronique. Le schéma est présenté ci-dessous :

J’ai commencé par observer le signal vidéo analogique à l’oscilloscope. Il ressemble à ceci :

À chaque ligne, il y a un pic vers le bas qui indique une nouvelle ligne. La zone au milieu de la photo survient à la fréquence de 60Hz, puisque la vidéo est interlacée, c’est l’indicateur de nouvelle image. Afin de me synchroniser là-dessus, je me sers de la partie distinctive qui est un plat près de 0V. Je passe donc ce signal dans la borne négative d’un premier comparateur, avec un niveau de voltage assez bas sur la borne positive (224mV), ce qui sort uniquement les pics les plus bas (nouvelle ligne et nouvelle image). La sortie de ce comparateur donne ceci :

Les pics plus importants correspondent au plateau près de zéro que je souhaitais isoler. Je passe ce signal dans un filtre passe-bas, qui va garder seulement la composante à 60Hz et retirer les petites fluctuations :

Je passe ensuite ce signal dans un second comparateur, afin d’obtenir un retour parfait au ground et une forme plus abrupte des pics :

Ça marche bien, sauf que c’est en open collector, donc on ne peut pas l’interfacer directement avec une puce en logique CMOS. Le voltage du niveau haut est à 4,3V environ, ce qui est trop loin du 5V. Après un bon bout de gossage, j’ai réussi à brancher un transistor PNP en configuration d’émetteur commun. Le biasing provoquait des effets bizarres, parce que je mettais une résistance de charge trop élevée, ce qui limitait trop le courant dans le collecteur. En le revirant de bord, (l’émetteur et le collecteur sont inversés), j’ai réussi à le faire marcher par magie. En investiguant un peu, je me suis rendu compte que faire ça diminue le facteur beta, soit le gain en courant du transistor. Autrement dit, la région d’amplification est beaucoup plus faible et il sature très rapidement. En diminuant la résistance de charge, le courant augmente drastiquement et on peut se servir du transistor branché normalement, puisqu’il reproduit bien l’onde carrée. Cela donne un pic inversé qui varie entre 4,9V et 0V :

En envoyant cela dans l’entrée d’horloge d’une bascule T, on crée une onde carrée avec un rapport cyclique de 50%, à la moitié de la fréquence des pulses d’entrée :

Donc après toutes ces étapes, on vient de recréer une horloge qui est synchronisée sur celle de la caméra (avec une phase décalée bien sûr), à 30Hz. Afin de contrôler les DELs, je vais la diviser encore en deux avec une autre bascule T, ce qui va produire deux ondes carrées à 15Hz déphasées de 180 degrés, autrement dit, les deux DELs vont clignoter en alternance à la fréquence de la caméra. Cela donne ceci :

Finalement, je me sers de ce signal pour moduler une autre onde carrée générée par un timer 555 avec un duty cycle faible, qui fait office de PWM pour mes DELs. Le résultat, appliqué sur la broche de contrôle des régulateurs de mes DELs, ressemble à ceci :

Et ça marche tout seul! Lorsqu’on allume le tout, les DELs se mettent à clignoter à la bonne fréquence comme par magie!

Analyse du flux vidéo de la caméra oxymétrique avec Processing

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!

Conception du détecteur de l’imagerie oxymétrique

Pour pouvoir mesurer le signal dans chaque fibre, je devais trouver un moyen de les imager avec ma caméra à haute sensibilité. J’ai tout d’abord réussi à trouver une lentille en ménisque convergent qui a une distance focale de 35 mm, en la collant directement sur l’objectif de la caméra, cela permet de rapprocher l’objet de la distance hyperfocale (approx 30 cm je crois) jusqu’à 35 mm, en diminuant le champ de vue par la même occasion.

J’ai donc imprimé deux pièces s’emboîtant en une boite en prisme cubique. La première est une plaque comportant 49 trous afin de faire passer toutes les fibres et de les aligner sur un même plan. La deuxième a un trou au centre pour y insérer la caméra ainsi qu’un petit remontant cylindrique afin d’y fixer la lentille. Le tout permet de bien maintenir toute l’optique solidement en position et dans l’obscurité, autant les fibres que la lentille et la caméra.

Une fois la boîte refermée, cela donne le détecteur assemblé visible sur les deux photos ci-dessus. D’un côté, il y a la caméra, bien insérée et collée, et de l’autre, le paquet de 49 fibres formant l’image. On peut voir le résultat capté par la caméra ci-dessous. L’espacement entre les fibres est tel qu’il permet à chacune d’être bien résolue et échantillonnée sur un bon nombre de pixels, même lorsque l’intensité lumineuse dans la fibre optique est très élevée. Dans le cas présent, j’avais pointé quelques fibres directement sur une ampoule incandescente. Bien évidemment, comme je n’utilise pas de lentille de champ à la sortie des fibres, celles sur le bord de l’image vont avoir une intensité captée par la caméra beaucoup plus faible que celles au centre, il va donc falloir faire une calibration pour appliquer la correction sur le signal mesuré.

 

Défrichage de fibre optique pour l’imagerie oxymétrique

Parce que oui, c’est possible de faire autre chose que des sapins de Noël avec de la fibre en PMMA à 10¢ le mètre. Je me suis lancé sérieusement, dans les derniers jours, dans mon projet d’imagerie oxymétrique. Pour commencer, j’ai découpé 147 bouts de 30 centimètres afin d’avoir une image composée d’un carré de 7×7 (ou toute autre forme comprenant 49 bouts de fibre sur l’image de la caméra).

Sources lumineuses

En utilisant deux sources de longueur d’onde différente, l’une dans le rouge, l’autre dans l’infrarouge, il est possible de déterminer le taux d’hémoglobine oxygénée par rapport à l’hémoglobine désoxygénée, en se basant sur les courbes d’absorption particulières. Ce principe est largement utilisé dans les sphygmo-oxymètres optiques, mesurant la concentration d’oxygène en un point (normalement un doigt) et supposant qu’elle est homogène dans le reste du corps. Or, la consommation d’oxygène par les organes varie selon l’effort énergétique qu’ils doivent fournir. En ayant une méthode qui permet de mesurer les variations d’oxygénation dans un volume donné, on ouvre la porte à toutes sortes d’applications intéressantes, comme l’imagerie spectroscopique proche infrarouge fonctionnelle (functional near-infrared imaging, fNIR, en anglais), ce qui est très excitant puisqu’elle permet de visualiser les processus mentaux en temps réel avec, ce que je souhaite vérifier, du matériel très peu coûteux et facile à trouver.

 (source : http://www.oximetry.org/pulseox/principles.htm)

Atténuation de la fibre

Résultats de recherche d'images pour « attenuation pmma fiber »

L’atténuation dans le proche-infrarouge pour le PMMA grimpe rapidement, comme on peut le voir sur le graphique. À 850nm, on a environ 5dB/m d’atténuation. Il faut donc garder les bouts de fibre très courts. Avec 30 centimètres, les pertes sont de 1,5dB (~71% du signal est transmis), ce qui fait mal, mais pas trop, espérons-le.

Matériel

Pour mes sources lumineuses, j’utilise deux DEL à très haute puissance optique (2,6W) afin d’être certain d’avoir suffisamment de signal. J’aurais pu utiliser des lasers, mais mon objectif était d’injecter simultanément toutes les fibres, le plus simplement possible. De plus, les DEL sont moins coûteuses et moins dangereuses (quoique à cette puissance, même si ce n’est pas un faisceau cohérent, j’ai tendance à être prudent quand même).

Je les avais déjà préalablement soudées l’année passée, lorsque j’avais commencé ce projet. Je leur ai rajouté un petit cylindre imprimé en 3D, qui se met directement autour du petit bulbe-lentille de la DEL et qui est suffisamment large pour y faire tenir les 49 fibres.

J’utilise pour les alimenter un power supply d’ordi, qui me donne 15V et 5A, le tout régulé à 700mA par diode par des régulateurs spécialisés pour cette application.

J’ai remarqué qu’allumées à pleine puissance, elles deviennent très chaudes très rapidement, ce qui va être à tenir en compte dans la suite du projet. Il va falloir soit les utiliser périodiquement avec un rapport cyclique faible, soit rajouter un système de refroidissement adéquat, ou bien y aller avec une combinaison de ces deux approches.

À 850nm, l’oeil perçoit un rouge très faible, sûrement la limite inférieure du spectre de la diode, qui est en fait bien plus lumineuse, tel qu’on peut l’observer avec la caméra qui lui donne une couleur mauve.

  

La DEL à 660nm est extrêmement brillante, puisque la quasi-totalité de son spectre est dans le visible. On peut voir que les fibres contiennent bien le faisceau.

Pour la caméra, j’utilise une caméra de drone de marque RunCam qui a une sensibilité absolument phénoménale pour son prix abordable : jusqu’à 0,0001 Lux (elle m’avait coûté 50$US si je me souviens bien). Je lui ai imprimé en 3D un petit adaptateur qui rassemble toutes les fibres et y fait tenir la caméra. Toutefois, comme on peut le voir sur la dernière image, ce sera à refaire puisque le focus de la caméra est à l’infini, ce qui mélange toutes les fibres en un gros blob de lumière. Il va donc falloir rajouter de l’optique pour imager correctement les fibres et arriver à les distinguer individuellement afin de pouvoir traiter le signal. C’est le pari à relever en ce moment dans ce projet.