Realidade aumentada simples com OpenCV, Three.js e WebSockets

Publicados: 2022-03-10
Resumo rápido ↬ Neste tutorial, usaremos o OpenCV em Python para detectar objetos em forma de círculo em um fluxo de webcam e substituí-los por 3D Earth em Three.js em uma janela do navegador enquanto usamos WebSockets para juntar tudo isso.

A realidade aumentada é geralmente considerada muito difícil de criar. No entanto, é possível fazer projetos visualmente impressionantes usando apenas bibliotecas de código aberto. Neste tutorial, usaremos o OpenCV em Python para detectar objetos em forma de círculo em um fluxo de webcam e substituí-los por 3D Earth em Three.js em uma janela do navegador enquanto usamos WebSockets para juntar tudo isso.

Queremos separar estritamente o front-end e o back-end para torná-lo reutilizável. Em um aplicativo do mundo real, poderíamos escrever o front-end em Unity, Unreal Engine ou Blender, por exemplo, para torná-lo realmente bonito. O front-end do navegador é o mais fácil de implementar e deve funcionar em quase todas as configurações possíveis.

Para simplificar, dividiremos o aplicativo em três partes menores:

  1. O back-end do Python com OpenCV OpenCV lerá o fluxo da webcam e abrirá várias janelas com a imagem da câmera depois de passá-la por vários filtros para facilitar a depuração e nos dar uma pequena visão do que o algoritmo de detecção de círculo realmente vê. A saída desta parte será apenas as coordenadas 2D e o raio do círculo detectado.
  2. Front-end JavaScript com Three.js em um navegador Implementação passo a passo da biblioteca Three.js para renderizar a Terra texturizada com a lua girando em torno dela. A coisa mais interessante aqui será mapear as coordenadas da tela 2D para o mundo 3D. Também aproximaremos as coordenadas e o raio para aumentar a precisão do OpenCV.
  3. WebSockets em ambos front-end e back-end Back-end com servidor WebSockets enviará periodicamente mensagens com coordenadas de círculo e raios detectados para o cliente do navegador.
Resultado final
Mais depois do salto! Continue lendo abaixo ↓

1. Back-end Python com OpenCV

Nosso primeiro passo será apenas importar a biblioteca OpenCV em Python e abrir uma janela com um stream de webcam ao vivo.

Vamos usar o mais novo OpenCV 3.0 (veja as notas de instalação) com o Python 2.7. Observe que a instalação em alguns sistemas pode ser problemática e a documentação oficial não é muito útil. Eu tentei no Mac OS X versão 3.0 do MacPorts e o binário tinha um problema de dependência, então tive que mudar para o Homebrew. Observe também que alguns pacotes OpenCV podem não vir com ligação Python por padrão (você precisa usar algumas opções de linha de comando).

Com o Homebrew eu corri:

 brew install opencv

Isso instala o OpenCV com ligações Python por padrão.

Apenas para testar as coisas, recomendo que você execute o Python no modo interativo (execute python na CLI sem nenhum argumento) e escreva import cv2 . Se o OpenCV estiver instalado corretamente e os caminhos para as ligações do Python estiverem corretos, não deverá gerar nenhum erro.

Mais tarde, também usaremos o numpy do Python para algumas operações simples com matrizes, para que possamos instalá-lo agora também.

 pip install numpy

Lendo a imagem da câmera

Agora podemos testar a câmera:

 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

Com cv2.VideoCapture(0) temos acesso à câmera no índice 0 que é o padrão (geralmente a câmera embutida). Se você quiser usar um diferente, tente números maiores que zero; no entanto, não há uma maneira fácil de listar todas as câmeras disponíveis com a versão atual do OpenCV.

Quando chamamos cv2.imshow('Camera stream', image) pela primeira vez ele verifica se não existe nenhuma janela com este nome e cria uma nova para nós com uma imagem da câmera. A mesma janela será reutilizada para cada iteração do loop principal.

Então usamos capture.read() para esperar e pegar a imagem atual da câmera. Este método também retorna uma propriedade booleana ret caso a câmera seja desconectada ou o próximo quadro não esteja disponível por algum motivo.

No final temos cv2.waitKey(1) que verifica por 1 milissegundo se alguma tecla foi pressionada e retorna seu código. Então, quando pressionamos q saímos do loop, fechamos a janela e o aplicativo terminará.

Se tudo isso funcionar, passamos pela parte mais difícil do aplicativo de back-end, que é fazer a câmera funcionar.

Filtrando imagens da câmera

Para a detecção real do círculo vamos usar circle Hough Transform que é implementado no método cv2.HoughCircles() e agora é o único algoritmo disponível no OpenCV. O importante para nós é que ele precisa de uma imagem em escala de cinza como entrada e usa o algoritmo do detector de bordas Canny para encontrar bordas na imagem. Queremos poder verificar manualmente o que o algoritmo vê para compor uma imagem grande a partir de quatro imagens menores, cada uma com um filtro diferente aplicado.

O detector de bordas Canny é um algoritmo que processa a imagem em tipicamente quatro direções (vertical, horizontal e duas diagonais) e encontra bordas. As etapas reais que esse algoritmo faz são explicadas com mais detalhes na Wikipedia ou brevemente nos documentos do OpenCV.

Em contraste com a correspondência de padrões, esse algoritmo detecta formas circulares para que possamos usar qualquer objeto circular que tenhamos à mão. Vou usar uma tampa de uma jarra de café instantâneo e depois uma caneca de café laranja.

Não precisamos trabalhar com imagens em tamanho real (dependendo da resolução da sua câmera, é claro), então vamos redimensioná-las entre capture.read() e cv2.imshow para largura e altura de 640px de acordo para manter a proporção:

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

Em seguida, queremos convertê-la em uma imagem em escala de cinza e aplicar primeiro o desfoque mediano que remove o ruído e retém as bordas e, em seguida, o detector de bordas Canny para ver com o que o algoritmo de detecção de círculos vai funcionar. Por esta razão, vamos compor uma grade 2x2 com todas as quatro prévias.

 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) 
Grade com visualizações. Canto superior esquerdo: dados brutos da webcam; superior direito: escala de cinza após o desfoque mediano; canto inferior esquerdo: escala de cinza e borda Canny; canto inferior direito: escala de cinza após o desfoque mediano e a borda Canny.

Embora o detector de bordas Canny use o desfoque gaussiano para reduzir o ruído, na minha experiência ainda vale a pena usar o desfoque mediano. Você pode comparar as duas imagens inferiores. O da esquerda é apenas a detecção de bordas do Canny sem nenhum outro filtro. A segunda imagem também é a detecção de borda do Canny, mas desta vez após aplicar o desfoque mediano. Reduziu objetos em segundo plano, o que ajudará na detecção de círculos.

Detectando Círculos com Gradiente Hough

Internamente, o OpenCV usa uma implementação mais eficiente de Hough Circle Transform chamada Hough Gradient Method que usa informações de borda do detector de borda Canny. O método gradiente é descrito em profundidade no livro Learning OpenCV and the Circle Hough Transform na Wikipedia .

Agora é hora da detecção real do círculo:

 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)

Isso retorna uma matriz de todos os círculos detectados. Para simplificar, vamos nos preocupar apenas com o primeiro. Hough Gradient é bastante sensível a formas realmente circulares, então é improvável que isso resulte em detecções falsas. Se sim, aumente o parâmetro at . É por isso que usamos o desfoque mediano acima; ele removeu mais ruído para que possamos usar um limiar mais baixo, tornando a detecção mais tolerante a imprecisões e com menor chance de detectar círculos falsos.

Vamos imprimir o centro do círculo e seu raio no console e também desenhar o círculo encontrado com seu centro na imagem da câmera em uma janela separada. Posteriormente, enviaremos via WebSocket para o navegador. Observe que x , y e radius estão todos em 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)

Isso imprimirá nas tuplas do console como:

 (251, 202, 74) (252, 203, 73) (250, 202, 74) (246, 202, 76) (246, 204, 74) (246, 205, 72) 
Fluxo de webcam com círculos detectados usando Hough Gradient.

Como você pode ver nesta animação, ela não conseguiu encontrar nenhum círculo. Minha câmera embutida tem apenas 15fps e quando movo minha mão rapidamente a imagem fica borrada para não encontrar bordas circulares, nem mesmo depois de aplicar filtros.

No final deste artigo voltaremos a esse problema e falaremos muito sobre configurações específicas da câmera e escolha do algoritmo de detecção, mas já podemos dizer que apesar de minha configuração ser muito ruim (apenas 15fps, iluminação fraca, muito ruído no fundo, o objeto tem baixo contraste), o resultado é razoavelmente bom.

É tudo por agora. Temos as coordenadas x y o radius em pixels de um círculo encontrado na imagem da webcam.

Você pode ver o código-fonte completo para esta parte em gist.github.com.

2. Front-end JavaScript com Three.js em navegadores

A parte de front-end é baseada na biblioteca Three.js (versão r72). Começaremos apenas criando uma esfera texturizada giratória representando a Terra no centro da tela, depois adicionaremos a lua girando em torno dela. No final, mapeamos as coordenadas do mouse da tela 2D para o espaço 3D.

Nossa página HTML consistirá em apenas um único elemento <canvas> . veja index.html em gist.github.com.

Criando a Terra

JavaScript vai ser um pouco mais longo, mas é dividido em várias funções de inicialização, onde cada uma tem um único propósito. As texturas da Terra e da Lua vêm de planetpixelemporium.com. Observe que ao carregar texturas, as regras CORS são aplicadas.

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

Veja uma demonstração ao vivo aqui.

threejs-spinning-earth
Esfera texturizada com Three.js. (Crédito da textura da terra)

Isso foi principalmente apenas coisas básicas do Three.js. Nomes de objetos e métodos são autoexplicativos (como receiveShadow ou castShadow ), mas se você nunca os usou antes, recomendo fortemente que veja os tutoriais de Lee Stemkoski.

Opcionalmente, também podemos desenhar um eixo no centro da tela para nos ajudar com o sistema de coordenadas.

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

Adicionando a Lua

Criar a lua será muito semelhante. A principal diferença é que precisamos definir a posição da lua em relação à 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; }

Veja uma demonstração ao vivo aqui.

Terra e lua com Three.js. (Crédito das texturas da Terra e da Lua)

Mapeando coordenadas 2D para um mundo 3D

Até agora, tudo é bastante óbvio. A parte mais interessante será como converter as coordenadas de tela 2D vindas do OpenCV (veja a saída da detecção circular acima) para um mundo 3D? Quando estávamos definindo raios e posições em Three.js, usamos algumas unidades, mas elas não têm nada a ver com pixels de tela reais. Na verdade, as dimensões de tudo o que vemos na cena são altamente dependentes das configurações de nossa câmera (como proporção ou campo de visão).

Por esta razão, faremos um objeto plano que será grande o suficiente para cobrir toda a cena com seu centro em [0,0,0] . Para fins de demonstração, mapeamos as coordenadas 2D do mouse para a posição da Terra em 3D com um eixo z fixo. Em outras palavras, converteremos apenas x e y e não nos preocuparemos com z , que é a distância do objeto até nossa câmera.

Vamos converter as posições da tela do mouse em um intervalo de -1.0 a +1.0 com seu centro em [0,0] porque precisamos trabalhar com vetores normalizados.

Mais tarde, usaremos essa técnica exata para mapear a posição do círculo detectado para 3D e também para combinar o tamanho do círculo de 2D para 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; }

Veja uma demonstração ao vivo aqui.

threejs-spinning-earth
Posição da Terra em 3D mapeada para a posição do mouse em 2D. (Crédito das texturas da Terra e da Lua)

Como estamos verificando a interseção com um avião, sabemos que sempre haverá apenas um.

Isso é tudo para esta parte. No final da próxima parte também adicionaremos WebSockets e um elemento <video> com nosso stream de câmera que será sobreposto pela cena 3D em Three.js.

3. WebSockets tanto no front-end quanto no back-end

Podemos começar implementando WebSockets no back-end do Python instalando bibliotecas simple-websocket-server . Existem muitas bibliotecas diferentes, como Tornado ou Autobahn. Usaremos simple-websocket-server porque é muito fácil de usar e não tem dependências.

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

Executaremos o servidor WebSocket em um thread separado e acompanharemos todos os clientes conectados.

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

Usamos o parâmetro selectInterval no construtor do servidor para fazer com que ele verifique periodicamente as mensagens pendentes. O servidor envia mensagens apenas ao receber dados de clientes ou precisa ficar no encadeamento principal em um loop. Não podemos deixá-lo bloquear o thread principal porque o OpenCV também precisa dele. Como sabemos que a câmera roda apenas a 15fps, podemos usar o mesmo intervalo no servidor WebSocket.

Então, após detectarmos os círculos, podemos iterar todos os clientes conectados e enviar a posição atual e o raio em relação ao tamanho da imagem.

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

Você pode ver o código-fonte completo do servidor em gist.github.com.

A parte JavaScript irá imitar o mesmo comportamento que fizemos com a posição do mouse. Também acompanharemos as poucas mensagens e calcularemos um valor médio para cada eixo e raio para melhorar a precisão.

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

Em vez de definir a posição da Terra para minha posição atual do mouse, usaremos a variável msgHistory .

Provavelmente não é necessário colar todo o código aqui, então fique à vontade para ver os detalhes de implementação em gist.gihtub.com.

Em seguida, adicione um elemento <video> com o fluxo da webcam preenchendo toda a janela que será sobreposta por nossa cena 3D com um fundo transparente.

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

O resultado final:

Aplicação final com a Terra mapeada para as coordenadas encontradas pelo OpenCV.

Para recapitular rapidamente o que fizemos e o que o vídeo acima mostra:

  1. O back-end do Python executa um servidor WebSocket.
  2. O servidor detecta um círculo usando OpenCV de um fluxo de webcam.
  3. O cliente JavaScript exibe o mesmo fluxo de webcam usando o elemento <video> .
  4. O cliente renderiza a cena 3D usando Three.js.
  5. O cliente se conecta ao servidor via protocolo WebSocket e recebe a posição do círculo e o raio.

O código real usado para esta demonstração está disponível no GitHub. É um pouco mais sofisticado e também interpola as coordenadas entre duas mensagens do back-end porque o fluxo da webcam é executado apenas a 15 fps enquanto a cena 3D é renderizada a 60 fps. Você pode ver o vídeo original no YouTube.

Ressalvas

Há alguns achados dignos de nota:

Detecção de círculo não é ideal

É ótimo que funcione com qualquer objeto circular, mas é muito sensível ao ruído e à deformação da imagem, embora, como você pode ver acima, nosso resultado seja muito bom. Além disso, provavelmente não há exemplos práticos de detecção de círculos disponíveis além do uso mais básico. Pode ser melhor usar a detecção de elipse, mas ela não está implementada no OpenCV no momento.

Tudo depende da sua configuração

As webcams embutidas geralmente são muito ruins. 15 fps não é suficiente e apenas aumentá-lo para 30 fps reduz significativamente o desfoque de movimento e torna a detecção mais confiável. Podemos dividir este ponto em mais quatro pontos:

  • Distorções da câmera
    Muitas câmeras introduzem alguma distorção de imagem, mais comumente um efeito olho de peixe que tem uma influência significativa na detecção de forma. A documentação do OpenCV tem um tutorial bem direto sobre como reduzir a distorção calibrando sua câmera.
  • Não há uma lista oficial de dispositivos suportados pelo OpenCV
    Mesmo que você já tenha uma boa câmera, ela pode não funcionar com o OpenCV sem maiores explicações. Eu também li sobre pessoas usando alguma outra biblioteca para capturar uma imagem de câmera (como libdc1394 para câmeras baseadas em IEEE 1394) e depois usando OpenCV apenas para processar as imagens. O gerenciador de pacotes Brew permite compilar o OpenCV diretamente com suporte a libdc1394.
  • Algumas câmeras funcionam melhor com OpenCV do que outras
    Se você tiver sorte, pode definir algumas opções de câmera, como quadros por segundo, diretamente em sua câmera, mas também pode não ter nenhum efeito se o OpenCV não for amigável com o seu dispositivo. Mais uma vez, sem qualquer explicação.
  • Todos os parâmetros dependem de um uso do mundo real
    Quando usado em uma instalação do mundo real, é altamente recomendável testar os algoritmos e filtros no ambiente real porque coisas como luzes, cor de fundo ou escolha de objeto têm efeitos significativos no resultado. Isso também inclui sombras da luz do dia, pessoas ao redor e assim por diante.

A correspondência de padrões geralmente é uma escolha melhor

Se você vir qualquer realidade aumentada usada na prática, provavelmente será baseada em correspondência de padrões. Geralmente é mais confiável e não é tão afetado pelos problemas descritos acima.

Filtros são cruciais

Acho que o uso correto de filtros requer alguma experiência e sempre um pouco de mágica. O tempo de processamento da maioria dos filtros depende de seus parâmetros, embora no OpenCV 3.0 alguns deles já estejam reescritos em CUDA C (uma linguagem tipo C para programação altamente paralela com placas gráficas NVIDIA) o que traz melhorias significativas de desempenho.

Filtrar dados do OpenCV

Vimos que a detecção de círculos tem algumas imprecisões: às vezes não encontra nenhum círculo ou detecta o raio errado. Para minimizar esse tipo de erro valeria a pena implementar algum método mais sofisticado para melhorar a precisão. Em nosso exemplo usamos mediana para x , y e radius , o que é muito simples. Um filtro comumente usado com bons resultados é o filtro Kalman, utilizado por pilotos automáticos de drones para reduzir a imprecisão proveniente de sensores. No entanto, sua implementação não é tão simples quanto usar apenas math.mean() de https://mathjs.org.

Conclusão

Vi pela primeira vez um aplicativo semelhante no Museu Nacional de História Natural de Madri há dois anos e me perguntei o quão difícil seria fazer algo semelhante.

Minha ideia central por trás desta demonstração foi usar ferramentas comuns na web (como WebSockets e Three.js) e não exigem nenhum pré-requisito para que qualquer pessoa possa começar a usá-las imediatamente. É por isso que eu queria usar apenas detecção de círculo e não correspondência de padrões, o que exigiria imprimir ou ter algum objeto específico do mundo real.

Devo dizer que subestimei severamente os requisitos reais da câmera. Altos quadros por segundo e boa iluminação são mais importantes que a resolução. Eu também não esperava que a incompatibilidade da câmera com o OpenCV fosse um problema.