Introducción
Plantillas 3D en Tulip Vision
Tulip utiliza plantillas de marcadores 3D porque es la forma más precisa de rastrear objetos con alta fidelidad en un entorno muy ruidoso como el taller. Los marcadores de plantilla son fáciles de pegar a las cosas. Se pueden colocar en una papelera, una carretilla elevadora, un semirremolque o una diminuta herramienta de relojero: funcionan a cualquier escala, siempre que la cámara sea capaz de captarlos. Un caso de uso clásico para las plantillas es, naturalmente, el seguimiento de los objetos de la plantilla en los que encaja la pieza de trabajo, pero con las capacidades 3D, estamos añadiendo la forma de seguir objetos complejos, con marcadores pegados en diferentes superficies del objeto.
Las plantillas están formadas por muchos marcadores 3D agrupados. Los marcadores 3D señalan un punto del objeto 3D que está fijo en el "marco" (Un marco son los 3 ejes utilizados para definir la posición y la orientación, los ejes X, Y y Z) del objeto, suponiendo que el objeto sea un cuerpo rígido. Siempre que veamos el marcador, sabremos que está fijado en el mismo punto del objeto. El grupo de marcadores jig - define un objeto completo. Las plantillas pueden definir formas 3D complejas, con algunos de los marcadores visibles y otros ocultos. Los marcadores visibles nos ayudan a encontrar una ubicación y orientación para el objeto. Cuando los marcadores ocultos aparecen a la vista - compensan a los otros marcadores que ahora están ocultos.
Optimización del ajuste del paquete
Al definir una plantilla a partir de marcadores capturamos muchas vistas del objeto marcado desde distintos ángulos con respecto a la cámara. Cada fotograma capturado por la cámara proporciona otra "vista" del objeto con sus marcadores. Y en cada fotograma calculamos la posición de los marcadores en el objeto y sus relaciones (transformaciones). Tras capturar suficientes vistas, las combinamos para obtener un modelo 3D holístico del objeto, es decir, el proceso de "registro". Sin embargo, debido a varias razones que tienen que ver con la óptica y la estabilidad numérica de los cálculos, las vistas alrededor del objeto no siempre se alinean perfectamente. De hecho, cuantas más vistas tomemos del objeto, el error acumulativo en el registro se hace mayor hasta que el registro final puede resultar inútil. Aquí es donde entra en juego el "ajuste de paquetes".
El ajuste de paquetes (BA) es un proceso de optimización numérica que combate el error acumulado al registrar varias vistas de cámara para reconstruir una geometría. En el BA tradicional, se optimizan casi todos los parámetros de la reconstrucción, incluido el modelado óptico de la cámara. Pero antes de explicar el proceso de BA, debemos definir los parámetros en juego que requieren optimización. Recomendamos encarecidamente consultar el maravilloso libro del profesor Richard Szeliski "Computer Vision: Algorithms and Applications", Springer press, 2011 (capítulo 7 pp320).
Pose cámara-objeto con marcadores 3D
Cuando una cámara mira a un marcador, que es un objeto plano, es posible calcular la orientación del marcador con respecto al origen de la cámara. Considere el siguiente diagrama:
El marcador es visible en la vista de la cámara y se proyecta en el plano de la imagen, una construcción conceptual que ayuda a formular la traslación entre coordenadas de píxeles 3D y 2D. Sin embargo, cuando tomamos una fotografía de la escena con el marcador, no conocemos los parámetros del punto 3D, sólo podemos detectar dónde se proyectaron esos puntos 3D en la imagen 2D. Esta proyección puede captarse mediante la siguiente ecuación:
Las X, Y y Z son las coordenadas 3D del, por ejemplo, centro del marcador, mientras que x, e y son la posición en píxeles 2D de las esquinas en la imagen. El marca la ambigüedad en los parámetros, una pieza de información que falta, causada por el hecho de que un punto 3D en el mundo puede aparecer en cualquier parte del rayo que va desde el centro de la cámara y el punto 3D real (véanse los puntos naranjas difuminados en el diagrama). En otras palabras, los objetos de cualquier escala arbitraria pueden aparecer en la imagen en cualquier tamaño arbitrario, todo depende de su distancia a la cámara. También tenemos en esa ecuación la rotación 3D (parámetros r) y la traslación (parámetros t) del objeto o inversamente de la cámara, sin pérdida de generalidad. Los parámetros f y c son los "parámetros intrínsecos" que modelan la óptica de la cámara (muy vagamente aquí en este ejemplo de juguete).
No obstante, existe una relación lineal entre los puntos 3D y los puntos 2D, y si conociéramos todos los parámetros de esta ecuación podríamos calcular: (1) la posición 3D en el mundo real del marcador a partir de la coordenada de píxel 2D, y (2) la rotación ri y la traslación tx,y,z del marcador con respecto a la cámara. Observaremos que para trabajar con coordenadas 2D no podemos simplemente prescindir del parámetro en nuestra ecuación, y de hecho para obtener los puntos de píxel dividiríamos por la última entrada del vector: x'=λx, x =x'/λ, y'=λy, y=y'/λ.
Dados suficientes puntos correspondientes de 2D a 3D, podemos reordenar la ecuación anterior en un conjunto de ecuaciones lineales (homogéneas) tales que podamos recuperar R y t. Utilizando los marcadores 3D podemos obtener al menos 4 de estos pares de puntos 2D-3D correspondientes para cada marcador. Los puntos 2D los obtenemos observando la imagen y encontrando las esquinas. Los puntos 3D vienen dados por la disposición del marcador, que también está bajo nuestro control (ya que imprimimos el marcador). El proceso global de recuperación de la pose se conoce como Perspectiva-n-Punto y existen muchos enfoques y algoritmos para su solución. Por ejemplo, así es como se encontraría la pose de la cámara en Python con OpenCV a partir de un conjunto de puntos 2D-3D alineados:
_, R, t = cv2.solvePnP(aligned_3d, aligned_2d, K, dc)
El problema de la optimización
Anotemos la última operación de "proyección" como sigue:
$$P_{2D}=\mathrm{Proj}([R|t], P_{3D})$$
Es decir, obtenemos la posición 2D del píxel (P2D) a partir de la proyección del punto 3D P3D y de la rotación R y la traslación t entre la cámara y el objeto. El principal problema de este régimen de proyección es que se basa en cálculos realizados sobre los puntos 2D en coordenadas de píxeles, que no son muy precisos y además están cuantizados en la cuadrícula de píxeles. Si reproyectamos los puntos 3D (los proyectamos de nuevo a 2D en la imagen) tras hallar la pose del objeto [R|t], a menudo encontramos las posiciones 2D con un desfase respecto a sus posiciones en la imagen. La siguiente imagen muestra los desfases, que suelen producirse con mayor contraste en situaciones extremas, como un fuerte ángulo respecto a la cámara, o en presencia de desenfoque.
Nuestro objetivo es encontrar los parámetros de posición de la cámara de forma que todos estos desplazamientos 2D sean lo más pequeños posible. Para poner esto en una fórmula, deseamos resolver el siguiente problema de minimización, que busca los óptimos [R|t] que minimizan los residuos:
\hat{[R|t]} = \mathop{\arg\min}_[R|t]} \sum_i \Vert \mathrm{Proj}([R|t],P_i^{\mathrm{3D}}) - P_i^{\mathrm{2D}} \Vert^2
La diferencia entre el punto 3D reproyectado y el punto 2D se denomina residuo. Y en general llamamos a este problema un problema de mínimos cuadrados, ya que elevamos al cuadrado el residuo. Este caso particular es un problema de mínimos cuadrados no lineales, ya que el operador Proj(.) no es lineal. Con esta formulación, también podemos introducir en el problema de optimización, por ejemplo, los parámetros intrínsecos de la cámara y encontrar también los valores óptimos para ellos:
\hat{[R|t]},\hat{\{P^\mathrm{3D}\}},\hat{K} = \mathop{\arg\min}_{[R|t],\{P^\mathrm{3D}\},K} \sum_i \Vert \mathrm{Proj}([R|t],P_i^{mathrm{3D}},K) - P_i^{mathrm{2D}} \Vert^2
Este es un ejemplo de cálculo de los residuos en Python con OpenCV a partir de pares de puntos 2D-3D correspondientes y de salida de una lista de residuos:
def calcResiduales(Rt): projPts2d,_ = cv2.projectPoints(pts3d, Rt[:3], Rt[3:], K, None) return (np.squeeze(projPts2d2) - pts2d21).ravel()
Por suerte, existen muchos algoritmos y paquetes de software para resolver problemas de mínimos cuadrados no lineales, como el Ceres Solver, varios métodos de MATLAB, SciPy de Python y muchos otros. Por ejemplo, con SciPy y OpenCV se podría resolver el problema así:
res = scipy.optimize.least_squares(calcResiduals, np.hstack([ cv2.Rodrigues(R)[0], t[np.nuevoeje] ]).ravel())
Resolver BA para plantillas 3D
Hasta ahora hemos hablado del BA en términos generales, sin embargo nuestros objetivos de optimización para las plantillas 3D son un poco diferentes. Cuando construimos nuestras plantillas 3D estamos construyendo, en esencia, un mapa 3D. El mapeado (y la localización) es un problema bien conocido en, por ejemplo, la navegación y la odometría autónomas, en las que un vehículo necesita orientarse en el mundo basándose en las observaciones de las cámaras. Nuestra técnica de mapeo con plantillas es similar a los algoritmos SLAM (localización y mapeo simultáneos), en el sentido de que construye un mapa del mundo observado de forma incremental, y ocasionalmente realiza BA sobre él para reducir el error residual de los distintos algoritmos de estimación lineal.
Como se mencionó en la primera sección, en un fotograma dado podemos ver algunos marcadores pero no otros, y a medida que avanza la cartografía tenemos más pistas sobre la posición de los marcadores entre sí. Comenzamos con los primeros marcadores visibles y anotamos su estructura tridimensional, suponiendo que esta estructura nunca cambiará. Por ejemplo, la transformación entre el marcador 1 y el marcador 2 se anota T12. En un fotograma posterior, ya no vemos el marcador 1 pero se revela el marcador 3, mientras que el marcador 2 permanece visible. Anotamos la transformación de 2 a 3 con T23, y de 1 a 3 concatenando las transformaciones: T13 = T12T23.
El proceso de mapeado introduce más errores en el mapa, agravados por el error intrínseco de recuperar la pose 3D del marcador que comentábamos antes. La concatenación de transformaciones agrava los errores, hasta el punto de que pueden darse casos degenerados. Debemos aplicar BA para paliar los errores compuestos, de lo contrario el proceso de mapeado de la plantilla fracasará.
Una opción para la optimización es fijar las transformaciones que obtenemos de la estimación de la pose de la cámara, y eso se parecería a la formulación BA de antes. Buscamos un CamP que minimice los residuos donde se dan los puntos 3D:
\hat{\mathrm{CamP}} = \mathop{\arg\min}_{\mathrm{CamP}} \sum_i \Vert \mathrm{Proj}(\mathrm{CamP},P_i^{mathrm{3D}}) - P_i^{mathrm{2D}} \Vert^2
Sin embargo, observamos que la pose de la cámara se deriva de los puntos 3D (a través de la correspondencia 2D-3D). Por tanto, podríamos optimizar las propias coordenadas de los puntos 3D y recalcular la pose de la cámara a partir de ellas. Fijamos la pose de la cámara y la minimizamos basándonos en los puntos 3D, buscando los puntos 3D óptimos que minimicen el residuo de reproyección 2D:
\hat{\{P^{\mathrm{3D}}\}} = \mathop{\arg\min}_{\{P^{\mathrm{3D}}\}} \sum_i \Vert \mathrm{Proj}(\mathrm{CamP},P_i^{\mathrm{3D}}) - P_i^{\mathrm{2D}} \Vert^2
Este truco nos ayuda principalmente a obtener un conjunto óptimo de puntos 3D que se encuentren sobre el objeto y cuyos errores con respecto a los puntos 2D originarios de las imágenes sean mínimos. Mantenemos la relación entre los puntos del mapa 3D y el ID de sus marcadores, para que en tiempo de ejecución podamos encontrar correspondencias 2D-3D y recuperar la pose del objeto, con solvePnP. En un nuevo fotograma entrante, localizamos las posiciones 2D de las esquinas de los marcadores y las hacemos coincidir con los puntos 3D del mapa, de modo que, en conjunto, podemos hallar la pose del objeto a partir de muchos puntos 2D-3D juntos, promediando el error.
Podemos ver claramente que después de realizar el BA en el mapa de plantilla 3D, los desplazamientos 2D se reducen y la estimación de la pose del objeto será mucho mejor.
Conclusiones
Las plantillas en Tulip Vision ofrecen una amplia gama de casos de uso para la detección de operaciones en el taller. Con las nuevas capacidades de las plantillas 3D, se pueden habilitar nuevos casos de uso, como el seguimiento de herramientas complejas que serán visibles desde distintos ángulos, como las herramientas manuales. Utilizando el mapeado Jig y el ajuste de paquetes podemos producir mapas de objetos complejos con un error mínimo y una geometría optimizada. Los Jigs están disponibles para su uso en Tulip de inmediato, con la optimización incorporada. Utilícelas para rastrear sus herramientas, el equipo de su estación de trabajo e incluso los materiales.