OpenCV, Three.js 및 WebSocket을 사용한 간단한 증강 현실
게시 됨: 2022-03-10증강 현실은 일반적으로 만들기가 매우 어려운 것으로 간주됩니다. 그러나 오픈 소스 라이브러리만 사용하여 시각적으로 인상적인 프로젝트를 만드는 것은 가능합니다. 이 자습서에서는 Python의 OpenCV 를 사용하여 웹캠 스트림에서 원 모양의 개체를 감지하고 브라우저 창에서 Three.js 의 3D Earth로 교체하는 동시에 WebSocket 을 사용하여 이 모든 것을 결합합니다.
재사용이 가능하도록 프론트엔드와 백엔드를 엄격하게 분리하고 싶습니다. 예를 들어 실제 응용 프로그램에서 Unity, Unreal Engine 또는 Blender로 프런트 엔드를 작성하여 정말 멋지게 만들 수 있습니다. 브라우저 프런트 엔드는 구현하기 가장 쉽고 거의 모든 가능한 구성에서 작동해야 합니다.
일을 단순하게 유지하기 위해 앱을 세 개의 작은 부분으로 나눕니다.
- OpenCV가 있는 Python 백엔드 OpenCV는 웹캠 스트림을 읽고 여러 필터를 통과한 후 카메라 이미지가 있는 여러 창을 열어 디버깅을 용이하게 하고 원 감지 알고리즘이 실제로 보는 것에 대한 약간의 통찰력을 제공합니다. 이 부분의 출력은 감지된 원의 2D 좌표와 반지름뿐입니다.
- 브라우저에서 Three.js를 사용하는 JavaScript 프론트엔드 달이 회전하는 질감 있는 지구를 렌더링하기 위한 Three.js 라이브러리의 단계별 구현. 여기서 가장 흥미로운 점은 2D 화면 좌표를 3D 세계로 매핑하는 것입니다. 또한 OpenCV의 정확도를 높이기 위해 좌표와 반경을 근사화할 것입니다.
- 프론트엔드와 백엔드 모두에 있는 WebSockets WebSockets 서버가 있는 백엔드는 주기적으로 브라우저 클라이언트에 원 좌표와 반경이 감지된 메시지를 보냅니다.
1. OpenCV를 사용한 Python 백엔드
첫 번째 단계는 Python에서 OpenCV 라이브러리를 가져오고 라이브 웹캠 스트림으로 창을 여는 것입니다.
Python 2.7과 함께 최신 OpenCV 3.0(설치 정보 참조)을 사용할 것입니다. 일부 시스템에 설치하면 문제가 발생할 수 있으며 공식 문서는 그다지 도움이 되지 않습니다. MacPorts의 Mac OS X 버전 3.0에서 직접 시도했는데 바이너리에 종속성 문제가 있어서 대신 Homebrew로 전환해야 했습니다. 또한 일부 OpenCV 패키지는 기본적으로 Python 바인딩과 함께 제공되지 않을 수 있습니다(일부 명령줄 옵션을 사용해야 함).
Homebrew를 사용하여 다음을 실행했습니다.
brew install opencv
이것은 기본적으로 Python 바인딩과 함께 OpenCV를 설치합니다.
테스트를 위해 대화형 모드에서 Python을 실행하고(인수 없이 CLI에서 python
실행) import cv2
를 작성하는 것이 좋습니다. OpenCV가 제대로 설치되고 Python 바인딩 경로가 올바른 경우 오류가 발생하지 않아야 합니다.
나중에 우리는 또한 Python의 numpy
를 사용하여 행렬을 사용하여 간단한 작업을 수행할 수 있으므로 지금 설치할 수도 있습니다.
pip install numpy
카메라 이미지 읽기
이제 카메라를 테스트할 수 있습니다.
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)
을 사용하면 기본값(보통 내장 카메라)인 인덱스 0
에 있는 카메라에 액세스할 수 있습니다. 다른 것을 사용하려면 0보다 큰 숫자를 시도하십시오. 그러나 현재 OpenCV 버전에서 사용 가능한 모든 카메라를 나열하는 쉬운 방법은 없습니다.
처음으로 cv2.imshow('Camera stream', image)
를 호출하면 이 이름의 창이 없는지 확인하고 카메라의 이미지로 새 창을 만듭니다. 메인 루프의 각 반복에 대해 동일한 창이 재사용됩니다.
그런 다음 capture.read()
를 사용하여 대기하고 현재 카메라 이미지를 가져옵니다. 이 메서드는 카메라 연결이 끊겼거나 어떤 이유로 다음 프레임을 사용할 수 없는 경우 부울 속성 ret
를 반환합니다.
마지막에 cv2.waitKey(1)
키가 눌렸는지 여부를 1밀리초 동안 확인하고 해당 코드를 반환합니다. 따라서 q
를 누르면 루프에서 빠져나오고 창을 닫으면 앱이 종료됩니다.
이 모든 것이 작동한다면 카메라를 작동시키는 백엔드 앱의 가장 어려운 부분을 통과한 것입니다.
카메라 이미지 필터링
실제 원 감지를 위해 우리는 cv2.HoughCircles()
메소드에서 구현된 circle Hough Transform 을 사용할 것이며 현재 OpenCV에서 사용할 수 있는 유일한 알고리즘입니다. 우리에게 중요한 것은 입력으로 회색조 이미지가 필요하고 내부에서 Canny 가장자리 감지기 알고리즘을 사용하여 이미지의 가장자리를 찾는 것입니다. 알고리즘이 보는 것을 수동으로 확인할 수 있기를 원하므로 각각 다른 필터가 적용된 4개의 작은 이미지에서 하나의 큰 이미지를 구성합니다.
Canny edge Detector는 일반적으로 4가지 방향(수직, 수평, 2개의 대각선)으로 이미지를 처리하고 가장자리를 찾는 알고리즘입니다. 이 알고리즘이 수행하는 실제 단계는 Wikipedia 또는 OpenCV 문서에 간략하게 설명되어 있습니다.
패턴 일치와 대조적으로 이 알고리즘은 원형 모양을 감지하므로 원형으로 손에 넣어야 하는 모든 개체를 사용할 수 있습니다. 나는 인스턴트 커피 용기의 뚜껑을 사용하고 오렌지 커피 머그를 사용할 것입니다.
전체 크기 이미지로 작업할 필요가 없으므로(물론 카메라 해상도에 따라 다름) capture.read capture.read()
와 cv2.imshow
사이에서 가로 세로 비율을 유지하기 위해 가로 세로 640px로 크기를 조정할 것입니다.
width, height = image.shape scale = 640.0 / width image = cv2.resize(image, (0,0), fx=scale, fy=scale)
그런 다음 이를 회색조 이미지로 변환하고 노이즈를 제거하고 가장자리를 유지하는 중앙값 흐림 효과 를 먼저 적용한 다음 Canny 가장자리 감지기를 적용하여 원 탐지 알고리즘이 작동할 대상을 확인하려고 합니다. 이러한 이유로 4개의 미리 보기가 모두 포함된 2x2 그리드를 구성합니다.
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 에지 감지기가 가우시안 흐림 효과를 사용하여 노이즈를 줄이더라도 내 경험상 중간 흐림 효과도 사용할 가치가 있습니다. 두 개의 하단 이미지를 비교할 수 있습니다. 왼쪽에 있는 것은 다른 필터 없이 Canny edge 감지입니다. 두 번째 이미지도 Canny edge 감지이지만 이번에는 중앙값 흐림 효과를 적용한 후입니다. 그것은 원 감지에 도움이 될 백그라운드에서 개체를 줄였습니다.
Hough Gradient로 원 감지하기
내부적으로 OpenCV는 Canny 에지 검출기의 에지 정보를 사용하는 Hough Gradient Method라고 하는 Hough Circle Transform의 보다 효율적인 구현을 사용합니다. 그래디언트 방법은 Learning OpenCV and Circle Hough Transform on Wikipedia 책에 자세히 설명되어 있습니다.
이제 실제 원 감지 시간입니다.
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)
이것은 감지된 모든 원의 배열을 반환합니다. 간단하게 하기 위해 우리는 첫 번째 것만 신경 쓸 것입니다. Hough Gradient는 실제로 원형 모양에 매우 민감하므로 잘못된 감지가 발생할 가능성은 거의 없습니다. 그렇다면 at
매개변수를 늘리십시오. 이것이 위에서 중앙값 흐림 효과를 사용한 이유입니다. 더 많은 노이즈를 제거하여 더 낮은 임계값을 사용할 수 있도록 하여 부정확성에 대한 탐지를 더욱 허용하고 잘못된 원을 탐지할 가능성을 낮췄습니다.
우리는 원의 중심과 반지름을 콘솔에 인쇄하고 중심이 있는 발견된 원을 별도의 창에 있는 카메라의 이미지에 그립니다. 나중에 WebSocket을 통해 브라우저로 보낼 것입니다. x
, y
및 radius
은 모두 픽셀 단위입니다.
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)
다음과 같은 콘솔 튜플에 인쇄됩니다.
(251, 202, 74) (252, 203, 73) (250, 202, 74) (246, 202, 76) (246, 204, 74) (246, 205, 72)
이 애니메이션에서 볼 수 있듯이 서클을 전혀 찾지 못했습니다. 내 내장 카메라에는 15fps만 있으며 손을 빠르게 움직이면 이미지가 흐려져 필터를 적용한 후에도 원 가장자리를 찾지 못합니다.
이 기사의 끝에서 우리는 이 문제로 돌아와 카메라별 설정과 감지 알고리즘 선택에 대해 많이 이야기할 것이지만 이미 설정이 매우 나쁘다고 말할 수 있습니다(단 15fps, 열악한 조명, 배경에 노이즈가 많고 개체의 대비가 낮음) 결과는 상당히 좋습니다.
지금은 여기까지입니다. 웹캠 이미지에서 찾은 원의 x
및 y
좌표와 radius
(픽셀 단위)이 있습니다.
이 부분의 전체 소스 코드는 gist.github.com에서 볼 수 있습니다.
2. 브라우저에서 Three.js를 사용하는 JavaScript 프론트엔드
프런트 엔드 부분은 Three.js(버전 r72) 라이브러리를 기반으로 합니다. 먼저 화면 중앙에 지구를 나타내는 회전하는 질감의 구를 만든 다음 그 주위를 회전하는 달을 추가합니다. 마지막으로 2D 화면 마우스 좌표를 3D 공간에 매핑합니다.
HTML 페이지는 단일 <canvas>
요소로 구성됩니다. gist.github.com에서 index.html 을 참조하세요.
지구 만들기
JavaScript는 조금 더 길어질 것이지만 각각 단일 목적을 갖는 여러 초기화 함수로 분할됩니다. 지구와 달 텍스처는 Planetpixelemporium.com에서 가져왔습니다. 텍스처를 로드할 때 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); });
여기에서 라이브 데모를 확인하세요.


이것은 대부분 기본적인 Three.js 항목이었습니다. 개체 및 메서드 이름은 ( receiveShadow
또는 castShadow
와 같이) 설명이 필요 없지만 이전에 사용한 적이 없다면 Lee Stemkoski의 자습서를 보는 것이 좋습니다.
선택적으로 좌표계에 도움이 되도록 화면 중앙에 축을 그릴 수도 있습니다.
var axes = new THREE.AxisHelper(60); axes.position.set(0, 0, 0); scene.add(axes);
달 추가
달을 만드는 것은 매우 유사할 것입니다. 주요 차이점은 지구를 기준으로 달의 위치를 설정해야 한다는 것입니다.
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; }
여기에서 라이브 데모를 확인하세요.
2D 좌표를 3D 세계에 매핑하기
지금까지는 모든 것이 매우 명확합니다. 가장 흥미로운 부분은 OpenCV(위의 원형 감지 출력 참조)에서 오는 2D 화면 좌표를 3D 세계로 바꾸는 방법입니다. Three.js에서 반경과 위치를 정의할 때 몇 가지 단위를 사용했지만 실제 화면 픽셀과 아무 관련이 없습니다. 사실 장면에서 보는 모든 것의 치수는 카메라 설정(종횡비 또는 시야각과 같은)에 크게 의존합니다.
이러한 이유로 [0,0,0]
에 중심을 두고 전체 장면을 덮을 만큼 충분히 큰 평면 객체를 만들 것입니다. 데모 목적으로 2D 마우스 좌표를 고정 z
축을 사용하여 3D에서 지구의 위치에 매핑합니다. 즉, x
와 y
만 변환하고 객체에서 카메라까지의 거리인 z
에 대해서는 걱정하지 않습니다.
정규화된 벡터로 작업해야 하기 때문에 마우스 화면 위치를 중심이 [0,0]
인 -1.0
에서 +1.0
범위로 변환합니다.
나중에 우리는 이 정확한 기술을 사용하여 감지된 원의 위치를 3D로 매핑하고 원 크기를 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; }
여기에서 라이브 데모를 확인하세요.

우리는 비행기와의 교차점을 확인하고 있기 때문에 항상 하나만 있을 것이라는 것을 알고 있습니다.
이것이 이 부분의 전부입니다. 다음 부분의 끝에서 우리는 또한 Three.js의 3D 장면에 의해 오버레이될 카메라 스트림과 함께 WebSockets 및 <video>
요소를 추가할 것입니다.
3. 프론트엔드와 백엔드 모두의 웹소켓
simple-websocket-server
라이브러리를 설치하여 Python 백엔드에서 WebSocket을 구현하는 것으로 시작할 수 있습니다. Tornado 또는 Autobahn과 같은 다양한 라이브러리가 있습니다. 사용하기 매우 쉽고 종속성이 없기 때문에 simple-websocket-server
를 사용할 것입니다.
pip install git+https://github.com/dpallot/simple-websocket-server.git
WebSocket 서버를 별도의 스레드에서 실행하고 연결된 모든 클라이언트를 추적합니다.
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 ...
서버의 생성자에서 selectInterval
매개변수를 사용하여 보류 중인 메시지를 주기적으로 확인하도록 했습니다. 서버는 클라이언트로부터 데이터를 수신할 때만 메시지를 보내거나 루프의 메인 스레드에 있어야 합니다. OpenCV에서도 필요하기 때문에 메인 스레드를 차단할 수 없습니다. 카메라가 15fps에서만 실행된다는 것을 알고 있기 때문에 WebSocket 서버에서 동일한 간격을 사용할 수 있습니다.
그런 다음 원을 감지한 후 연결된 모든 클라이언트를 반복하고 이미지 크기에 상대적인 현재 위치와 반경을 보낼 수 있습니다.
for client in clients: msg = json.dumps({'x': x / w, 'y': y / h, 'radius': radius / w}) client.sendMessage(unicode(msg))
서버의 전체 소스 코드는 gist.github.com에 있습니다.
JavaScript 부분은 마우스 위치와 동일한 동작을 모방합니다. 또한 몇 가지 메시지를 추적하고 정확도를 개선하기 위해 각 축과 반경의 평균값을 계산합니다.
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. };
지구의 위치를 현재 마우스 위치로 설정하는 대신 msgHistory
변수를 사용합니다.
여기에 전체 코드를 붙여넣을 필요는 없으므로 gist.gihtub.com에서 구현 세부 정보를 자유롭게 살펴보십시오.
그런 다음 투명한 배경이 있는 3D 장면이 오버레이될 전체 창을 채우는 웹캠 스트림이 있는 <video>
요소를 하나 추가합니다.
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() {}); }
최종 결과:
우리가 한 일과 위의 비디오가 보여주는 것을 빠르게 요약하려면:
- Python 백엔드는 WebSocket 서버를 실행합니다.
- 서버는 웹캠 스트림에서 OpenCV를 사용하여 원을 감지합니다.
- JavaScript 클라이언트는
<video>
요소를 사용하여 동일한 웹캠 스트림을 표시합니다. - 클라이언트는 Three.js를 사용하여 3D 장면을 렌더링합니다.
- 클라이언트는 WebSocket 프로토콜을 통해 서버에 연결하고 원 위치와 반경을 받습니다.
이 데모에 사용된 실제 코드는 GitHub에서 사용할 수 있습니다. 웹캠 스트림은 15fps에서만 실행되는 반면 3D 장면은 60fps에서 렌더링되기 때문에 약간 더 정교하고 백엔드의 두 메시지 사이의 좌표를 보간합니다. 유튜브에서 원본 영상을 보실 수 있습니다.
주의 사항
주목할 만한 몇 가지 발견 사항이 있습니다.
원 감지는 이상적이지 않습니다.
어떤 원형 물체와도 작동하지만 노이즈와 이미지 변형에 매우 민감하지만 위에서 볼 수 있듯이 결과는 꽤 좋습니다. 또한 가장 기본적인 사용법 외에 사용할 수 있는 원 감지의 실제 예는 없을 것입니다. 타원 감지를 사용하는 것이 더 나을 수 있지만 지금은 OpenCV에서 구현되지 않습니다.
모든 것은 설정에 따라 다릅니다.
내장 웹캠은 일반적으로 매우 좋지 않습니다. 15fps로는 충분하지 않으며 30fps로 늘리면 모션 블러가 크게 줄어들고 감지가 더 안정적입니다. 우리는 이 점을 네 가지로 나눌 수 있습니다.
- 카메라 왜곡
많은 카메라에서 일부 이미지 왜곡, 가장 일반적으로 모양 감지에 큰 영향을 미치는 어안 효과가 발생합니다. OpenCV의 문서에는 카메라를 보정하여 왜곡을 줄이는 방법에 대한 매우 간단한 자습서가 있습니다. - OpenCV에서 지원하는 공식 장치 목록이 없습니다.
이미 좋은 카메라가 있더라도 추가 설명 없이는 OpenCV에서 작동하지 않을 수 있습니다. 또한 다른 라이브러리를 사용하여 카메라 이미지(IEEE 1394 기반 카메라의 경우 libdc1394)를 캡처한 다음 OpenCV를 사용하여 이미지를 처리하는 사람들에 대해서도 읽었습니다. Brew 패키지 관리자를 사용하면 libdc1394 지원으로 OpenCV를 직접 컴파일할 수 있습니다. - 일부 카메라는 다른 카메라보다 OpenCV에서 더 잘 작동합니다.
운이 좋다면 카메라에서 직접 초당 프레임 수와 같은 일부 카메라 옵션을 설정할 수 있지만 OpenCV가 장치와 호환되지 않는 경우 전혀 효과가 없을 수도 있습니다. 다시 말하지만, 아무 설명도 없이. - 모든 매개변수는 실제 사용에 따라 다릅니다.
실제 설치에서 사용하는 경우 조명, 배경색 또는 개체 선택과 같은 것들이 결과에 상당한 영향을 미치기 때문에 실제 환경에서 알고리즘과 필터를 테스트하는 것이 좋습니다. 여기에는 일광의 그림자, 주변에 서 있는 사람 등이 포함됩니다.
일반적으로 패턴 일치가 더 나은 선택입니다.
실제로 사용되는 증강 현실을 본다면 아마도 패턴 매칭을 기반으로 할 것입니다. 일반적으로 더 안정적이며 위에서 설명한 문제의 영향을 받지 않습니다.
필터는 중요합니다
필터를 올바르게 사용하려면 약간의 경험과 항상 약간의 마법이 필요하다고 생각합니다. OpenCV 3.0에서 일부 필터는 이미 CUDA C(NVIDIA 그래픽 카드를 사용한 고도로 병렬 프로그래밍을 위한 C 유사 언어)로 다시 작성되어 성능이 크게 향상되지만 대부분의 필터의 처리 시간은 매개변수에 따라 다릅니다.
OpenCV에서 데이터 필터링
우리는 원 감지가 약간의 부정확성을 가지고 있음을 확인했습니다. 때때로 원을 찾지 못하거나 잘못된 반경을 감지합니다. 이러한 유형의 오류를 최소화하려면 정확도를 개선하기 위해 좀 더 정교한 방법을 구현하는 것이 좋습니다. 이 예에서 우리는 x
, y
및 radius
에 대해 중앙값을 사용했습니다. 이는 매우 간단합니다. 일반적으로 좋은 결과를 얻는 필터는 센서에서 오는 부정확성을 줄이기 위해 무인 항공기의 자동 조종 장치에서 사용하는 칼만 필터입니다. 그러나 그 구현은 https://mathjs.org에서 math.mean()
을 사용하는 것만큼 간단하지 않습니다.
결론
2년 전 마드리드 국립 자연사 박물관에서 비슷한 응용 프로그램을 처음 보았고 비슷한 것을 만드는 것이 얼마나 어려울지 궁금했습니다.
이 데모의 핵심 아이디어는 웹에서 일반적인 도구(WebSocket 및 Three.js와 같은)를 사용하고 전제 조건이 필요하지 않아 누구나 바로 사용할 수 있도록 하는 것이었습니다. 그래서 패턴 일치가 아닌 원 감지만 사용하고 싶었습니다. 패턴 일치는 인쇄하거나 특정 실제 개체가 있어야 합니다.
나는 실제 카메라 요구 사항을 심각하게 과소 평가했다고 말해야합니다. 초당 높은 프레임과 좋은 조명은 해상도보다 더 중요합니다. 또한 OpenCV와의 카메라 비호환성이 문제가 될 것이라고는 예상하지 못했습니다.