Semplice realtà aumentata con OpenCV, Three.js e WebSocket

Pubblicato: 2022-03-10
Riepilogo rapido ↬ In questo tutorial, utilizzeremo OpenCV in Python per rilevare oggetti a forma di cerchio in un flusso di webcam e sostituirli con 3D Earth in Three.js in una finestra del browser mentre utilizziamo WebSocket per unire tutto questo.

La realtà aumentata è generalmente considerata molto difficile da creare. Tuttavia, è possibile realizzare progetti visivamente impressionanti utilizzando solo librerie open source. In questo tutorial, utilizzeremo OpenCV in Python per rilevare oggetti a forma di cerchio in un flusso di webcam e sostituirli con 3D Earth in Three.js in una finestra del browser mentre utilizziamo WebSocket per unirli tutti insieme.

Vogliamo separare rigorosamente front-end e back-end per renderlo riutilizzabile. In un'applicazione del mondo reale potremmo scrivere il front-end in Unity, Unreal Engine o Blender, ad esempio, per renderlo davvero bello. Il front-end del browser è il più semplice da implementare e dovrebbe funzionare su quasi tutte le possibili configurazioni.

Per semplificare le cose, divideremo l'app in tre parti più piccole:

  1. Back-end Python con OpenCV OpenCV leggerà il flusso della webcam e aprirà più finestre con l'immagine della telecamera dopo averlo passato attraverso più filtri per facilitare il debug e darci un'idea di ciò che vede effettivamente l'algoritmo di rilevamento del cerchio. L'output di questa parte sarà solo coordinate 2D e raggio del cerchio rilevato.
  2. Front-end JavaScript con Three.js in un browser Implementazione passo passo della libreria Three.js per il rendering della Terra strutturata con la luna che ruota attorno ad essa. La cosa più interessante qui sarà mappare le coordinate dello schermo 2D nel mondo 3D. Approssimeremo anche le coordinate e il raggio per aumentare la precisione di OpenCV.
  3. WebSocket sia nel front-end che nel back-end Il server back-end con WebSocket invierà periodicamente messaggi con le coordinate del cerchio ei raggi rilevati al client del browser.
Risultato finale
Altro dopo il salto! Continua a leggere sotto ↓

1. Back-end Python con OpenCV

Il nostro primo passo sarà semplicemente importare la libreria OpenCV in Python e aprire una finestra con uno streaming live della webcam.

Utilizzeremo il nuovissimo OpenCV 3.0 (vedi note di installazione) con Python 2.7. Tieni presente che l'installazione su alcuni sistemi potrebbe essere problematica e la documentazione ufficiale non è molto utile. Ho provato io stesso su Mac OS X versione 3.0 da MacPorts e il binario aveva un problema di dipendenza, quindi ho dovuto passare a Homebrew. Nota inoltre che alcuni pacchetti OpenCV potrebbero non essere dotati di associazione Python per impostazione predefinita (è necessario utilizzare alcune opzioni della riga di comando).

Con Homebrew ho eseguito:

 brew install opencv

Questo installa OpenCV con i collegamenti Python per impostazione predefinita.

Solo per testare le cose, ti consiglio di eseguire Python in modalità interattiva (esegui python in CLI senza argomenti) e di scrivere import cv2 . Se OpenCV è installato correttamente e i percorsi per i collegamenti Python sono corretti, non dovrebbe generare alcun errore.

Successivamente, useremo anche numpy di Python per alcune semplici operazioni con le matrici in modo da poterlo installare anche ora.

 pip install numpy

Lettura dell'immagine della fotocamera

Ora possiamo testare la fotocamera:

 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

Con cv2.VideoCapture(0) otteniamo l'accesso alla telecamera sull'indice 0 che è l'impostazione predefinita (di solito la telecamera incorporata). Se vuoi usarne uno diverso, prova con numeri maggiori di zero; tuttavia, non esiste un modo semplice per elencare tutte le fotocamere disponibili con l'attuale versione di OpenCV.

Quando chiamiamo cv2.imshow('Camera stream', image) per la prima volta, controlla che non esista alcuna finestra con questo nome e ne crea una nuova per noi con un'immagine dalla fotocamera. La stessa finestra verrà riutilizzata per ogni iterazione del ciclo principale.

Quindi abbiamo utilizzato capture.read() per attendere e acquisire l'immagine della fotocamera corrente. Questo metodo restituisce anche una proprietà booleana ret nel caso in cui la fotocamera sia disconnessa o il fotogramma successivo non sia disponibile per qualche motivo.

Alla fine abbiamo cv2.waitKey(1) che controlla per 1 millisecondo se viene premuto un tasto e ne restituisce il codice. Quindi, quando premiamo q usciamo dal ciclo, chiudiamo la finestra e l'app terminerà.

Se tutto funziona, abbiamo superato la parte più difficile dell'app di back-end che consiste nel far funzionare la fotocamera.

Filtraggio delle immagini della fotocamera

Per il rilevamento effettivo del cerchio useremo circle Hough Transform che è implementato nel metodo cv2.HoughCircles() e in questo momento è l'unico algoritmo disponibile in OpenCV. La cosa importante per noi è che ha bisogno di un'immagine in scala di grigi come input e utilizza l'algoritmo Canny edge detector all'interno per trovare i bordi nell'immagine. Vogliamo essere in grado di controllare manualmente ciò che vede l'algoritmo in modo da comporre un'immagine grande da quattro immagini più piccole ciascuna con un filtro diverso applicato.

Il rilevatore di bordi Canny è un algoritmo che elabora l'immagine in tipicamente quattro direzioni (verticale, orizzontale e due diagonali) e trova i bordi. I passaggi effettivi che questo algoritmo compie sono spiegati in modo più dettagliato su Wikipedia o brevemente nei documenti OpenCV.

In contrasto con il pattern matching, questo algoritmo rileva le forme circolari in modo da poter utilizzare qualsiasi oggetto circolare che abbiamo a disposizione. Userò il coperchio di un barattolo di caffè istantaneo e poi una tazza di caffè arancione.

Non abbiamo bisogno di lavorare con immagini a grandezza naturale (dipende dalla risoluzione della tua fotocamera, ovviamente) quindi le ridimensioneremo tra capture.read() e cv2.imshow a 640px di larghezza e altezza di conseguenza per mantenere le proporzioni:

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

Quindi vogliamo convertirlo in un'immagine in scala di grigi e applicare prima la sfocatura mediana che rimuove il rumore e mantiene i bordi, quindi il rilevatore di bordi Canny per vedere con cosa funzionerà l'algoritmo di rilevamento del cerchio. Per questo motivo, comporremo una griglia 2x2 con tutte e quattro le anteprime.

 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) 
Griglia con anteprime. In alto a sinistra: dati grezzi della webcam; in alto a destra: scala di grigi dopo la sfocatura mediana; in basso a sinistra: in scala di grigi e bordo Canny; in basso a destra: scala di grigi dopo la sfocatura mediana e il bordo Canny.

Anche se il rilevatore di bordi Canny utilizza la sfocatura gaussiana per ridurre il rumore, secondo la mia esperienza vale comunque la pena usare anche la sfocatura mediana. Puoi confrontare le due immagini in basso. Quello a sinistra è solo il rilevamento del bordo Canny senza altri filtri. La seconda immagine è anche il rilevamento del bordo Canny, ma questa volta dopo aver applicato la sfocatura mediana. Ha ridotto gli oggetti sullo sfondo che aiuteranno il rilevamento del cerchio.

Rilevamento di cerchi con gradiente Hough

Internamente, OpenCV utilizza un'implementazione più efficiente di Hough Circle Transform chiamata Hough Gradient Method che utilizza le informazioni sui bordi del rilevatore di bordi Canny. Il metodo del gradiente è descritto in modo approfondito nel libro Learning OpenCV and the Circle Hough Transform su Wikipedia .

Ora è il momento del rilevamento effettivo del cerchio:

 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)

Questo restituisce un array di tutti i cerchi rilevati. Per semplicità, ci preoccuperemo solo del primo. Hough Gradient è abbastanza sensibile alle forme davvero circolari, quindi è improbabile che ciò si traduca in falsi rilevamenti. In tal caso, aumentare il parametro at . Questo è il motivo per cui abbiamo usato la sfocatura mediana sopra; ha rimosso più rumore in modo da poter utilizzare una soglia più bassa, rendendo il rilevamento più tollerante alle imprecisioni e con una minore possibilità di rilevare falsi cerchi.

Stamperemo il centro del cerchio e il suo raggio sulla console e disegneremo anche il cerchio trovato con il suo centro sull'immagine dalla fotocamera in una finestra separata. Successivamente, lo invieremo tramite WebSocket al browser. Nota che x , y e radius sono tutti in pixel.

 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)

Questo stamperà sulla console tuple come:

 (251, 202, 74) (252, 203, 73) (250, 202, 74) (246, 202, 76) (246, 204, 74) (246, 205, 72) 
Streaming della webcam con cerchi rilevati utilizzando Hough Gradient.

Come puoi vedere in questa animazione, non è riuscito a trovare alcun cerchio. La mia fotocamera integrata ha solo 15 fps e quando muovo velocemente la mano l'immagine è sfocata, quindi non trova i bordi del cerchio, nemmeno dopo aver applicato i filtri.

Alla fine di questo articolo torneremo su questo problema e parleremo molto delle impostazioni specifiche della fotocamera e della scelta dell'algoritmo di rilevamento, ma possiamo già dire che anche se la mia configurazione è pessima (solo 15fps, scarsa illuminazione, un molto rumore sullo sfondo, l'oggetto ha un contrasto basso), il risultato è abbastanza buono.

È tutto per ora. Abbiamo le coordinate x y il radius in pixel di un cerchio che si trova nell'immagine della webcam.

Puoi vedere il codice sorgente completo per questa parte su gist.github.com.

2. Front-end JavaScript con Three.js nei browser

La parte front-end si basa sulla libreria Three.js (versione r72). Inizieremo semplicemente creando una sfera strutturata rotante che rappresenta la Terra al centro dello schermo, quindi aggiungeremo la luna che ruota attorno ad essa. Alla fine mapperemo le coordinate del mouse dello schermo 2D nello spazio 3D.

La nostra pagina HTML sarà composta da un solo elemento <canvas> . vedere index.html su gist.github.com.

Creare la Terra

JavaScript sarà un po' più lungo ma è suddiviso in più funzioni di inizializzazione in cui ognuna ha un unico scopo. Le texture della Terra e della Luna provengono da planetpixelemporium.com. Nota che durante il caricamento delle trame, vengono applicate le regole CORS.

 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); });

Guarda una demo dal vivo qui.

threejs-spinning-earth
Sfera strutturata con Three.js. (Credito di consistenza terrestre)

Questa era per lo più solo roba di base di Three.js. I nomi di oggetti e metodi sono autoesplicativi (come receiveShadow o castShadow ) ma se non l'hai mai usato prima ti consiglio vivamente di guardare i tutorial di Lee Stemkoski.

Facoltativamente, potremmo anche disegnare un asse al centro dello schermo per aiutarci con il sistema di coordinate.

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

Aggiunta della luna

Creare la luna sarà molto simile. La differenza principale è che dobbiamo impostare la posizione della luna rispetto alla Terra.

 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; }

Guarda una demo dal vivo qui.

Terra e luna con Three.js. (Credito texture terra e luna)

Mappatura delle coordinate 2D in un mondo 3D

Finora, tutto è abbastanza ovvio. La parte più interessante sarà come nascondere le coordinate dello schermo 2D provenienti da OpenCV (vedi l'output del rilevamento circolare sopra) in un mondo 3D? Quando stavamo definendo i raggi e le posizioni in Three.js abbiamo usato alcune unità ma queste non hanno nulla a che fare con i pixel dello schermo effettivi. In effetti, le dimensioni di tutto ciò che vediamo nella scena dipendono fortemente dalle impostazioni della nostra fotocamera (come le proporzioni o il campo visivo).

Per questo motivo, creeremo un oggetto piano piatto che sarà abbastanza grande da coprire l'intera scena con il suo centro a [0,0,0] . A scopo dimostrativo mapperemo le coordinate del mouse 2D sulla posizione della Terra in 3D con un asse z fisso. In altre parole, convertiremo solo x y non ci preoccuperemo di z , che è la distanza dall'oggetto alla nostra fotocamera.

Convertiremo le posizioni dello schermo del mouse in un intervallo da -1.0 a +1.0 con il suo centro a [0,0] perché dobbiamo lavorare con vettori normalizzati.

Successivamente utilizzeremo questa tecnica esatta per mappare la posizione del cerchio rilevato in 3D e anche per abbinare le dimensioni del cerchio da 2D a 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; }

Guarda una demo dal vivo qui.

threejs-spinning-earth
Posizione terrestre in 3D mappata alla posizione 2D del mouse. (Credito texture terra e luna)

Dato che stiamo controllando l'intersezione con un aereo, sappiamo che ce ne sarà sempre solo uno.

Questo è tutto per questa parte. Alla fine della parte successiva aggiungeremo anche WebSocket e un elemento <video> con il nostro flusso di telecamere che verrà sovrapposto alla scena 3D in Three.js.

3. WebSocket sia nel front-end che nel back-end

Possiamo iniziare implementando WebSocket nel back-end Python installando librerie simple-websocket-server . Ci sono molte biblioteche differenti come Tornado o Autobahn. Useremo simple-websocket-server perché è molto facile da usare e non ha dipendenze.

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

Eseguiremo il server WebSocket in un thread separato e terremo traccia di tutti i client connessi.

 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 ...

Abbiamo utilizzato il parametro selectInterval nel costruttore del server per controllare periodicamente la presenza di messaggi in sospeso. Il server invia messaggi solo quando riceve dati dai client o deve rimanere sul thread principale in un ciclo. Non possiamo lasciare che blocchi il thread principale perché anche OpenCV ne ha bisogno. Poiché sappiamo che la fotocamera funziona solo a 15 fps, possiamo utilizzare lo stesso intervallo sul server WebSocket.

Quindi, dopo aver rilevato i cerchi, possiamo eseguire l'iterazione di tutti i client collegati e inviare la posizione e il raggio correnti rispetto alle dimensioni dell'immagine.

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

Puoi vedere il codice sorgente completo per il server su gist.github.com.

La parte JavaScript imiterà lo stesso comportamento che abbiamo fatto con la posizione del mouse. Terremo anche traccia dei pochi messaggi e calcoleremo un valore medio per ogni asse e raggio per migliorare la precisione.

 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. };

Invece di impostare la posizione della Terra sulla mia posizione attuale del mouse, useremo la variabile msgHistory .

Probabilmente non è necessario incollare l'intero codice qui, quindi sentiti libero di guardare i dettagli di implementazione su gist.gihtub.com.

Quindi aggiungi un elemento <video> con il flusso della webcam che riempie l'intera finestra che verrà sovrapposta alla nostra scena 3D con uno sfondo trasparente.

 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() {}); }

Il risultato finale:

Applicazione finale con mappatura della Terra alle coordinate trovate da OpenCV.

Per ricapitolare rapidamente cosa abbiamo fatto e cosa mostra il video sopra:

  1. Il back-end Python esegue un server WebSocket.
  2. Il server rileva un cerchio utilizzando OpenCV da un flusso di webcam.
  3. Il client JavaScript visualizza lo stesso flusso della webcam utilizzando l'elemento <video> .
  4. Il client esegue il rendering della scena 3D utilizzando Three.js.
  5. Il client si connette al server tramite il protocollo WebSocket e riceve la posizione del cerchio e il raggio.

Il codice effettivo utilizzato per questa demo è disponibile su GitHub. È leggermente più sofisticato e interpola anche le coordinate tra due messaggi dal back-end perché il flusso della webcam funziona solo a 15 fps mentre la scena 3D è renderizzata a 60 fps. Puoi vedere il video originale su YouTube.

Avvertenze

Ci sono alcuni risultati degni di nota:

Il rilevamento del cerchio non è l'ideale

È fantastico che funzioni con qualsiasi oggetto circolare, ma è molto sensibile al rumore e alla deformazione dell'immagine, anche se, come puoi vedere sopra, il nostro risultato è abbastanza buono. Inoltre, probabilmente non sono disponibili esempi pratici di rilevamento dei cerchi a parte l'utilizzo più elementare. Potrebbe essere meglio utilizzare il rilevamento dell'ellisse ma al momento non è implementato in OpenCV.

Tutto dipende dalla tua configurazione

Le webcam integrate sono generalmente piuttosto scadenti. 15 fps non sono sufficienti e il solo aumento a 30 fps riduce significativamente il motion blur e rende il rilevamento più affidabile. Possiamo suddividere questo punto in altri quattro punti:

  • Distorsioni della fotocamera
    Molte fotocamere introducono una certa distorsione dell'immagine, più comunemente un effetto fish-eye che ha un'influenza significativa sul rilevamento della forma. La documentazione di OpenCV ha un tutorial molto semplice su come ridurre la distorsione calibrando la tua fotocamera.
  • Non esiste un elenco ufficiale dei dispositivi supportati da OpenCV
    Anche se hai già una buona fotocamera, potrebbe non funzionare con OpenCV senza ulteriori spiegazioni. Ho anche letto di persone che utilizzano un'altra libreria per acquisire un'immagine della fotocamera (come libdc1394 per fotocamere basate su IEEE 1394) e quindi utilizzano OpenCV solo per elaborare le immagini. Il gestore di pacchetti Brew ti consente di compilare OpenCV direttamente con il supporto di libdc1394.
  • Alcune fotocamere funzionano meglio con OpenCV rispetto ad altre
    Se sei fortunato puoi impostare alcune opzioni della fotocamera come fotogrammi al secondo direttamente sulla tua fotocamera, ma potrebbe anche non avere alcun effetto se OpenCV non è compatibile con il tuo dispositivo. Ancora una volta, senza alcuna spiegazione.
  • Tutti i parametri dipendono da un utilizzo nel mondo reale
    Se utilizzato in un'installazione reale, si consiglia vivamente di testare gli algoritmi e i filtri nell'ambiente reale perché elementi come luci, colore di sfondo o scelta dell'oggetto hanno effetti significativi sul risultato. Ciò include anche le ombre della luce del giorno, le persone in piedi e così via.

La corrispondenza dei modelli è solitamente una scelta migliore

Se vedi una realtà aumentata usata nella pratica, probabilmente sarà basata sul pattern matching. È generalmente più affidabile e non così influenzato dai problemi sopra descritti.

I filtri sono fondamentali

Penso che l'uso corretto dei filtri richieda un po' di esperienza e sempre un po' di magia. Il tempo di elaborazione della maggior parte dei filtri dipende dai loro parametri, sebbene in OpenCV 3.0 alcuni di essi siano già riscritti in CUDA C (un linguaggio simile al C per la programmazione altamente parallela con le schede grafiche NVIDIA) che apporta significativi miglioramenti delle prestazioni.

Filtra i dati da OpenCV

Abbiamo visto che il rilevamento del cerchio presenta alcune imprecisioni: a volte non riesce a trovare alcun cerchio o rileva il raggio sbagliato. Per ridurre al minimo questo tipo di errore varrebbe la pena implementare un metodo più sofisticato per migliorare la precisione. Nel nostro esempio abbiamo usato la mediana per x , y e radius , il che è molto semplice. Un filtro comunemente usato con buoni risultati è il filtro Kalman, utilizzato dagli autopiloti per i droni per ridurre le imprecisioni provenienti dai sensori. Tuttavia, la sua implementazione non è semplice come usare solo math.mean() da https://mathjs.org.

Conclusione

Ho visto per la prima volta un'applicazione simile al Museo Nazionale di Storia Naturale di Madrid due anni fa e mi sono chiesto quanto sarebbe stato difficile realizzare qualcosa di simile.

La mia idea principale alla base di questa demo era quella di utilizzare strumenti comuni sul Web (come WebSockets e Three.js) e che non richiedono alcun prerequisito in modo che chiunque possa iniziare a utilizzarli immediatamente. Ecco perché volevo usare solo il rilevamento del cerchio e non la corrispondenza dei modelli, che richiederebbe la stampa o la presenza di un particolare oggetto del mondo reale.

Devo dire che ho fortemente sottovalutato i requisiti effettivi della fotocamera. I fotogrammi al secondo alti e una buona illuminazione sono più importanti della risoluzione. Inoltre, non mi aspettavo che l'incompatibilità della fotocamera con OpenCV sarebbe stato un problema.