TD3 - Suivi d'un marqueur coloré
1. Color tracking
L'objectif de cette première partie est de réaliser le suivi d'une marqueur coloré (un post-it) dans une flux vidéo. Pour ce faire, vous allez mettre en œuvre une technique standard de vision par ordinateur. Pour vous simplifier la tâche, vous allez utiliser la la bibliothèque OpenCV (Open Computer Vision) via le wrapper JavaScript opencv4nodejs pour Node.js.

Téléchargez le squelette de code rvra_td3.zip et installez les modules
nécessaire en exécutant la commande npm install
depuis le répertoire décompressé. Sur les machines du
CREMI, pour accéder à la version correcte de Node.js et d'OpenCV, exécutez au préalable les commandes :
export PATH=/opt/users/Node.js/bin:$PATH
export LD_LIBRARY_PATH=/opt/local/lib:$LD_LIBRARY_PATH
npm start
. Dans un navigateur web, ouvrez l'URL http://localhost:3000/config : le flux vidéo de la caméra devrait s'y afficher. Lisez le code
fourni dans 'main.js' et 'public/config.html' ; en vous aidant de la documentation des différents modules,
analysez comment les images du flux vidéo sont récupérées sur le serveur et envoyées au client via socket.io.
Vous pouvez sélectionner par une clic souris la couleur du marqueur à suivre. Cette couleur est alors mise à jour dans le menu (pour éventuellement la modifier à la main) et transmise au serveur à l'aide d'une requête asynchrone.
Le pipeline de base que vous devez mettre en place sur le serveur est le suivant :
- Seuillez l'image couleur à l'aide la fonction
inRange
(doc.). - Lors du seuillage, une certaine tolérance sera nécessaire autour de la couleur sélectionnée ; permettez à l'utilisateur de la contrôler (ajoutez ce paramètre au menu).
- Pour diminuer le bruit dans l'image seuillée, appliquez une ouverture morphologique à l'aide des focntion
erode
etdilate
. - Détectez les contours dans l'image binaire obtenue à l'aide de la fonction
findContours
. - Parmi les N polygones détectés, conservez le plus grand ; calculez et afficher sur l'image son cercle englobant.
Vous constaterez que l'espace de couleur RGB n'est pas très robuste aux variations des conditions d'éclairage. Pour améliorer les résultats, effectuez le seuillage dans l'espace HSV sur le canal de teinte H. Vous pouvez arbitrairement fixer les bornes pour les autres canaux S et V (à [100,255] par exemple).
Pour travailler sans caméra, vous pouvez utiliser la vidéo suivante : PostIt.mov. Utilisez alors le code suivant pour ouvrir la capture vidéo OpenCV :const wCap = new cv.VideoCapture(path.join(__dirname, 'PostIt.mov' ));
2. Stéréo et tracking
Vous allez utiliser le code de tracking précédent pour suivre la tête de l'observateur et en estimer la distance à l'écran (profondeur), en supposant que la direction de vue reste orthogonale à l'écran. Le calcul de la profondeur ne peut néanmoins pas se faire en se basant uniquement sur l'image 2D : une information supplémentaire est nécessaire, le rapport entre la taille réelle du marqueur et sa taille après projection dans l'image. Pour cela, nous avons besoin des paramètres intrinsèques de la caméra.
2.1. Calibration de la caméra
La calibration de la caméra consiste à retrouver les transformations qu'un point dans l'espace subit pour être projeté sur l'image. Dans ce TD, nous nous intéresserons seulement au cas particulier du modèle de caméra tête d'épingle (pinhole camera).
Dans cette configuration trois transformations sont à estimer :
- la transformation liée aux paramètres intrinsèques de la caméra (distance focale et coordonnées du centre optique)
- les distorsions dues à la lentille.
- la transformation liée à ses paramètres extrinsèques (position et orientation)
Les deux premières sont constantes pour une caméra donnée et peuvent donc être pré-calculées. Plusieurs solutions existent pour calibrer une caméra, notamment avec OpenCV. Lisez la documentation et expliquez dans votre rapport le fonctionnement général d'une calibration de caméra.
Compilez et utilisez le code fournit correspondant à ce tutoriel OpenCV pour déterminer la matrice des paramètres intrinsèques et les coefficients de distorsion de votre caméra.
Pour travailler sans caméra, vous pouvez utiliser la vidéo suivante : calibration.mov. Modifiez alors la ligne 19 du fichier de configuration param.xml
comme suit :<Input>"calibration.mov"</Input>
2.2. Calcul de la profondeur
Copiez dans votre code de suivi d'un marqueur coloré les paramètres intrinsèques précédemment estimés. En utilisant cette information, proposez un schéma représentant la scène et les différents éléments nécessaires au calcul de la profondeur. Modifiez ensuite le code de votre tracker pour convertir la position du centre du cercle englobante dans l'image (en pixels) en positions 3D (en mm) dans le repère de la caméra.
Intégrez enfin le code du rendu stéréo du TD précédent (code fourni dans le dossier public). L'objectif
est de placer la caméra virtuelle à la position précédemment calculée. Pour transmettre cette information du serveur
au client, utilisez à nouveau socket.io (utilisez le nom de votre
machine à la place de localhost
). Testez en collant le marqueur coloré sur un Google Cardboard.
2.3. Bonus : fiducial marker
La robustesse et la précision du suivi avec un marqueur coloré est faible. En outre, un tel marqueur ne permettent pas d'obtenir l'orientation 3D du marqueur par rapport à la caméra (paramètres extrinsèques). Une façon plus robuste d'y parvenir est d'utiliser un (voire plusieurs) marqueur(s) possédant un motif binaire distinctif.
ARToolKit a été l'une des premières bibliothèques logicielles implémentant une telle approche. De nombreuses alternatives ont été ensuite proposées, améliorant aussi bien les motifs des marqueurs (cf. figure ci-jointe) que les algorithmes de tracking. Pour ce TD, vous utiliserez la bibliothèque ArUco via le portage JavaScript js-aruco.

En vous aidant de la (succincte) documentation, remplacer votre color tracking par l'estimation de la pose
de cette bibliothèque. Comme celle-ci n'est pas basée sur opencv4nodejs, vous aurez besoin
de convertir une cv.Mat
en
ImageData
; utilisez pour cela la bibliothèque pixel-util et le code suivant :
const imgData = pixelUtil.createImageData(frame.cols, frame.rows);
const matRGBA = frame.channels === 1
? frame.cvtColor(cv.COLOR_GRAY2RGBA)
: frame.cvtColor(cv.COLOR_BGR2RGBA);
imgData.data = matRGBA.getData();
Pour travailler sans caméra, vous pouvez utiliser la vidéo suivante : ArUco.mov.