OpenCV, Three.js ve WebSockets ile Basit Artırılmış Gerçeklik
Yayınlanan: 2022-03-10Artırılmış gerçeklik genellikle yaratılması çok zor olarak kabul edilir. Ancak, sadece açık kaynak kitaplıkları kullanarak görsel olarak etkileyici projeler yapmak mümkündür. Bu eğitimde, bir web kamerası akışındaki daire şeklindeki nesneleri algılamak için Python'da OpenCV'yi kullanacağız ve bunları bir araya getirmek için WebSockets kullanırken bir tarayıcı penceresinde Three.js'de 3D Earth ile değiştireceğiz.
Yeniden kullanılabilir hale getirmek için ön ucu ve arka ucu kesin olarak ayırmak istiyoruz. Gerçek dünyadaki bir uygulamada, örneğin gerçekten güzel görünmesi için ön ucu Unity, Unreal Engine veya Blender'da yazabiliriz. Tarayıcı ön ucu, uygulanması en kolay olanıdır ve neredeyse tüm olası yapılandırmalarda çalışması gerekir.
İşleri basitleştirmek için uygulamayı üç küçük parçaya böleceğiz:
- OpenCV ile Python arka ucu OpenCV, web kamerası akışını okuyacak ve hata ayıklamayı kolaylaştırmak için birden fazla filtreden geçirdikten sonra kamera görüntüsüyle birden fazla pencere açacak ve bize daire algılama algoritmasının gerçekte ne gördüğü hakkında biraz fikir verecektir. Bu parçanın çıktısı, tespit edilen dairenin sadece 2B koordinatları ve yarıçapı olacaktır.
- Bir tarayıcıda Three.js ile JavaScript ön ucu, etrafında dönen ay ile dokulu Dünya'yı oluşturmak için Three.js kitaplığının adım adım uygulanması. Buradaki en ilginç şey, 2B ekran koordinatlarını 3B dünyayla eşleştirmek olacaktır. Ayrıca OpenCV'nin doğruluğunu artırmak için koordinatları ve yarıçapı yaklaşık olarak hesaplayacağız.
- Hem ön uç hem de arka uçtaki WebSockets WebSockets sunucusuna sahip Arka uç, algılanan daire koordinatları ve yarıçapları ile tarayıcı istemcisine periyodik olarak mesajlar gönderir.
1. OpenCV ile Python Arka Uç
İlk adımımız Python'daki OpenCV kitaplığını içe aktarmak ve canlı bir web kamerası akışı ile bir pencere açmak olacaktır.
Python 2.7 ile en yeni OpenCV 3.0'ı (kurulum notlarına bakın) kullanacağız. Lütfen, bazı sistemlerde kurulumun sorunlu olabileceğini ve resmi belgelerin pek yardımcı olmadığını unutmayın. Kendimi MacPorts'tan Mac OS X sürüm 3.0'da denedim ve ikili dosyanın bir bağımlılık sorunu vardı, bu yüzden bunun yerine Homebrew'a geçmek zorunda kaldım. Ayrıca bazı OpenCV paketlerinin varsayılan olarak Python bağlaması ile gelmeyebileceğini unutmayın (bazı komut satırı seçeneklerini kullanmanız gerekir).
Homebrew ile koştum:
brew install opencv
Bu, OpenCV'yi varsayılan olarak Python bağlamalarıyla yükler.
Sadece bir şeyleri test etmek için Python'u etkileşimli modda çalıştırmanızı ( python
CLI'de herhangi bir argüman olmadan çalıştırın) ve import cv2
yazmanızı öneririm. OpenCV düzgün bir şekilde kurulmuşsa ve Python bağlamalarına giden yollar doğruysa, herhangi bir hata vermemelidir.
Daha sonra, matrislerle bazı basit işlemler için Python'un numpy
de kullanacağız, böylece onu şimdi de yükleyebiliriz.
pip install numpy
Kamera Görüntüsünü Okumak
Şimdi kamerayı test edebiliriz:
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
cv2.VideoCapture(0)
ile varsayılan olan (genellikle yerleşik kamera) indeks 0
kameraya erişim elde ederiz. Farklı bir tane kullanmak istiyorsanız, sıfırdan büyük sayıları deneyin; ancak mevcut tüm kameraları mevcut OpenCV sürümüyle listelemenin kolay bir yolu yoktur.
cv2.imshow('Camera stream', image)
ilk defa çağırdığımızda, bu isimde herhangi bir pencerenin olmadığını kontrol ediyor ve kameradan bir görüntü ile bizim için yeni bir tane oluşturuyor. Ana döngünün her yinelemesi için aynı pencere yeniden kullanılacaktır.
Ardından, mevcut kamera görüntüsünü beklemek ve almak için capture.read()
'i kullandık. Bu yöntem ayrıca kameranın bağlantısının kesilmesi veya herhangi bir nedenle sonraki karenin mevcut olmaması durumunda bir Boole özelliği ret
döndürür.
Sonunda, 1 milisaniye boyunca herhangi bir tuşa basılıp basılmadığını kontrol eden ve kodunu döndüren cv2.waitKey(1)
var. Yani, q
tuşuna bastığımızda döngüden çıkıyoruz, pencereyi kapatıyoruz ve uygulama sona erecek.
Bunların hepsi işe yararsa, arka uç uygulamasının en zor kısmı olan kamerayı çalıştırmayı geçtik.
Kamera Görüntülerini Filtreleme
Gerçek daire tespiti için, cv2.HoughCircles()
yönteminde uygulanan ve şu anda OpenCV'de kullanılabilen tek algoritma olan daire Hough Dönüşümünü kullanacağız. Bizim için önemli olan girdi olarak gri tonlamalı bir görüntüye ihtiyaç duyması ve görüntüdeki kenarları bulmak için içerideki Canny kenar bulma algoritmasını kullanmasıdır. Algoritmanın ne gördüğünü manuel olarak kontrol edebilmek istiyoruz, böylece her biri farklı bir filtre uygulanmış dört küçük görüntüden bir büyük görüntü oluşturacağız.
Canny kenar dedektörü, görüntüyü tipik olarak dört yönde (dikey, yatay ve iki diyagonal) işleyen ve kenarları bulan bir algoritmadır. Bu algoritmanın yaptığı gerçek adımlar, Wikipedia'da daha ayrıntılı olarak veya OpenCV belgelerinde kısaca açıklanmıştır.
Model eşleştirmenin aksine, bu algoritma dairesel şekilleri algılar, böylece elimizde dairesel olan herhangi bir nesneyi kullanabiliriz. Hazır kahve kavanozundan bir kapak ve ardından turuncu bir kahve kupası kullanacağım.
Tam boyutlu görüntülerle çalışmamız gerekmiyor (elbette kamera çözünürlüğünüze bağlı olarak), bu nedenle en boy oranını korumak için onları tam yakalama.read capture.read()
ve cv2.imshow
arasında 640 piksel genişlik ve yüksekliğe göre yeniden boyutlandıracağız:
width, height = image.shape scale = 640.0 / width image = cv2.resize(image, (0,0), fx=scale, fy=scale)
Ardından, onu gri tonlamalı bir görüntüye dönüştürmek ve önce gürültüyü ortadan kaldıran ve kenarları koruyan medyan bulanıklığı , ardından daire algılama algoritmasının neyle çalışacağını görmek için Canny kenar detektörünü uygulamak istiyoruz. Bu nedenle, dört önizlemenin tümü ile 2x2 ızgara oluşturacağız.
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)
Canny kenar dedektörü gürültüyü azaltmak için Gauss bulanıklığını kullanıyor olsa da, benim deneyimime göre yine de ortanca bulanıklığı kullanmaya değer. Alttaki iki resmi karşılaştırabilirsiniz. Soldaki, başka bir filtre olmadan sadece Canny kenar algılamadır. İkinci görüntü de Canny kenar algılamadır, ancak bu sefer medyan bulanıklık uygulandıktan sonra. Arka planda daire algılamaya yardımcı olacak nesneleri azalttı.
Hough Gradyanlı Daireleri Algılama
Dahili olarak, OpenCV, Canny kenar dedektöründen gelen kenar bilgilerini kullanan Hough Gradient Method adlı daha verimli bir Hough Circle Transform uygulamasını kullanır. Gradyan yöntemi, Wikipedia'daki Learning OpenCV ve Circle Hough Transform kitabında ayrıntılı olarak açıklanmıştır.
Şimdi gerçek daire algılama zamanı:
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)
Bu, algılanan tüm çevrelerin bir dizisini döndürür. Basitlik adına, yalnızca ilkini önemseyeceğiz. Hough Gradient gerçekten dairesel şekillere karşı oldukça hassastır, bu nedenle bunun yanlış algılamalarla sonuçlanması pek olası değildir. Varsa, at
parametresini artırın. Bu nedenle yukarıda medyan bulanıklığı kullandık; daha fazla gürültüyü ortadan kaldırdı, böylece daha düşük bir eşik kullanabiliriz, bu da algılamayı hatalara karşı daha toleranslı hale getirir ve yanlış daireleri algılama şansını azaltır.
Daire merkezini ve yarıçapını konsola yazdıracağız ve ayrıca bulunan daireyi merkezi ile ayrı bir pencerede kameradan görüntüye çizeceğiz. Daha sonra WebSocket aracılığıyla tarayıcıya göndereceğiz. x
, y
ve radius
piksel cinsinden olduğuna dikkat edin.
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)
Bu, aşağıdaki gibi konsol demetlerine yazdıracaktır:
(251, 202, 74) (252, 203, 73) (250, 202, 74) (246, 202, 76) (246, 204, 74) (246, 205, 72)
Bu animasyonda da görebileceğiniz gibi, hiç çevre bulamadı. Dahili kameram sadece 15 fps'ye sahip ve elimi hızlı hareket ettirdiğimde görüntü bulanıklaşıyor, bu yüzden filtreler uygulandıktan sonra bile daire kenarları bulamıyor.
Bu makalenin sonunda bu soruna geri döneceğiz ve kameraya özgü ayarlar ve algılama algoritması seçimi hakkında çokça konuşacağız, ancak kurulumum çok kötü olsa da (sadece 15 fps, zayıf aydınlatma, bir arka planda çok fazla gürültü var, nesnenin kontrastı düşük), sonuç oldukça iyi.
Şimdilik bu kadar. Web kamerası görüntüsünde bulunan bir dairenin piksel cinsinden x
ve y
koordinatlarına ve radius
sahibiz.
Bu bölümün tam kaynak kodunu gist.github.com adresinde görebilirsiniz.
2. Tarayıcılarda Three.js ile JavaScript Ön Uç
Ön uç kısım, Three.js (sürüm r72) kitaplığını temel alır. Ekranın ortasında Dünya'yı temsil eden dönen dokulu bir küre oluşturarak başlayacağız, ardından onun etrafında dönen ayı ekleyeceğiz. Sonunda 2B ekran fare koordinatlarını 3B alana eşleyeceğiz.
HTML sayfamız yalnızca tek bir <canvas>
öğesinden oluşacaktır. gist.github.com'da index.html'ye bakın.
Dünyayı Yaratmak
JavaScript biraz daha uzun olacak, ancak her birinin tek bir amacı olduğu birden çok başlatma işlevine bölünmüş durumda. Dünya ve ay dokuları planetpixelemporium.com'dan alınmıştır. Dokuları yüklerken CORS kurallarının uygulandığını unutmayın.
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); });
Burada canlı bir demo görün.


Bu çoğunlukla sadece temel Three.js şeyleriydi. Nesne ve yöntem adları kendiliğinden açıklayıcıdır ( receiveShadow
veya castShadow
gibi) ancak daha önce hiç kullanmadıysanız, Lee Stemkoski'nin eğitimlerine bakmanızı şiddetle tavsiye ederim.
İsteğe bağlı olarak, koordinat sisteminde bize yardımcı olması için ekranın ortasına bir eksen de çizebiliriz.
var axes = new THREE.AxisHelper(60); axes.position.set(0, 0, 0); scene.add(axes);
Ay Eklemek
Ay'ı yaratmak çok benzer olacak. Temel fark, Ay'ın Dünya'ya göre konumunu ayarlamamız gerektiğidir.
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; }
Burada canlı bir demo görün.
2B Koordinatları 3B Dünyaya Eşleme
Buraya kadar her şey oldukça açık. En ilginç kısım, OpenCV'den (yukarıdaki dairesel algılama çıktısına bakın) bir 3B dünyaya gelen 2B ekran koordinatlarının nasıl gizleneceği olacak? Three.js'de yarıçapları ve konumları tanımlarken bazı birimler kullandık ama bunların gerçek ekran pikselleriyle ilgisi yok. Aslında, sahnede gördüğümüz her şeyin boyutları büyük ölçüde kamera ayarlarımıza bağlıdır (en-boy oranı veya görüş alanı gibi).
Bu nedenle, merkezi [0,0,0]
olan tüm sahneyi kaplayacak kadar büyük olacak düz bir düzlem nesne yapacağız. Gösteri amacıyla, 2B fare koordinatlarını sabit bir z
ekseni ile 3B olarak Dünya'nın konumuna eşleyeceğiz. Başka bir deyişle, yalnızca x
ve y
dönüştüreceğiz ve nesneden kameramıza olan mesafe olan z
hakkında endişelenmeyeceğiz.
Normalleştirilmiş vektörlerle çalışmamız gerektiğinden, fare ekranı konumlarını, merkezi [0,0]
olacak şekilde -1.0
ile +1.0
arasında bir aralığa dönüştüreceğiz.
Daha sonra, tespit edilen dairenin konumunu 3B'ye eşlemek ve ayrıca daire boyutunu 2B'den 3B'ye eşleştirmek için bu kesin tekniği kullanacağız.
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; }
Burada canlı bir demo görün.

Kavşağı bir uçakla kontrol ettiğimiz için her zaman sadece bir tane olacağını biliyoruz.
Bu kısım için bu kadar. Bir sonraki bölümün sonunda ayrıca, Three.js'deki 3B sahne tarafından kaplanacak olan kamera akışımızla birlikte WebSockets ve bir <video>
öğesi ekleyeceğiz.
3. Hem Ön Uçta hem de Arka Uçta WebSockets
simple-websocket-server
kitaplıkları kurarak Python arka ucunda WebSockets uygulayarak başlayabiliriz. Tornado veya Autobahn gibi birçok farklı kütüphane var. simple-websocket-server
kullanacağız çünkü kullanımı çok kolay ve hiçbir bağımlılığı yok.
pip install git+https://github.com/dpallot/simple-websocket-server.git
WebSocket sunucusunu ayrı bir iş parçacığında çalıştıracağız ve bağlı tüm istemcileri takip edeceğiz.
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 ...
Bekleyen mesajları periyodik olarak kontrol etmesini sağlamak için sunucunun yapıcısındaki selectInterval
parametresini kullandık. Sunucu, yalnızca istemcilerden veri alırken mesaj gönderir veya bir döngüde ana iş parçacığına oturması gerekir. OpenCV'nin de buna ihtiyacı olduğu için ana iş parçacığını engellemesine izin veremeyiz. Kameranın sadece 15 fps'de çalıştığını bildiğimiz için WebSocket sunucusunda aynı aralığı kullanabiliriz.
Ardından, daireleri algıladıktan sonra, bağlı tüm istemcileri yineleyebilir ve görüntü boyutuna göre geçerli konumu ve yarıçapı gönderebiliriz.
for client in clients: msg = json.dumps({'x': x / w, 'y': y / h, 'radius': radius / w}) client.sendMessage(unicode(msg))
Sunucunun tam kaynak kodunun gist.github.com adresinde olduğunu görebilirsiniz.
JavaScript kısmı, fare konumuyla yaptığımız davranışın aynısını taklit edecektir. Ayrıca birkaç mesajı takip edeceğiz ve doğruluğu artırmak için her eksen ve yarıçap için bir ortalama değer hesaplayacağız.
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. };
Dünya'nın konumunu benim mevcut fare konumuma ayarlamak yerine msgHistory
değişkenini kullanacağız.
Kodun tamamını buraya yapıştırmanız muhtemelen gerekli değildir, bu nedenle gist.gihtub.com adresinde uygulama ayrıntılarına bakmaktan çekinmeyin.
Ardından, şeffaf bir arka planla 3B sahnemiz tarafından kaplanacak olan tüm pencereyi dolduran web kamerası akışıyla bir <video>
öğesi ekleyin.
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() {}); }
Nihai sonuç:
Ne yaptığımızı ve yukarıdaki videonun gösterdiğini hızlıca özetlemek için:
- Python arka ucu bir WebSocket sunucusu çalıştırır.
- Sunucu, bir web kamerası akışından OpenCV kullanarak bir daire algılar.
- JavaScript istemcisi,
<video>
öğesini kullanarak aynı web kamerası akışını görüntüler. - İstemci, Three.js kullanarak 3B sahne oluşturur.
- İstemci, WebSocket protokolü aracılığıyla sunucuya bağlanır ve daire konumu ve yarıçapı alır.
Bu demo için kullanılan gerçek kod GitHub'da mevcuttur. Biraz daha karmaşıktır ve ayrıca arka uçtan gelen iki mesaj arasındaki koordinatları enterpolasyon yapar, çünkü web kamerası akışı yalnızca 15 fps'de çalışır, 3D sahne ise 60 fps'de oluşturulur. Orijinal videoyu YouTube'da görebilirsiniz.
uyarılar
Kayda değer bazı bulgular var:
Daire Algılama İdeal Değil
Herhangi bir dairesel nesneyle çalışması harika ama gürültüye ve görüntü deformasyonuna karşı çok hassas, ancak yukarıda da görebileceğiniz gibi sonucumuz oldukça iyi. Ayrıca, en temel kullanım dışında muhtemelen pratik daire algılama örnekleri yoktur. Elips algılamayı kullanmak daha iyi olabilir, ancak şu anda OpenCV'de uygulanmamaktadır.
Her Şey Kurulumunuza Bağlıdır
Yerleşik web kameraları genellikle oldukça kötüdür. 15 fps yeterli değildir ve yalnızca 30 fps'ye yükseltmek hareket bulanıklığını önemli ölçüde azaltır ve algılamayı daha güvenilir hale getirir. Bu noktayı dört noktaya daha ayırabiliriz:
- Kamera bozulmaları
Birçok kamera, en yaygın olarak şekil tespiti üzerinde önemli bir etkiye sahip olan bir balık gözü etkisi olan bazı görüntü bozulmalarına neden olur. OpenCV'nin belgeleri, kameranızı kalibre ederek bozulmayı nasıl azaltacağınıza dair çok basit bir öğreticiye sahiptir. - OpenCV tarafından desteklenen resmi bir cihaz listesi yok
Zaten iyi bir kameranız olsa bile, daha fazla açıklama yapmadan OpenCV ile çalışmayabilir. Ayrıca bir kamera görüntüsü yakalamak için başka bir kitaplık kullanan (IEEE 1394 tabanlı kameralar için libdc1394 gibi) ve ardından yalnızca görüntüleri işlemek için OpenCV kullanan insanlar hakkında da okudum. Brew paket yöneticisi, OpenCV'yi doğrudan libdc1394 desteği ile derlemenize izin verir. - Bazı kameralar OpenCV ile diğerlerinden daha iyi çalışır
Şanslıysanız, saniyedeki kare sayısı gibi bazı kamera seçeneklerini doğrudan kameranızda ayarlayabilirsiniz, ancak OpenCV cihazınızla uyumlu değilse bunun hiçbir etkisi olmayabilir. Yine hiçbir açıklama yapmadan. - Tüm parametreler gerçek dünya kullanımına bağlıdır
Gerçek dünyadaki bir kurulumda kullanıldığında, ışıklar, arka plan rengi veya nesne seçimi gibi şeylerin sonuç üzerinde önemli etkileri olduğundan, algoritmaları ve filtreleri gerçek ortamda test etmeniz şiddetle tavsiye edilir. Buna gün ışığından, etrafta duran insanlardan vb. gelen gölgeler de dahildir.
Desen Eşleştirme Genellikle Daha İyi Bir Seçimdir
Uygulamada kullanılan herhangi bir artırılmış gerçeklik görürseniz, muhtemelen desen eşleştirmeye dayalı olacaktır. Genellikle daha güvenilirdir ve yukarıda açıklanan sorunlardan pek etkilenmez.
Filtreler Çok Önemlidir
Filtrelerin doğru kullanımının biraz tecrübe ve her zaman biraz sihir gerektirdiğini düşünüyorum. Çoğu filtrenin işlem süresi parametrelerine bağlıdır, ancak OpenCV 3.0'da bazıları zaten CUDA C'ye (NVIDIA grafik kartlarıyla yüksek düzeyde paralel programlama için C benzeri bir dil) yeniden yazılmıştır ve bu da önemli performans iyileştirmeleri sağlar.
OpenCV'den Verileri Filtrele
Daire algılamanın bazı yanlışlıklar olduğunu gördük: bazen herhangi bir daire bulamıyor veya yanlış yarıçapı algılıyor. Bu tür bir hatayı en aza indirmek için doğruluğu artırmak için daha karmaşık bir yöntem uygulamak faydalı olacaktır. Örneğimizde x
, y
ve radius
için medyan kullandık, bu çok basit. İyi sonuçlar veren yaygın olarak kullanılan bir filtre, sensörlerden gelen yanlışlıkları azaltmak için dronlar için otopilotlar tarafından kullanılan Kalman filtresidir. Ancak, uygulaması https://mathjs.org'dan yalnızca math.mean()
kullanmak kadar basit değildir.
Çözüm
Benzer bir uygulamayı ilk kez iki yıl önce Madrid'deki Ulusal Doğa Tarihi Müzesi'nde görmüştüm ve benzer bir şeyi yapmanın ne kadar zor olacağını merak etmiştim.
Bu demonun arkasındaki temel fikrim, web'de yaygın olan (WebSockets ve Three.js gibi) ve herhangi bir ön koşul gerektirmeyen araçları kullanmaktı, böylece herkes bunları hemen kullanmaya başlayabilir. Bu nedenle, yazdırmayı veya belirli bir gerçek dünya nesnesine sahip olmayı gerektiren kalıp eşleştirmeyi değil, yalnızca daire algılamayı kullanmak istedim.
Gerçek kamera gereksinimlerini ciddi şekilde hafife aldığımı söylemeliyim. Saniyede yüksek kare sayısı ve iyi aydınlatma, çözünürlükten daha önemlidir. Ayrıca OpenCV ile kamera uyumsuzluğunun bir sorun olacağını beklemiyordum.