Réalité augmentée simple avec OpenCV, Three.js et WebSockets

Publié: 2022-03-10
Résumé rapide ↬ Dans ce didacticiel, nous utiliserons OpenCV en Python pour détecter des objets en forme de cercle dans un flux de webcam et les remplacer par 3D Earth dans Three.js dans une fenêtre de navigateur tout en utilisant WebSockets pour joindre tout cela ensemble.

La réalité augmentée est généralement considérée comme très difficile à créer. Cependant, il est possible de réaliser des projets visuellement impressionnants en utilisant uniquement des bibliothèques open source. Dans ce didacticiel, nous utiliserons OpenCV en Python pour détecter des objets en forme de cercle dans un flux de webcam et les remplacer par 3D Earth dans Three.js dans une fenêtre de navigateur tout en utilisant WebSockets pour joindre le tout.

Nous voulons séparer strictement le front-end et le back-end afin de le rendre réutilisable. Dans une application du monde réel, nous pourrions écrire le front-end dans Unity, Unreal Engine ou Blender, par exemple, pour le rendre vraiment agréable. Le frontal du navigateur est le plus facile à mettre en œuvre et devrait fonctionner sur presque toutes les configurations possibles.

Pour simplifier les choses, nous allons diviser l'application en trois parties plus petites :

  1. Le back-end Python avec OpenCV OpenCV lira le flux de la webcam et ouvrira plusieurs fenêtres avec l'image de la caméra après l'avoir passée à travers plusieurs filtres pour faciliter le débogage et nous donner un petit aperçu de ce que l'algorithme de détection de cercle voit réellement. La sortie de cette partie sera uniquement les coordonnées 2D et le rayon du cercle détecté.
  2. Front-end JavaScript avec Three.js dans un navigateur Implémentation étape par étape de la bibliothèque Three.js pour rendre la Terre texturée avec la lune tournant autour d'elle. La chose la plus intéressante ici sera de mapper les coordonnées de l'écran 2D dans le monde 3D. Nous allons également approximer les coordonnées et le rayon pour augmenter la précision d'OpenCV.
  3. WebSockets dans le front-end et le back-end Le back-end avec le serveur WebSockets enverra périodiquement des messages avec des coordonnées de cercle et des rayons détectés au client du navigateur.
Résultat final
Plus après saut! Continuez à lire ci-dessous ↓

1. Back-end Python avec OpenCV

Notre première étape consistera simplement à importer la bibliothèque OpenCV en Python et à ouvrir une fenêtre avec un flux de webcam en direct.

Nous allons utiliser le plus récent OpenCV 3.0 (voir les notes d'installation) avec Python 2.7. Veuillez noter que l'installation sur certains systèmes peut être problématique et que la documentation officielle n'est pas très utile. Je me suis essayé sur Mac OS X version 3.0 de MacPorts et le binaire avait un problème de dépendance, j'ai donc dû passer à Homebrew à la place. Notez également que certains packages OpenCV peuvent ne pas être fournis avec la liaison Python par défaut (vous devez utiliser certaines options de ligne de commande).

Avec Homebrew j'ai lancé :

 brew install opencv

Cela installe OpenCV avec les liaisons Python par défaut.

Juste pour tester les choses, je vous recommande d'exécuter Python en mode interactif (exécuter python en CLI sans aucun argument) et d'écrire import cv2 . Si OpenCV est installé correctement et que les chemins vers les liaisons Python sont corrects, aucune erreur ne devrait être générée.

Plus tard, nous utiliserons également numpy de Python pour certaines opérations simples avec des matrices afin que nous puissions également l'installer maintenant.

 pip install numpy

Lecture de l'image de la caméra

Nous pouvons maintenant tester la caméra :

 import cv2 capture = cv2.VideoCapture(0) while True: ret, image = capture.read() cv2.imshow('Camera stream', image) if cv2.waitKey(1) & 0xFF == ord('q'): break

Avec cv2.VideoCapture(0) , nous avons accès à la caméra sur l'index 0 qui est la valeur par défaut (généralement la caméra intégrée). Si vous souhaitez en utiliser un autre, essayez des nombres supérieurs à zéro ; cependant, il n'y a pas de moyen facile de répertorier toutes les caméras disponibles avec la version actuelle d'OpenCV.

Lorsque nous appelons cv2.imshow('Camera stream', image) pour la première fois, il vérifie qu'aucune fenêtre portant ce nom n'existe et en crée une nouvelle pour nous avec une image de la caméra. La même fenêtre sera réutilisée pour chaque itération de la boucle principale.

Ensuite, nous avons utilisé capture.read() pour attendre et saisir l'image actuelle de la caméra. Cette méthode renvoie également une propriété booléenne ret au cas où la caméra est déconnectée ou si l'image suivante n'est pas disponible pour une raison quelconque.

À la fin, nous avons cv2.waitKey(1) qui vérifie pendant 1 milliseconde si une touche est enfoncée et renvoie son code. Ainsi, lorsque nous appuyons sur q , nous sortons de la boucle, fermons la fenêtre et l'application se terminera.

Si tout cela fonctionne, nous avons passé la partie la plus difficile de l'application back-end qui consiste à faire fonctionner la caméra.

Filtrage des images de la caméra

Pour la détection de cercle proprement dite, nous allons utiliser le cercle Hough Transform qui est implémenté dans la méthode cv2.HoughCircles() et est actuellement le seul algorithme disponible dans OpenCV. L'important pour nous est qu'il a besoin d'une image en niveaux de gris en entrée et utilise l'algorithme de détection de bord Canny à l'intérieur pour trouver des bords dans l'image. Nous voulons être en mesure de vérifier manuellement ce que l'algorithme voit, nous allons donc composer une grande image à partir de quatre images plus petites, chacune avec un filtre différent appliqué.

Le détecteur de bords Canny est un algorithme qui traite l'image dans quatre directions (verticale, horizontale et deux diagonales) et trouve les bords. Les étapes réelles de cet algorithme sont expliquées plus en détail sur Wikipedia ou brièvement dans la documentation OpenCV.

Contrairement à la correspondance de motifs, cet algorithme détecte les formes circulaires afin que nous puissions utiliser tous les objets circulaires que nous avons sous la main. Je vais utiliser un couvercle d'un pot de café instantané, puis une tasse à café orange.

Nous n'avons pas besoin de travailler avec des images en taille réelle (cela dépend de la résolution de votre appareil photo, bien sûr), nous les redimensionnerons donc entre capture.read() et cv2.imshow à 640px de largeur et de hauteur en conséquence pour conserver le rapport d'aspect :

 width, height = image.shape scale = 640.0 / width image = cv2.resize(image, (0,0), fx=scale, fy=scale)

Ensuite, nous voulons le convertir en une image en niveaux de gris et appliquer d'abord le flou médian qui supprime le bruit et conserve les bords, puis le détecteur de bord Canny pour voir avec quoi l'algorithme de détection de cercle va fonctionner. Pour cette raison, nous allons composer une grille 2x2 avec les quatre aperçus.

 t = 100 # threshold for Canny Edge Detection algorithm grey = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blured = cv2.medianBlur(grey, 15) # Create 2x2 grid for all previews grid = np.zeros([2*h, 2*w, 3], np.uint8) grid[0:h, 0:w] = image # We need to convert each of them to RGB from greyscaled 8 bit format grid[h:2*h, 0:w] = np.dstack([cv2.Canny(grey, t / 2, t)] * 3) grid[0:h, w:2*w] = np.dstack([blured] * 3) grid[h:2*h, w:2*w] = np.dstack([cv2.Canny(blured, t / 2, t)] * 3) 
Grille avec aperçus. En haut à gauche : données brutes de la webcam ; en haut à droite : niveaux de gris après le flou médian ; en bas à gauche : niveaux de gris et bordure Canny ; en bas à droite : niveaux de gris après le flou médian et le bord Canny.

Même si le détecteur de bord Canny utilise le flou gaussien pour réduire le bruit, d'après mon expérience, il vaut toujours la peine d'utiliser également le flou médian. Vous pouvez comparer les deux images du bas. Celui de gauche est juste une détection de bord Canny sans aucun autre filtre. La deuxième image est également une détection de contour Canny mais cette fois après l'application d'un flou médian. Il a réduit les objets en arrière-plan, ce qui facilitera la détection des cercles.

Détection de cercles avec un gradient élevé

En interne, OpenCV utilise une implémentation plus efficace de Hough Circle Transform appelée Hough Gradient Method qui utilise les informations de bord du détecteur de bord Canny. La méthode du gradient est décrite en détail dans le livre Learning OpenCV and the Circle Hough Transform sur Wikipedia .

Il est maintenant temps pour la détection de cercle proprement dite :

 sc = 1 # Scale for the algorithm md = 30 # Minimum required distance between two circles # Accumulator threshold for circle detection. Smaller numbers are more # sensitive to false detections but make the detection more tolerant. at = 40 circles = cv2.HoughCircles(blured, cv2.HOUGH_GRADIENT, sc, md, t, at)

Ceci renvoie un tableau de tous les cercles détectés. Par souci de simplicité, nous nous intéresserons uniquement au premier. Hough Gradient est assez sensible aux formes vraiment circulaires, il est donc peu probable que cela entraîne de fausses détections. Si c'est le cas, augmentez le paramètre at . C'est pourquoi nous avons utilisé le flou médian ci-dessus ; il a supprimé plus de bruit afin que nous puissions utiliser un seuil plus bas, ce qui rend la détection plus tolérante aux inexactitudes et avec une moindre chance de détecter de faux cercles.

Nous imprimerons le centre du cercle et son rayon sur la console et dessinerons également le cercle trouvé avec son centre sur l'image de la caméra dans une fenêtre séparée. Plus tard, nous l'enverrons via WebSocket au navigateur. Notez que x , y et radius sont tous en pixels.

 if circles is not None: # We care only about the first circle found. circle = circles[0][0] x, y, radius = int(circle[0]), int(circle[1]), int(circle[2]) print(x, y, radius) # Highlight the circle cv2.circle(image, [x, y], radius, (0, 0, 255), 1) # Draw a dot in the center cv2.circle(image, [x, y], 1, (0, 0, 255), 1)

Cela imprimera sur les tuples de la console comme :

 (251, 202, 74) (252, 203, 73) (250, 202, 74) (246, 202, 76) (246, 204, 74) (246, 205, 72) 
Flux de webcam avec cercles détectés à l'aide de Hough Gradient.

Comme vous pouvez le voir sur cette animation, il n'a trouvé aucun cercle. Mon appareil photo intégré n'a que 15 images par seconde et lorsque je bouge rapidement ma main, l'image est floue, de sorte qu'elle ne trouve pas les bords du cercle, même après l'application de filtres.

À la fin de cet article, nous reviendrons sur ce problème et parlerons beaucoup des paramètres spécifiques à la caméra et du choix de l'algorithme de détection, mais nous pouvons déjà dire que même si ma configuration est très mauvaise (seulement 15 images par seconde, un mauvais éclairage, un beaucoup de bruit en arrière-plan, l'objet a un faible contraste), le résultat est assez bon.

C'est tout pour le moment. Nous avons les coordonnées x et y et le radius en pixels d'un cercle trouvé dans l'image de la webcam.

Vous pouvez voir le code source complet de cette partie sur gist.github.com.

2. JavaScript Front-End avec Three.js dans les navigateurs

La partie front-end est basée sur la librairie Three.js (version r72). Nous allons commencer par créer une sphère texturée rotative représentant la Terre au centre de l'écran, puis ajouter la lune tournant autour d'elle. À la fin, nous mapperons les coordonnées de la souris de l'écran 2D dans l'espace 3D.

Notre page HTML sera composée d'un seul élément <canvas> . voir index.html sur gist.github.com.

Créer la Terre

JavaScript va être un peu plus long mais il est divisé en plusieurs fonctions d'initialisation où chacune a un seul but. Les textures de la terre et de la lune proviennent de planetpixelemporium.com. Notez que lors du chargement des textures, les règles CORS sont appliquées.

 var scene, camera, renderer, light, earthMesh, earthRotY = 0; function initScene(width, height) { scene = new THREE.Scene(); // Setup cameta with 45 deg field of view and same aspect ratio camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000); // Set the camera to 400 units along `z` axis camera.position.set(0, 0, 400); renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setSize(width, height); renderer.shadowMap.enabled = true; document.body.appendChild(renderer.domElement); } function initLight() { light = new THREE.SpotLight(0xffffff); // Position the light slightly to a side to make shadows look better. light.position.set(400, 100, 1000); light.castShadow = true; scene.add(light); } function initEarth() { // Load Earth texture and create material from it var earthMaterial = new THREE.MeshLambertMaterial({ map: THREE.ImageUtils.loadTexture("/images/earthmap1k.jpg"), }); // Create a sphere 25 units in radius and 16 segments // both horizontally and vertically. var earthGeometry = new THREE.SphereGeometry(25, 16, 16); earthMesh = new THREE.Mesh(earthGeometry, earthMaterial); earthMesh.receiveShadow = true; earthMesh.castShadow = true; // Add Earth to the scene scene.add(earthMesh); } // Update position of objects in the scene function update() { earthRotY += 0.007; earthMesh.rotation.y = earthRotY; } // Redraw entire scene function render() { update(); renderer.setClearColor(0x000000, 0); renderer.render(scene, camera); // Schedule another frame requestAnimationFrame(render); } document.addEventListener('DOMContentLoaded', function(e) { // Initialize everything and start rendering initScene(window.innerWidth, window.innerHeight); initEarth(); initLight(); // Start rendering the scene requestAnimationFrame(render); });

Voir une démo en direct ici.

troisjs-tourner-la-terre
Sphère texturée avec Three.js. (Crédit texture Terre)

Il s'agissait principalement de trucs de base de Three.js. Les noms d'objet et de méthode sont explicites (comme receiveShadow ou castShadow ) mais si vous ne l'avez jamais utilisé auparavant, je vous recommande fortement de consulter les tutoriels de Lee Stemkoski.

En option, nous pourrions également dessiner un axe au centre de l'écran pour nous aider avec le système de coordonnées.

 var axes = new THREE.AxisHelper(60); axes.position.set(0, 0, 0); scene.add(axes);

Ajout de la lune

Créer la lune va être très similaire. La principale différence est que nous devons définir la position de la lune par rapport à la Terre.

 function initMoon() { // The same as initEarth() with just different texture } // Update position of objects in the scene function update() { // Update Earth position // ... // Update Moon position moonRotY += 0.005; radY += 0.03; radZ += 0.0005; // Calculate position on a sphere x = moonDist * Math.cos(radZ) * Math.sin(radY); y = moonDist * Math.sin(radZ) * Math.sin(radY); z = moonDist * Math.cos(radY); var pos = earthMesh.position; // We can keep `z` as is because we're not moving the Earth // along z axis. moonMesh.position.set(x + earthMesh.pos.x, y + earthMesh.pos.y, z); moonMesh.rotation.y = moonRotY; }

Voir une démo en direct ici.

Terre et lune avec Three.js. (Crédit textures terre et lune)

Mappage de coordonnées 2D sur un monde 3D

Jusqu'à présent, tout est assez évident. La partie la plus intéressante sera de savoir comment convertir les coordonnées d'écran 2D provenant d'OpenCV (voir la sortie de la détection circulaire ci-dessus) en un monde 3D ? Lorsque nous avons défini les rayons et les positions dans Three.js, nous avons utilisé certaines unités, mais celles-ci n'ont rien à voir avec les pixels d'écran réels. En fait, les dimensions de tout ce que nous voyons dans la scène dépendent fortement des paramètres de notre caméra (comme le format d'image ou le champ de vision).

Pour cette raison, nous allons créer un objet plan plat qui sera suffisamment grand pour couvrir toute la scène avec son centre à [0,0,0] . À des fins de démonstration, nous mapperons les coordonnées de la souris 2D sur la position de la Terre en 3D avec un axe z fixe. En d'autres termes, nous ne convertirons que x et y et ne nous soucierons pas de z , qui est la distance entre l'objet et notre caméra.

Nous allons convertir les positions de l'écran de la souris dans une plage de -1.0 à +1.0 avec son centre à [0,0] car nous devons travailler avec des vecteurs normalisés.

Plus tard, nous utiliserons cette technique exacte pour mapper la position du cercle détecté en 3D et également pour faire correspondre la taille du cercle de 2D à 3D.

 var mouse = {}; function initPlane() { // The plane needs to be large to always cover entire scene var tmpGeometry = new THREE.PlaneGeometry(1000, 1000, 1, 1); tmpGeometry.position = new THREE.Vector3(0, 0, 0); var tmpMesh = new THREE.Mesh(tmpGeometry); } function onDocumentMouseMove(event) { // Current mouse position with [0,0] in the center of the window // and ranging from -1.0 to +1.0 with `y` axis inverted. mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = - (event.clientY / window.innerHeight) * 2 + 1; } function update() { // ... the rest of the function // We need mouse x and y coordinates to set vector's direction var vector = new THREE.Vector3(mouse.x, mouse.y, 0.0); // Unproject camera distortion (fov, aspect ratio) vector.unproject(camera); var norm = vector.sub(camera.position).normalize(); // Cast a line from our camera to the tmpMesh and see where these // two intersect. That's our 2D position in 3D coordinates. var ray = new THREE.Raycaster(camera.position, norm); var intersects = ray.intersectObject(tmpMesh); earthMesh.position.x = intersects[0].point.x; earthMesh.position.y = intersects[0].point.y; }

Voir une démo en direct ici.

troisjs-tourner-la-terre
Position de la Terre en 3D mappée à la position de la souris 2D. (Crédit textures terre et lune)

Puisque nous vérifions l'intersection avec un avion, nous savons qu'il n'y en aura toujours qu'un.

C'est tout pour cette partie. À la fin de la partie suivante, nous ajouterons également WebSockets et un élément <video> avec notre flux de caméra qui sera recouvert par la scène 3D dans Three.js.

3. WebSockets dans le front-end et le back-end

Nous pouvons commencer par implémenter WebSockets dans le back-end Python en installant des bibliothèques simple-websocket-server . Il existe de nombreuses bibliothèques différentes comme Tornado ou Autobahn. Nous utiliserons simple-websocket-server car il est très facile à utiliser et n'a aucune dépendance.

 pip install git+https://github.com/dpallot/simple-websocket-server.git

Nous exécuterons le serveur WebSocket dans un thread séparé et garderons une trace de tous les clients connectés.

 from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket clients = [], server = None class SimpleWSServer(WebSocket): def handleConnected(self): clients.append(self) def handleClose(self): clients.remove(self) def run_server(): global server server = SimpleWebSocketServer(', 9000, SimpleWSServer, selectInterval=(1000.0 / 15) / 1000) server.serveforever() t = threading.Thread(target=run_server) t.start() # The rest of the OpenCV code ...

Nous avons utilisé le paramètre selectInterval dans le constructeur du serveur pour qu'il vérifie périodiquement les messages en attente. Le serveur envoie des messages uniquement lorsqu'il reçoit des données des clients, ou il doit s'asseoir sur le thread principal dans une boucle. Nous ne pouvons pas le laisser bloquer le thread principal car OpenCV en a également besoin. Puisque nous savons que la caméra ne fonctionne qu'à 15 ips, nous pouvons utiliser le même intervalle sur le serveur WebSocket.

Ensuite, après avoir détecté les cercles, nous pouvons parcourir tous les clients connectés et envoyer la position et le rayon actuels par rapport à la taille de l'image.

 for client in clients: msg = json.dumps({'x': x / w, 'y': y / h, 'radius': radius / w}) client.sendMessage(unicode(msg))

Vous pouvez voir que le code source complet du serveur est sur gist.github.com.

La partie JavaScript imitera le même comportement que nous avons fait avec la position de la souris. Nous garderons également une trace des quelques messages et calculerons une valeur moyenne pour chaque axe et rayon pour améliorer la précision.

 var history = []; var ws = new WebSocket('ws://localhost:9000'); ws.onopen = function() { console.log('onopen'); }; ws.onmessage = function (event) { var m = JSON.parse(event.data); history.push({ x: mx * 2 - 1, y: -my * 2 + 1, radius: m.radius}); // ... rest of the function. };

Au lieu de définir la position de la Terre sur la position actuelle de ma souris, nous utiliserons la variable msgHistory .

Il n'est probablement pas nécessaire de coller l'intégralité du code ici, alors n'hésitez pas à consulter les détails d'implémentation sur gist.gihtub.com.

Ajoutez ensuite un élément <video> avec le flux de webcam remplissant toute la fenêtre qui sera recouverte par notre scène 3D avec un fond transparent.

 var videoElm = document.querySelector('video'); // Make sure the video fits the window. var constrains = { video: { mandatory: { minWidth: window.innerWidth }}}; if (navigator.getUserMedia) { navigator.getUserMedia(constrains, function(stream) { videoElm.src = window.URL.createObjectURL(stream); // When the webcam stream is ready get it's dimensions. videoElm.oncanplay = function() { init(videoElm.clientWidth, videoElm.clientHeight); // Init everything ... requestAnimationFrame(render); } }, function() {}); }

Le résultat final :

Application finale avec la Terre mappée aux coordonnées trouvées par OpenCV.

Pour résumer rapidement ce que nous avons fait et ce que montre la vidéo ci-dessus :

  1. Le back-end Python exécute un serveur WebSocket.
  2. Le serveur détecte un cercle à l'aide d'OpenCV à partir d'un flux de webcam.
  3. Le client JavaScript affiche le même flux de webcam à l'aide de l'élément <video> .
  4. Le client rend la scène 3D à l'aide de Three.js.
  5. Le client se connecte au serveur via le protocole WebSocket et reçoit la position et le rayon du cercle.

Le code réel utilisé pour cette démo est disponible sur GitHub. Il est légèrement plus sophistiqué et interpole également les coordonnées entre deux messages du back-end car le flux de la webcam ne fonctionne qu'à 15 ips tandis que la scène 3D est rendue à 60 ips. Vous pouvez voir la vidéo originale sur YouTube.

Mises en garde

Il y a quelques découvertes à noter :

La détection de cercle n'est pas idéale

C'est formidable que cela fonctionne avec n'importe quel objet circulaire, mais il est très sensible au bruit et à la déformation de l'image, même si, comme vous pouvez le voir ci-dessus, notre résultat est plutôt bon. De plus, il n'y a probablement pas d'exemples pratiques de détection de cercle disponibles en dehors de l'utilisation la plus basique. Il serait peut-être préférable d'utiliser la détection d'ellipse, mais elle n'est pas implémentée dans OpenCV pour le moment.

Tout dépend de votre configuration

Les webcams intégrées sont généralement assez mauvaises. 15 ips ne suffit pas et le simple fait de l'augmenter à 30 ips réduit considérablement le flou de mouvement et rend la détection plus fiable. Nous pouvons décomposer ce point en quatre autres points :

  • Distorsions de la caméra
    De nombreuses caméras introduisent une certaine distorsion de l'image, le plus souvent un effet fish-eye qui a une influence significative sur la détection de forme. La documentation d'OpenCV contient un didacticiel très simple sur la façon de réduire la distorsion en calibrant votre appareil photo.
  • Il n'y a pas de liste officielle des appareils pris en charge par OpenCV
    Même si vous avez déjà un bon appareil photo, il se peut qu'il ne fonctionne pas avec OpenCV sans autre explication. J'ai également lu des informations sur des personnes utilisant une autre bibliothèque pour capturer une image de caméra (comme libdc1394 pour les caméras basées sur IEEE 1394), puis utilisant OpenCV uniquement pour traiter les images. Le gestionnaire de packages Brew vous permet de compiler OpenCV directement avec le support libdc1394.
  • Certaines caméras fonctionnent mieux avec OpenCV que d'autres
    Si vous avez de la chance, vous pouvez définir certaines options de l'appareil photo comme les images par seconde directement sur votre appareil photo, mais cela peut également n'avoir aucun effet si OpenCV n'est pas compatible avec votre appareil. Encore une fois, sans aucune explication.
  • Tous les paramètres dépendent d'une utilisation réelle
    Lorsqu'il est utilisé dans une installation réelle, il est fortement recommandé de tester les algorithmes et les filtres dans l'environnement réel, car des éléments tels que les lumières, la couleur d'arrière-plan ou le choix des objets ont des effets significatifs sur le résultat. Cela inclut également les ombres de la lumière du jour, les personnes debout, etc.

La correspondance de motifs est généralement un meilleur choix

Si vous voyez une réalité augmentée utilisée dans la pratique, elle sera probablement basée sur la correspondance de modèles. Il est généralement plus fiable et moins affecté par les problèmes décrits ci-dessus.

Les filtres sont cruciaux

Je pense que l'utilisation correcte des filtres nécessite une certaine expérience et toujours un peu de magie. Le temps de traitement de la plupart des filtres dépend de leurs paramètres, bien que dans OpenCV 3.0 certains d'entre eux soient déjà réécrits en CUDA C (un langage de type C pour la programmation hautement parallèle avec les cartes graphiques NVIDIA) ce qui apporte des améliorations de performances significatives.

Filtrer les données d'OpenCV

Nous avons vu que la détection de cercle a quelques imprécisions : parfois, elle ne trouve aucun cercle ou détecte le mauvais rayon. Pour minimiser ce type d'erreur, il serait utile de mettre en œuvre une méthode plus sophistiquée pour améliorer la précision. Dans notre exemple, nous avons utilisé la médiane pour x , y et le radius , ce qui est très simple. Un filtre couramment utilisé avec de bons résultats est le filtre de Kalman, utilisé par les pilotes automatiques pour les drones afin de réduire l'imprécision provenant des capteurs. Cependant, sa mise en œuvre n'est pas aussi simple que d'utiliser simplement math.mean() de https://mathjs.org.

Conclusion

J'ai vu pour la première fois une application similaire au Musée national d'histoire naturelle de Madrid il y a deux ans et je me suis demandé à quel point il serait difficile de faire quelque chose de similaire.

Mon idée principale derrière cette démo était d'utiliser des outils courants sur le Web (comme WebSockets et Three.js) et ne nécessitant aucun prérequis afin que tout le monde puisse commencer à les utiliser immédiatement. C'est pourquoi je voulais utiliser uniquement la détection de cercle et non la correspondance de motifs, ce qui nécessiterait d'imprimer ou d'avoir un objet particulier du monde réel.

Je dois dire que j'ai gravement sous-estimé les exigences réelles en matière de caméra. Un nombre élevé d'images par seconde et un bon éclairage sont plus importants que la résolution. Je ne m'attendais pas non plus à ce que l'incompatibilité de la caméra avec OpenCV soit un problème.