Realitate simplă crescută cu OpenCV, Three.js și WebSockets
Publicat: 2022-03-10Realitatea augmentată este, în general, considerată a fi foarte greu de creat. Cu toate acestea, este posibil să realizați proiecte impresionante din punct de vedere vizual folosind doar biblioteci open source. În acest tutorial, vom folosi OpenCV în Python pentru a detecta obiecte în formă de cerc într-un flux de webcam și le vom înlocui cu 3D Earth în Three.js într-o fereastră de browser în timp ce folosim WebSockets pentru a uni toate acestea.
Dorim să separăm strict front-end și back-end pentru a le face reutilizabile. Într-o aplicație din lumea reală, am putea scrie front-end-ul în Unity, Unreal Engine sau Blender, de exemplu, pentru a-l face să arate foarte frumos. Front-end-ul browserului este cel mai ușor de implementat și ar trebui să funcționeze pe aproape orice configurație posibilă.
Pentru a menține lucrurile simple, vom împărți aplicația în trei părți mai mici:
- Back-end-ul Python cu OpenCV OpenCV va citi fluxul webcam și va deschide mai multe ferestre cu imaginea camerei după ce o trece prin mai multe filtre pentru a ușura depanarea și pentru a ne oferi o mică perspectivă asupra a ceea ce vede algoritmul de detectare a cercului. Ieșirea acestei părți va fi doar coordonatele 2D și raza cercului detectat.
- Front-end JavaScript cu Three.js într-un browser Implementarea pas cu pas a bibliotecii Three.js pentru a reda Pământul texturat cu luna care se rotește în jurul lui. Cel mai interesant lucru aici va fi maparea coordonatelor ecranului 2D în lumea 3D. De asemenea, vom aproxima coordonatele și raza pentru a crește precizia OpenCV.
- WebSockets atât în front-end cât și în back-end Back-end cu serverul WebSockets va trimite periodic mesaje cu coordonatele și razele cercului detectate către clientul browser.
1. Back-End Python cu OpenCV
Primul nostru pas va fi doar importarea bibliotecii OpenCV în Python și deschiderea unei ferestre cu un flux live webcam.
Vom folosi cel mai nou OpenCV 3.0 (vezi notele de instalare) cu Python 2.7. Vă rugăm să rețineți că instalarea pe unele sisteme poate fi problematică, iar documentația oficială nu este foarte utilă. M-am încercat pe Mac OS X versiunea 3.0 de la MacPorts și binarul a avut o problemă de dependență, așa că a trebuit să trec la Homebrew. De asemenea, rețineți că unele pachete OpenCV ar putea să nu vină cu legare Python în mod implicit (trebuie să utilizați unele opțiuni de linie de comandă).
Cu Homebrew am alergat:
brew install opencv
Aceasta instalează în mod implicit OpenCV cu legături Python.
Doar pentru a testa lucrurile, vă recomand să rulați Python în modul interactiv (rulați python
în CLI fără niciun argument) și să scrieți import cv2
. Dacă OpenCV este instalat corect și căile către legăturile Python sunt corecte, nu ar trebui să arunce erori.
Mai târziu, vom folosi și numpy
-ul lui Python pentru unele operații simple cu matrice, astfel încât să îl putem instala și acum.
pip install numpy
Citirea imaginii camerei
Acum putem testa camera:
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
Cu cv2.VideoCapture(0)
obținem acces la camera pe indexul 0
, care este implicit (de obicei camera încorporată). Dacă doriți să utilizați unul diferit, încercați numere mai mari decât zero; cu toate acestea, nu există o modalitate ușoară de a enumera toate camerele disponibile cu versiunea curentă OpenCV.
Când apelăm cv2.imshow('Camera stream', image)
pentru prima dată verifică că nu există nicio fereastră cu acest nume și ne creează una nouă cu o imagine din cameră. Aceeași fereastră va fi reutilizată pentru fiecare iterație a buclei principale.
Apoi am folosit capture.read()
pentru a aștepta și a prelua imaginea curentă a camerei. Această metodă returnează, de asemenea, o proprietate booleană ret
în cazul în care camera este deconectată sau următorul cadru nu este disponibil dintr-un motiv oarecare.
La sfârșit avem cv2.waitKey(1)
care verifică timp de 1 milisecundă dacă vreo tastă este apăsată și returnează codul acesteia. Deci, când apăsăm q
, ieșim din buclă, închidem fereastra și aplicația se va încheia.
Dacă toate acestea funcționează, am trecut de partea cea mai dificilă a aplicației de back-end, care face ca camera să funcționeze.
Filtrarea imaginilor camerei
Pentru detectarea efectivă a cercului, vom folosi Transformarea Hough al cercului, care este implementată în metoda cv2.HoughCircles()
și chiar acum este singurul algoritm disponibil în OpenCV. Lucrul important pentru noi este că are nevoie de o imagine în tonuri de gri ca intrare și folosește algoritmul Canny edge detector în interior pentru a găsi margini în imagine. Dorim să putem verifica manual ceea ce vede algoritmul, așa că vom compune o imagine mare din patru imagini mai mici, fiecare cu un filtru diferit aplicat.
Detectorul de margini Canny este un algoritm care procesează imaginea în patru direcții (verticală, orizontală și două diagonale) și găsește margini. Pașii efectivi pe care îi face acest algoritm sunt explicați mai detaliat pe Wikipedia sau pe scurt în documentele OpenCV.
Spre deosebire de potrivirea modelelor, acest algoritm detectează forme circulare, astfel încât să putem folosi orice obiecte pe care le avem la îndemână care sunt circulare. Voi folosi un capac de la un borcan de cafea instant și apoi o cană de cafea portocalie.
Nu trebuie să lucrăm cu imagini la dimensiune completă (în funcție de rezoluția camerei dvs., desigur), așa că le vom redimensiona chiar între capture.read()
și cv2.imshow
la 640px lățime și înălțime în consecință pentru a păstra raportul de aspect:
width, height = image.shape scale = 640.0 / width image = cv2.resize(image, (0,0), fx=scale, fy=scale)
Apoi dorim să o convertim într-o imagine în tonuri de gri și să aplicăm mai întâi estomparea mediană care elimină zgomotul și reține marginile, apoi detectorul de margini Canny pentru a vedea cu ce va funcționa algoritmul de detectare a cercului. Din acest motiv, vom compune grila 2x2 cu toate cele patru previzualizări.
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)
Chiar dacă detectorul de margini Canny folosește estomparea gaussiană pentru a reduce zgomotul, din experiența mea merită să folosiți și estomparea mediană. Puteți compara cele două imagini de jos. Cel din stânga este doar detectarea marginilor Canny fără niciun alt filtru. A doua imagine este, de asemenea, detectarea marginilor Canny, dar de data aceasta după aplicarea estomparii mediane. A redus obiectele din fundal, ceea ce va ajuta la detectarea cercurilor.
Detectarea cercurilor cu gradient Hough
Pe plan intern, OpenCV folosește o implementare mai eficientă a Hough Circle Transform numită Hough Gradient Method, care utilizează informațiile de margine de la detectorul de margini Canny. Metoda gradientului este descrisă în profunzime în cartea Learning OpenCV and the Circle Hough Transform on Wikipedia .
Acum este timpul pentru detectarea efectivă a cercului:
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)
Aceasta returnează o matrice a tuturor cercurilor detectate. De dragul simplității, ne va păsa doar de primul. Deși Gradient este destul de sensibil la formele cu adevărat circulare, așa că este puțin probabil ca acest lucru să ducă la detectări false. Dacă a făcut-o, creșteți parametrul at
. Acesta este motivul pentru care am folosit estomparea mediană mai sus; a eliminat mai mult zgomot, astfel încât să putem folosi un prag mai mic, făcând detectarea mai tolerantă la inexactități și cu o șansă mai mică de a detecta cercuri false.
Vom imprima centrul cercului și raza acestuia pe consolă și, de asemenea, vom desena cercul găsit cu centrul său pe imaginea de la cameră într-o fereastră separată. Mai târziu, îl vom trimite prin WebSocket către browser. Rețineți că x
, y
și radius
sunt toate în pixeli.
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)
Aceasta va imprima pe consolă tupluri precum:
(251, 202, 74) (252, 203, 73) (250, 202, 74) (246, 202, 76) (246, 204, 74) (246, 205, 72)
După cum puteți vedea în această animație, nu a reușit să găsească niciun cerc. Camera mea încorporată are doar 15fps și când îmi mișc rapid mâna imaginea este neclară, așa că nu găsește margini de cerc, nici măcar după aplicarea filtrelor.
La sfârșitul acestui articol vom reveni la această problemă și vom vorbi mult despre setările specifice camerei și alegerea algoritmului de detectare, dar deja putem spune că, deși configurația mea este foarte proastă (doar 15fps, iluminare slabă, o mult zgomot în fundal, obiectul are contrast scăzut), rezultatul este rezonabil de bun.
Asta este tot pentru acum. Avem coordonatele x
și y
și radius
în pixeli a unui cerc găsit în imaginea camerei web.
Puteți vedea codul sursă complet pentru această parte pe gist.github.com.
2. JavaScript Front-End cu Three.js în browsere
Partea front-end se bazează pe biblioteca Three.js (versiunea r72). Vom începe prin a crea o sferă texturată rotativă reprezentând Pământul în centrul ecranului, apoi vom adăuga luna care se învârte în jurul ei. La sfârșit, vom mapa coordonatele mouse-ului ecranului 2D la spațiul 3D.
Pagina noastră HTML va consta dintr-un singur element <canvas>
. vezi index.html pe gist.github.com.
Crearea Pământului
JavaScript va fi puțin mai lung, dar este împărțit în mai multe funcții de inițializare în care fiecare are un singur scop. Texturile Pământului și Lună provin de la planetpixelemporium.com. Rețineți că atunci când încărcați texturi, se aplică regulile 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); });
Vedeți o demonstrație live aici.
Acestea au fost în mare parte doar chestii de bază Three.js. Numele obiectelor și metodelor se explică de la sine (cum ar fi receiveShadow
sau castShadow
), dar dacă nu le-ați folosit niciodată înainte, vă recomand cu tărie să vă uitați la tutorialele lui Lee Stemkoski.
Opțional, am putea desena și o axă în centrul ecranului pentru a ne ajuta cu sistemul de coordonate.
var axes = new THREE.AxisHelper(60); axes.position.set(0, 0, 0); scene.add(axes);
Adăugarea Lunii
Crearea lunii va fi foarte asemănătoare. Principala diferență este că trebuie să setăm poziția Lunii față de Pământ.
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; }
Vedeți o demonstrație live aici.
Maparea coordonatelor 2D într-o lume 3D
Până acum, totul este destul de evident. Cea mai interesantă parte va fi cum să disimulați coordonatele ecranului 2D care provin de la OpenCV (vezi rezultatul detectării circulare de mai sus) într-o lume 3D? Când definim razele și pozițiile în Three.js, am folosit unele unități, dar acestea nu au nimic de-a face cu pixelii efectivi ai ecranului. De fapt, dimensiunile a tot ceea ce vedem în scenă depind în mare măsură de setările camerei noastre (cum ar fi raportul de aspect sau câmpul vizual).
Din acest motiv, vom face un obiect plan plat care va fi suficient de mare pentru a acoperi întreaga scenă cu centrul său la [0,0,0]
. În scopuri demonstrative, vom mapa coordonatele mouse-ului 2D la poziția Pământului în 3D cu o axă z
fixă. Cu alte cuvinte, vom converti doar x
și y
și nu ne vom îngrijora de z
, care este distanța de la obiect la camera noastră.
Vom converti pozițiile ecranului mouse-ului într-un interval de la -1.0
la +1.0
cu centrul său la [0,0]
deoarece trebuie să lucrăm cu vectori normalizați.
Mai târziu vom folosi această tehnică exactă pentru a mapa poziția cercului detectat în 3D și, de asemenea, pentru a potrivi dimensiunea cercului de la 2D la 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; }
Vedeți o demonstrație live aici.
Din moment ce verificăm intersecția cu un avion, știm că întotdeauna va fi doar unul.
Asta e tot pentru partea asta. La sfârșitul părții următoare, vom adăuga, de asemenea, WebSockets și un element <video>
cu fluxul camerei noastre, care va fi suprapus de scena 3D în Three.js.
3. WebSockets atât în front-end, cât și în back-end
Putem începe prin a implementa WebSockets în back-end-ul Python, instalând biblioteci simple-websocket-server
. Există multe biblioteci diferite, cum ar fi Tornado sau Autobahn. Vom folosi simple-websocket-server
pentru că este foarte ușor de utilizat și nu are dependențe.
pip install git+https://github.com/dpallot/simple-websocket-server.git
Vom rula serverul WebSocket într-un fir separat și vom urmări toți clienții conectați.
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 ...
Am folosit parametrul selectInterval
din constructorul serverului pentru ca acesta să verifice periodic orice mesaje în așteptare. Serverul trimite mesaje numai atunci când primește date de la clienți sau trebuie să stea pe firul principal într-o buclă. Nu îl putem lăsa să blocheze firul principal pentru că și OpenCV are nevoie de el. Din moment ce știm că camera rulează doar la 15fps, putem folosi același interval pe serverul WebSocket.
Apoi, după ce detectăm cercurile, putem repeta toți clienții conectați și trimitem poziția și raza curentă în raport cu dimensiunea imaginii.
for client in clients: msg = json.dumps({'x': x / w, 'y': y / h, 'radius': radius / w}) client.sendMessage(unicode(msg))
Puteți vedea că codul sursă complet al serverului este pe gist.github.com.
Partea JavaScript va imita același comportament ca și cu poziția mouse-ului. De asemenea, vom urmări câteva mesaje și vom calcula o valoare medie pentru fiecare axă și rază pentru a îmbunătăți acuratețea.
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. };
În loc să setăm poziția Pământului la poziția mea curentă a mouse-ului, vom folosi variabila msgHistory
.
Probabil că nu este necesar să lipiți întregul cod aici, așa că nu ezitați să vă uitați la detaliile de implementare pe gist.gihtub.com.
Apoi adăugați un element <video>
cu fluxul webcam umplând întreaga fereastră care va fi suprapusă de scena noastră 3D cu un fundal 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() {}); }
Rezultatul final:
Pentru a recapitula rapid ceea ce am făcut și ceea ce arată videoclipul de mai sus:
- Back-end-ul Python rulează un server WebSocket.
- Serverul detectează un cerc folosind OpenCV dintr-un flux webcam.
- Clientul JavaScript afișează același flux de cameră web folosind elementul
<video>
. - Clientul redă scena 3D folosind Three.js.
- Clientul se conectează la server prin protocolul WebSocket și primește poziția și raza cercului.
Codul real folosit pentru această demonstrație este disponibil pe GitHub. Este puțin mai sofisticat și, de asemenea, interpolează coordonatele între două mesaje din back-end, deoarece fluxul webcam rulează doar la 15fps, în timp ce scena 3D este redată la 60fps. Videoclipul original îl puteți vedea pe YouTube.
Avertismente
Există câteva constatări care merită remarcate:
Detectarea cercurilor nu este ideală
Este grozav că funcționează cu orice obiect circular, dar este foarte sensibil la zgomot și deformarea imaginii, deși, după cum puteți vedea mai sus, rezultatul nostru este destul de bun. De asemenea, probabil că nu există exemple practice de detectare a cercurilor disponibile în afară de cea mai simplă utilizare. Ar putea fi mai bine să utilizați detectarea elipselor, dar nu este implementat în OpenCV acum.
Totul depinde de configurația dvs
Camerele web încorporate sunt în general destul de proaste. 15fps nu este suficient și doar creșterea acestuia la 30fps reduce semnificativ neclaritatea mișcării și face detectarea mai fiabilă. Putem descompune acest punct în alte patru puncte:
- Distorsiuni ale camerei
Multe camere introduc o oarecare distorsiune a imaginii, cel mai frecvent un efect de ochi de pește care are o influență semnificativă asupra detectării formei. Documentația OpenCV are un tutorial foarte simplu despre cum să reduceți distorsiunea prin calibrarea camerei. - Nu există o listă oficială de dispozitive acceptate de OpenCV
Chiar dacă aveți deja o cameră bună, este posibil să nu funcționeze cu OpenCV fără explicații suplimentare. De asemenea, am citit despre oameni care folosesc o altă bibliotecă pentru a captura o imagine a camerei (cum ar fi libdc1394 pentru camerele bazate pe IEEE 1394) și apoi folosesc OpenCV doar pentru a procesa imaginile. Managerul de pachete Brew vă permite să compilați OpenCV direct cu suport libdc1394. - Unele camere funcționează mai bine cu OpenCV decât altele
Dacă aveți noroc, puteți seta unele opțiuni ale camerei, cum ar fi cadre pe secundă, direct pe camera dvs., dar s-ar putea să nu aibă niciun efect dacă OpenCV nu este prietenos cu dispozitivul dvs. Din nou, fără nicio explicație. - Toți parametrii depind de o utilizare reală
Când este utilizat într-o instalare din lumea reală, este foarte recomandat să testați algoritmii și filtrele în mediul real, deoarece lucruri precum luminile, culoarea de fundal sau alegerea obiectelor au efecte semnificative asupra rezultatului. Aceasta include, de asemenea, umbrele din lumina zilei, oamenii care stau în jur și așa mai departe.
Potrivirea modelelor este de obicei o alegere mai bună
Dacă vedeți orice realitate augmentată folosită în practică, probabil că se va baza pe potrivirea modelelor. În general, este mai fiabil și nu este atât de afectat de problemele descrise mai sus.
Filtrele sunt cruciale
Cred că utilizarea corectă a filtrelor necesită ceva experiență și întotdeauna puțină magie. Timpul de procesare al majorității filtrelor depinde de parametrii acestora, deși în OpenCV 3.0 unele dintre ele sunt deja rescrise în CUDA C (un limbaj asemănător C pentru programare extrem de paralelă cu plăcile grafice NVIDIA), ceea ce aduce îmbunătățiri semnificative de performanță.
Filtrați datele din OpenCV
Am văzut că detectarea cercului are unele inexactități: uneori nu reușește să găsească niciun cerc sau detectează o rază greșită. Pentru a minimiza acest tip de eroare, ar fi util să implementați o metodă mai sofisticată pentru a îmbunătăți acuratețea. În exemplul nostru am folosit mediana pentru x
, y
și radius
, ceea ce este foarte simplu. Un filtru folosit în mod obișnuit cu rezultate bune este filtrul Kalman, folosit de autopiloții pentru drone pentru a reduce inexactitatea provenită de la senzori. Cu toate acestea, implementarea sa nu este la fel de simplă ca utilizarea math.mean()
de la https://mathjs.org.
Concluzie
Am văzut pentru prima dată o aplicație similară la Muzeul Național de Istorie Naturală din Madrid acum doi ani și m-am întrebat cât de greu ar fi să fac ceva asemănător.
Ideea mea de bază din spatele acestui demo a fost să folosesc instrumente care sunt comune pe web (cum ar fi WebSockets și Three.js) și nu necesită nicio condiție prealabilă, astfel încât oricine să poată începe să le folosească imediat. De aceea am vrut să folosesc doar detectarea cercurilor și nu potrivirea modelelor, ceea ce ar necesita imprimarea sau avea un anumit obiect din lumea reală.
Trebuie să spun că am subestimat sever cerințele reale ale camerei. Cadrele mari pe secundă și iluminarea bună sunt mai importante decât rezoluția. De asemenea, nu mă așteptam ca incompatibilitatea camerei cu OpenCV să fie o problemă.