Table of Contents

Uso de EasyAR en motores 3D

Para usar EasyAR en un motor 3D, es necesario renderizar la imagen de la cámara y los objetos virtuales. Renderizar objetos virtuales requiere alinearlos con la imagen de la cámara. Al renderizar la imagen de la cámara, algunos parámetros durante la generación y visualización de la imagen pueden no coincidir, como la posición, orientación, formato, relación de aspecto, etc., de la cámara física con la imagen mostrada en pantalla. Esto debe considerarse al renderizar. Si se necesita integrar EasyAR en un motor 3D sin soporte, se debe prestar especial atención a los siguientes detalles.

Recorte de relleno en bordes de la imagen de cámara

El recorte, transposición y codificación de imágenes requieren un alto cálculo computacional. Para reducir cálculos y retrasos, generalmente se usan formatos más básicos. Para facilitar la codificación de video, las imágenes de cámaras físicas suelen alinearse a bloques de 8x8, 16x16, 32x32, 64x64. Por ejemplo, en algunos teléfonos con resolución 1920x1080, la imagen real puede ser 1920x1088 porque 1080 no es múltiplo de 64.

image with padding

Esto requiere eliminar las partes de relleno durante el renderizado. Existen varios métodos: especificar el ancho al cargar la imagen en memoria de video (por ejemplo, en OpenGL usar glPixelStorei(GL_PACK_ROW_LENGTH, ...)), o calcular manualmente las coordenadas UV en el fragment shader y truncar las partes excedidas al muestrear.

Renderizado según la rotación de pantalla

En teléfonos, las imágenes de cámaras físicas suelen estar fijas al dispositivo, sin rotar con la orientación de la pantalla. Sin embargo, los cambios en la orientación del dispositivo afectan nuestra definición de direcciones (arriba/abajo, izquierda/derecha). Al renderizar, la orientación actual de la pantalla también afecta la dirección de la imagen mostrada.

Normalmente, al renderizar se debe determinar un ángulo de rotación de la imagen de cámara relativo a la orientación de pantalla.

Si \(\theta_{screen}\) representa los radianes que la imagen de pantalla rota en sentido horario respecto a su orientación natural, y \(\theta_{phycam}\) representa los radianes que la imagen de cámara física debe rotar en sentido horario para mostrarse correctamente en una pantalla en orientación natural, entonces \(\theta\) representa los radianes que la imagen de cámara física debe rotar en sentido horario para mostrarse en la pantalla actual.

Para cámaras traseras:

\[ \theta = \theta_{phycam} - \theta_{screen} \]

Por ejemplo, en Android con orientación natural, \(\theta_{screen} = 0, \theta_{phycam} = \frac{\pi}{2}\), entonces \(\theta = \frac{\pi}{2}\).

Para cámaras frontales, si tras la rotación se aplica un volteo horizontal:

\[ \theta = \theta_{phycam} + \theta_{screen} \]
Nota

Al rotar la imagen de pantalla, \(\theta\) debe recalcularse inmediatamente en el primer fotograma posterior, de lo contrario puede aparecer una orientación incorrecta momentánea.

Renderizado de fondo de cámara y objetos virtuales

Para renderizar objetos virtuales alineados con la imagen de cámara en teléfonos, debemos colocar ambos en un espacio virtual correspondiente al espacio real, usando el mismo campo de visión y relación de aspecto que la cámara física. La transformación de proyección en perspectiva aplicada a la imagen de cámara y a los objetos virtuales es casi idéntica, con una diferencia clave: la transformación de la imagen de cámara ocurre principalmente en la cámara física, mientras que la de objetos virtuales es un proceso computacional.

Seguimos la convención de OpenGL. Supongamos que el sistema de coordenadas de la cámara define: eje x hacia la derecha, eje y hacia arriba, eje z saliendo de la pantalla. El sistema de coordenadas de recorte define: eje x hacia la derecha, eje y hacia arriba, eje z saliendo de la pantalla, eje w virtual.

La matriz de transformación de proyección en perspectiva requerida para renderizar la imagen de cámara es:

\[ P_i=\left( \begin{array}{cccc} (-1)^{\text{flip}} & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & 1 & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & 1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right)\left( \begin{array}{cccc} \cos (-\theta ) & -\sin (-\theta ) & \phantom{0} & \phantom{0} \\ \sin (-\theta ) & \cos (-\theta ) & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & 1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right)\left( \begin{array}{cccc} s_x & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & s_y & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & 1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right) \]

Donde: flip indica si la imagen se voltea horizontalmente (1: volteada, 0: normal); \(\theta\) es el ángulo de rotación horaria en radianes; \(s_x\), \(s_y\) son factores de escala para rellenado/ajuste proporcional, dependientes de \(\theta\). Esta matriz primero escala la imagen, luego la rota y finalmente la voltea. Se debe usar un rectángulo que cubra toda la pantalla (ej. en OpenGL, vértices en \((-1, -1, 0)\), \((1, -1, 0)\), \((1, 1, 0)\), \((-1, 1, 0)\) con coordenadas UV en las esquinas correspondientes), renderizado con esta matriz.

La matriz de proyección en perspectiva para objetos virtuales es:

\[ P=P_i\left( \begin{array}{cccc} 1 & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & 1 & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & -\frac{f+n}{f-n} & -\frac{2 f n}{f-n} \\ \phantom{0} & \phantom{0} & -1 & \phantom{0} \\ \end{array} \right)\left( \begin{array}{cccc} \frac{2}{w} & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & \frac{2}{h} & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & 1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right)\left( \begin{array}{cccc} 1 & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & -1 & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & -1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right)\left( \begin{array}{cccc} f_x & \phantom{0} & c_x & \phantom{0} \\ \phantom{0} & f_y & c_y & \phantom{0} \\ \phantom{0} & \phantom{0} & 1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right)\left( \begin{array}{cccc} 1 & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & -1 & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & -1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right) \]

Donde: \(n\), \(f\) son los parámetros de recorte cercano/lejano típicos; \(w\), \(h\) son el ancho/alto en píxeles de la imagen de cámara; \(f_x\), \(f_y\), \(c_x\), \(c_y\) son parámetros intrínsecos del modelo de cámara (\(f_x\), \(f_y\): distancias focales en píxeles; \(c_x\), \(c_y\): posición del punto principal). Esta matriz aplica secuencialmente: proyección en perspectiva con parámetros intrínsecos (con dos cambios de sistema de coordenadas por diferencias entre OpenCV/OpenGL), transformación del sistema de coordenadas de píxeles a coordenadas de rectángulo, transformación de recorte cercano/lejano, y la misma transformación de proyección usada para la imagen de cámara.

Simplificando:

\[ P=P_i\left( \begin{array}{cccc} \frac{2 f_x}{w} & \phantom{0} & 1-\frac{2 c_x}{w} & \phantom{0} \\ \phantom{0} & \frac{2 f_y}{h} & -1+\frac{2 c_y}{h} & \phantom{0} \\ \phantom{0} & \phantom{0} & -\frac{f+n}{f-n} & -\frac{2 f n}{f-n} \\ \phantom{0} & \phantom{0} & -1 & \phantom{0} \\ \end{array} \right) \]

Este proceso requiere dos pasos de renderizado: primero la imagen de cámara, luego los objetos virtuales superpuestos.

Algunos motores 3D expresan la matriz de proyección como ángulo de visión horizontal (FOV) y relación de aspecto. Si ignoramos rotación, volteo y desplazamiento del punto principal, puede calcularse como: FOV horizontal \(\alpha=2 arctan{\frac{w}{2 f_x}}\), relación de aspecto \(r=\frac{w}{h}\).

Nota: Este proceso no considera distorsión de lente, ya que en la mayoría de teléfonos es mínima.