lunes, 19 de mayo de 2014

Mejoramos nuestra aplicación RA: estabilizamos el tracking

Os presento ahora una importante mejora que podemos introducir en nuestros programas de Realidad Aumentada: se trata de mejorar el tracking de marcadores para conseguir una imagen capturada más estabilizada. Para ello, utilizaremos la función histórica de ARToolKit.



ARToolKit incorpora una función de tratamiento del histórico de percepciones para estabilizar el tracking. Este histórico se implementa en una función alternativa a arGetTransMat que, en realidad, utiliza únicamente la percepción anterior, llamada arGetTransMatCont. Mediante el uso de esta función se elimina gran parte del efecto de registro tembloroso.

programa historico
Salida del programa con histórico

La función histórica requiere como parámetro la percepción anterior y no podrá utilizarse hasta que no dispongamos (al menos) de una percepción. Por esta razón, es necesario llamar primero a la función sin histórico arGetTransMat y, con la primera percepción, llamar a la función de histórico arGetTransMatCont. Esto lo conseguimos modificando ligeramente el código de la función mainLoop, una vez detectado el marcador, de la siguiente forma:

   if(k != -1) {   // Si ha detectado el patron en algun sitio... obtenemos
                   // transformacion relativa entre marca y la camara real
      if (useCont && contAct) {
            arGetTransMatCont(&marker_info[k], patt_trans, p_center,
            p_width, patt_trans);
            printf ("Usando historico!!!\n");
         }
      else {
            useCont = 1;      // En la siguiente iteracion lo podemos usar!
            arGetTransMat(&marker_info[k], p_center, p_width, patt_trans);
            printf ("Sin historico...\n");
         }
      draw();       // Dibujamos los objetos de la escena
  } else {
      useCont = 0;
      printf ("Reset Historico (fallo de deteccion)\n");
  } 

Utilizamos la variable  useCont (definida anteriormente) para indicarnos si ya podemos utilizar la función con histórico o no, en función de que tengamos capturado o no el primer frame. Esta variable de comportamiento booleano se tendrá que poner a false (valor 0) cuando no se detecte la marca. De este modo, si el usuario activó el uso del histórico (y ya tenemos al menos una percepción previa de la marca, utilizamos la función con histórico. En otro caso, llamaremos a la función de detección sin histórico y activaremos el uso del histórico para la siguiente llamada.

En nuestro programa, imtroducimos también una función de callback de teclado que permite activar el uso del histórico mediante la tecla h:

  // ======== keyboard ================================================
  static void keyboard(unsigned char key, int x, int y) {
  switch (key) {
      case 'H': case 'h':
                if (contAct) {contAct = 0; printf("Historico Desactivado\n");}
                else {contAct = 1; printf("Historico Activado\n");} 
                break;
      case 0x1B: case 'Q': case 'q':
                cleanup(); exit(1); 
                break;
      }
   }


Salida del programa

ARToolKit
Salida del programa 
Esta es la salida del programa y los siguientes enlaces permiten descargar el código y el marcador utilizado (imagen para impresión y datos para el programa) y los ficheros de inicialización de la cámara. Todos los ficheros deberán estar en ...artoolkit\bin\Data.

código del programa histórico
marcador meditel
ficheros cámara

miércoles, 14 de mayo de 2014

Modificamos nuestra aplicación RA para detectar varios patrones

Con unos sencillos cambios podemos modificar nuestra primera aplicación de Realidad Aumentada para que reconozca en la escena varios marcadores, asociando a cada uno de ellos una imagen tridimensional diferente. Os mostraré cómo reconocer cinco marcadores para dibujar cinco objetos diferentes. Podéis descargaros el código y los ficheros necesarios mediante los links correspondientes en este post.



A la hora de realizar una aplicación de Realidad Aumentada que sea capaz de reconocer diferentes patrones asignando imágenes u objetos 3D distintos a cada uno de ellos, tendremos que tener en cuenta las siguientes cambios: (en la imagen se ve la salida del programa ejemplo para dos marcadores distintos)


  1. Cargaremos un fichero de datos que contenga información sobre varios patrones en lugar del fichero de patrón ya utilizado pattMeditel.
  2. Utilizaremos, también, una estructura de datos asociada a los patrones diferente; que permita almacenar varios patrones.
  3. Modificaremos la función de dibujo draw para poder dibujar diferentes objetos en función del patron detectado.

Carga del fichero de datos de los patrones

El fichero de datos que contiene información de todos los marcadores es mi_object_data (es un fichero de texto con una estructura muy sencilla que contiene, para cada marcador, su nombre, el nombre de su fichero de datos y la anchura y centro del marcador; puedes descargarlo aquí). Para cargarlo en el programa, primero definiremos las variables:

   // ==== Fichero Object Data =========================================
   char            *model_name = "Data/mi_object_data";
   ObjectData_T    *object;

   int             objectnum;

A continuación, en la función init, cargamos el fichero mediante una función de ARToolKit definida en object.c llamada read_ObjData a la que le pasamos el fichero de datos y el número de marcadores definidos en él. Los datos referentes a las marcas quedan almacenados en una estructura de datos ObjectData_T específica de ARToolKit. Aquí os dejo el código que hace esto:

  // Cargamos el fichero mi_object_data 
  if ((object = read_ObjData(model_name, &objectnum)) == NULL) exit(0);
  printf("Objectfile num = %d\n", objectnum);


Detección de patrones


ARToolKit puede ahora tratar de identificar varios patrones en la rutina arDetectMarker. Puesto que ahora la detección es para múltiples marcadores, una vez detectado una marca con mayor fiabilidad, habrá que comprobar si se trata de un patrón conocido (presente en el fichero cargado). En este ejemplo, dibujamos también sus bordes cuando esto ocurre. Por otra parte, también es necesario mantener la transformación específica para cada uno de los marcadores detectados. La parte de código que hace esto será:

   // busca patrones conocidos en los marcadores detectados y dibuja un cuadrado
   // de otro color sobre los patrones reconocidos
   for (i = 0; i < objectnum; i++) {
      k = -1;
      for (j = 0; j < marker_num; j++) {
      if (object[i].id == marker_info[j].id) {   //si reconoce un patron
          glColor3f(0.0, 1.0, 0.0);
          argDrawSquare(marker_info[j].vertex, 0, 0);
              if (k == -1) k = j;
          else       // Vemos donde detecta el patron con mayor fiabilidad
           if (marker_info[k].cf < marker_info[j].cf) k = j;
         }
      }
       if (k == -1) {
          object[i].visible = 0;
          continue;
          }

       // calculamos la transformada para cada patron reconocido.
       object[i].visible = 1;
       arGetTransMat(&marker_info[k], object[i].marker_center, 
       object[i].marker_width,
       object[i].trans);
    }

Hay una bandera del visiblitdad asociada con cada marcador, y se realizará una nueva transformación para cada marcador detectado.


Dibujado del objeto asociado al marcador

Para dibujar el objeto asociado al marcador detectado, se llama a la función draw con la estructura ObjectData_T  y el número de objetos como parámetros. Dentro de esta función, se convertimos la matriz de transformación a formato openGL para cada marcador detectado:

    for (i = 0; i < objectnum; i++) {
      if (object[i].visible == 0) continue;
      argConvGlpara(object[i].trans, gl_para);  
          draw_object(object[i].id, gl_para);
    }

Finalmente, dibujamos el objeto asociado al marcador detectado a través de la subrutina draw_object que, mediante la anidación de funciones if selecciona el objeto para cada marcador (pondré solo un ejemplo):

    if (obj_id == 0){
       glMaterialfv(GL_FRONT, GL_SPECULAR, mat_flash_collide);
       glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_collide);
       glTranslatef(0.0, 0.0, 30.0);
       glutSolidSphere(30, 12, 6);    // dibujamos una esfera
    }


Salida del programa

La salida de este programa detectando dos marcadores diferentes, con sus correspondientes objetos asociados (en este caso una esferarosa y un conjunto esfera-toroide azul) puede verse en la imagen siguiente:

salida varios marcadores
Salida del programa de detección de varios marcadores


lunes, 12 de mayo de 2014

Perspectiva: proyectando objetos en Realidad Aumentada

La Perspectiva es un modelo de proyección que consigue efecto distancia al quedar representados los objetos más alejados con un menor tamaño. En esta entrada, trataré de explicar cómo se realiza la transformación de proyección en perspectiva en el ámbito de la Realidad Aumentada para obtener escenas más realistas, simulando la manera en que el ojo humano y las cámaras físicas forman imágenes. 



El modelo de proyección en perspectiva consiste en la proyección de los vértices de los objetos de la escena mediante trayectorias convergentes en un punto central del plano de proyección. Se consigue así un efecto distancia ya que los objetos situados más distantes del plano de visualización aparezcan más pequeños en la imagen. Las escenas generadas utilizando este modelo de proyección son más realistas, ya que ésta es la manera en que el ojo humano y las cámaras físicas forman imágenes.

perspectiva simple
matriz perspectiva
Perspectiva simple y matriz de proyección

Siguiendo la idea de la cámara estenopeica, desplazamos el plano de visualización una distancia d por detrás del origen (en dirección -Z). De esta forma, según la proyección simple, cada punto p se proyecta en el plano de visualización situado en z = -d obteniendo el punto p'=(px',py',-d). Aplicando semejanza de triángulos y un parámetro de homogeneización h = -pz/d, obtenemos la matriz de proyección Mp, que nos aplasta los vértices de los objetos 3D sobre el plano de visualización 2D.

Esta proceso no puede deshacerse; no tiene inverso. Por este motivo es interesante proyectar en lugar de sobre un plano, sobre una pirámide truncada (lo mas estrecha posible) que, una vez normalizada, se convertirá en un cubo unitario  (en lugar de un cuadrado de lado 1), que si podrá deshacerse. Así, definimos la pirámide de visualización (frustrum) cuyas bases son paralelas al plano de proyección (que queda definido por los lados top, bottom, left, right y las distancias near y far). La matriz de perspectiva que transforma la pirámide en el cubo unitario será:

frustrum
Frustrum
matriz perspectiva frustrum
Matriz de perspectiva para el frustrum

Para evitar errores de precisión en el Z-Buffer con distancias grandes, conviene que los planos de la pirámide truncada estén cerca (la distancia far-near sea muy pequeño).

Obtenemos así el volumen de visualización normalizado a un cubo unitario y las coordenadas normalizadas. Hemos pasado los objetos de 3D a 2D y las coordenadas Z de los puntos son almacenadas en un Buffer de profundidad (Z-Buffer).

AR Conceptos III

Esta es la tercera entrada de la serie de AR Conceptos, como en las anteriores, realizaré un listado y una breve explicación de los más importantes sin dejar, por ello, de dedicar diferentes entradas a otros tantos conceptos que requieran una explicación mayor.


Visualización 3D o gráficos 3D por ordenador

Es es conjunto de tecnologías y técnicas gráficas desarrolladas mediante software específico para crear objetos o diseños gráficos tridimensionales. Un gráfico 3D difiere de uno bidimensional principalmente por la forma en que ha sido generado. Este tipo de gráficos se originan mediante un proceso de cálculos matemáticos sobre entidades geométricas tridimensionales producidas en un ordenador, y cuyo propósito es conseguir una proyección visual en dos dimensiones para ser mostrada en una pantalla o impresa en papel.

Alrededor de  la visualización 3D existen diferentes técnicas y conceptos.

Modelado
La etapa de modelado consiste en ir dando forma a objetos individuales que luego serán usados en la escena creada. Existen diversos tipos de modelado, con NURBS y modelado poligonal o subdivisión de superficies. Además, aunque menos usado, existe otro tipo llamado Image Based Modeling (IBM) que consiste en convertir una fotografía a 3D mediante el uso de diversas técnicas, de las cuales, la más conocida es la fotogrametría cuyo principal impulsor es Paul Debevec. El Modelado de Superficies no tiene curvas calculadas en cada linea (ejemplo SketchUp).

modelado por superficies
Modelado por superficies NURBS

Iluminación
Creación de luces de diversos tipos puntuales, direccionales en área o volumen, con distinto color o propiedades. Esto es la clave de una animación. Gran parte de la iluminación en 3D requiere del entendimiento físico de la luz en la realidad, este entendimiento puede ir desde lo más básico en el tema como por ejemplo el concepto de iluminación global hasta comportamientos complejos y extraños de la luz como la dispersión en superficies y subsuperficies

Animación
La animación es muy importante dentro de los gráficos porque en estas animaciones se intenta imitar a la realidad misma; por esto es un trabajo que usualmente requiere muchas horas. Los objetos se pueden animar en cuanto a:
  • Transformaciones básicas en los tres ejes (XYZ), rotación, escala y traslación.
  • Forma: mediante esqueletos, mediante deformadores o dinámicas.

Renderizado
renderizado
Renderizado
Mediante el renderizado se consiguen imágenes realistas. Proceso de generar una imagen o vídeo mediante el calculo de iluminación global (GI) partiendo de un modelo en 3D. Este término técnico es utilizado por los animadores o productores audiovisuales (CG) y en programas de diseño en 3D como por ejemplo 3DMax, Maya, Blender, etc.

El proceso de renderizado se desarrolla con el fin de generar en un espacio 3D formado por estructuras poligonales; una simulación realista del comportamiento tanto de luces, texturas y materiales (agua, madera, metal, plástico, tela, etcétera) como también de los comportamientos físicos y animación, es el caso de la simulación de colisiones y fluidos, simulando ambientes y estructuras físicas verosímiles. Una de las partes más importantes de los programas dedicados a la renderización es el motor de renderizado, el cual es capaz de realizar complejos cálculos como radiosidad, raytrace (trazador de rayos), canal alfa, reflexión, refracción o iluminación global (GI).

El proceso de rénder necesita una gran capacidad de cálculo, pues requiere simular gran cantidad de procesos físicos complejos. La capacidad de cálculo se ha incrementado rápidamente a través de los años, permitiendo un grado superior de realismo en los rénders. Los estudios de cine que producen animaciones generadas por ordenador hacen uso, en general, de lo que se conoce como render farm (granja de rénder) para acelerar la producción de fotogramas.


Radiosidad
La radiosidad es un conjunto de técnicas para el cálculo de la iluminación global que tratan de resolver el problema básico de la renderización de la forma más realista posible en el campo de los gráficos 3D por computadora. Dicho problema es el transporte de la luz que sólo se puede modelar de forma óptima considerando que cada fuente luminosa emite un número enorme de fotones, que rebotan al chocar contra una superficie describiendo una cantidad de trayectorias imposibles de simular en un computador. Una de las técnicas empleadas en el cálculo de la radiosidad es el método de Montecarlo para resolver este problema mediante números aleatorios y de forma estadística.

Raytrace
Determina las superficies visibles en la escena que se quiere sintetizar trazando rayos desde el observador (cámara) hasta la escena a través del plano de la imagen. Se calculan las intersecciones del rayo con los diferentes objetos de la escena y aquella intersección que esté más cerca del observador determina cuál es el objeto visible.

El algoritmo de trazado de rayos extiende la idea de trazar los rayos para determinar las superficies visibles con un proceso de sombreado (cálculo de la intensidad del píxel) que tiene en cuenta efectos globales de iluminación como pueden ser reflexiones, refracciones o sombras arrojadas. Para simular los efectos de reflexión y refracción se trazan rayos recursivamente desde el punto de intersección que se está sombreando dependiendo de las características del material del objeto intersecado. Para simular las sombras arrojadas se lanzan rayos desde el punto de intersección hasta las fuentes de luz. Estos rayos se conocen con el nombre de rayos de sombra (shadow rays).

El algoritmo básico de trazado de rayos fue mejorado por Robert Cook (1985) para simular otros efectos en las imágenes mediante el muestreo estocástico usando un método de Montecarlo; entre estos efectos podemos citar el desenfoque por movimiento (blur motion), la profundidad de campo o el submuestreo para eliminar efectos de aliasing en la imagen resultante.

En la actualidad, el algoritmo de trazado de rayos es la base de otros algoritmos más complejos para síntesis de imágenes (mapeado de fotones, Metropolis, entre otros) que son capaces de simular efectos de iluminación global complejos como la mezcla de colores (color blending) o las cáusticas.

Proyección
Es la transformación de coordenadas para convertir cada objeto 3D desde el sistema de coordenadas de visualización (coordenadas de la cámara) en un objeto 2D dibujado sobre en el plano de visualización (coordenadas de pantalla). Ecomputer vision existen distintos modelos de proyección, aunque los más utilizados son:

  1. Modelo de proyección paralelaConsiste en proyectar puntos del espacio contra el plano de proyección mediante haces de rectas siempre paralelas entre sí. Esta técnica de representación gráfica se utiliza en diseños de ingeniería y arquitectura ya que su principal ventaja es que mantiene las proporciones relativas de lo representado y se puede medir sobre él directamente. De esta manera se puede reconstruir el objeto fácilmente a partir de representaciones concretas.
  2. Modelo de proyección en perspectivaConsiste en la proyección de los vértices de los objetos de la escena mediante trayectorias convergentes en un punto central del plano de proyección. Se consigue efecto distancia al quedar representados los objetos más alejados con un menor tamaño. Las escenas generadas utilizando este modelo de proyección son más realistas, ya que ésta es la manera en que el ojo humano y las cámaras físicas forman imágenes.
    proyección en perspectiva
    Modelo de proyección en perspectiva


Visualización 3D para Realidad Aumentada

En esta entrada, trataré de explicar conceptos básicos sobre visualización 3D que son necesarios para trabajar en el ámbito de la Realidad Aumentada. Hablaré de sistemas de coordenadas, parámetros de visualización y transformaciones


Inicialmente, necesitamos definir un sistema de coordenadas de visualización para los parámetros de visualización de la cámara (en negro en el detalle, es la matriz P; que si además tenemos en cuenta la distorsión también se incluye el rojo) que nos permita definir el plano de proyección o visualización (en verde en el detalle; es lo que ve la cámara), donde podamos proyectar cada objeto 3D traído desde sus coordenadas locales (en el detalle en azul) hasta las coordenadas de visualización.

relacion entre sist coord
Relación entre los diferentes sistemas de coordenadas definidos

Se suceden diferentes transformaciones de coordenadas para llevar cada objeto 3D desde la escena (coordenadas locales) hasta el sistema de coordenadas de visualización (coordenadas de la cámara) y de ahí proyectarlos en el plano de visualización (coordenadas de pantalla). Esta última transformación dependerá del modelo de proyección elegida que en computer vision pueden ser diversos; aunque los más utilizados son el de proyección paralela y el de proyección en perspectiva. Dedicaré otra entrada a explicarlos.

transformaciones RA
Transformaciones RA
El proceso de visualización 3D, en concreto, comienza por posicionar y orientar los objetos de la escena respecto del SRU, obteniendo las coordenadas universales del objeto (transformación de modelado). A continuación se hace lo mismo con la cámara, obteniendo las coordenadas de visualización (transformación de visualización) y el volumen de visualización.
En este momento cámara y objetos están posicionados a partir del origen del RSU y mirando en dirección negativa del eje Z. ARToolKit hace estos dos procesos en uno solo, al cargar la matriz T en el modelview de OpenGL.

A continuación, mediante la transformación de proyección (en perspectiva en nuestro caso) se transforma el volumen de visualización en un cubo unitario obteniendo las coordenadas normalizadas. Hemos pasado los objetos de 3D a 2D y las coordenadas Z de los puntos son almacenadas en un Buffer de profundidad (Z-Buffer). Los objetos que quedan fuera del volumen son eliminados o recortados para obtener las coordenadas recortadas (transformación de recorte).

Finalmente, se ajustan las coordenadas x e y del cubo a las coordenadas de la pantalla, pudiendo adaptarse a diferentes tamaños y resoluciones (transformación de pantalla). En una etapa final de rasterización, se toman las coordenadas de la pantalla y el Z-Buffer y se definen los colores de los pixels.

transformación de visualización
Transformación de visualización. Las zonas con sombra corresponden al volumen de visualización



viernes, 9 de mayo de 2014

Un poco de geometría: transformaciones geométricas en Realidad Aumentada

Algunos conceptos de geometría teórica básica son necesarios para trabajar con los objetos 3D de la escena en el ámbito de la Realidad Aumentada. En concreto, las transformaciones geométricas de posición, rotación y tamaño; así como su representación matricial. 


En general podemos decir que una transformación toma como entrada elementos como vértices y vectores y los convierte de alguna manera. En este post, veremos las transformaciones más básicas necesarias para el desarrollo de aplicaciones de Realidad Aumentada. En primer lugar, veremos las operaciones en 2D y su notación matricial 2D, después haremos una generalización tridimensional empleando coordenadas homogéneas.

Traslación
traslaciónSe realiza la traslación de un punto p = (px,py) mediante la suma de un vector de desplazamiento t = (tx,ty)a las coordenadas iniciales del punto, para obtener una nueva posición de coordenadas. Si aplicamos esta traslación a todos los puntos del objeto, estaríamos desplazando ese objeto de una posición a otra. 

p′x = px + tx
p′y = py + ty 





Rotación
rotacionPodemos expresar la rotación de un punto p = (px,py) a una nueva posición rotando un ángulo θ respecto del origen de coordenadas, especificando el eje de rotación y un ángulo θ. Las coordenadas iniciales del punto se pueden expresar como:

px = dcosα
py = dsenα

Siendo d la distancia entre el punto y el origen del sistema de coordenadas. Así, usando identidades trigonométricas se pueden expresar las coordenadas transformadas como la suma de los ángulos del punto original α y el que queremos rotar θ como:

p′x = dcos(α+θ) = dcosαcosθ − dsenαsenθ
p′y = dsen(α+θ) = dcosαsenθ + dsenαcosθ

Que sustituyendo en la ecuación anterior, obtenemos:

p′x = px cosθ − py senθ
p′y = px sinθ + py cosθ


Cambio de escala
cambio escalaUn cambio de escala de un objeto bidimensional puede llevarse a cabo multiplicando las componentes x,y del objeto por el factor de escala Sx,Sy en cada eje. Se puede expresar como:

p′x = pxSx
p′y = pySy



Representación matricial
Las operaciones anteriores se expresan en 2D de forma matricial como:

matriz traslacion
Matriz de traslación 2D
matrices rotacion y escalado
Matrices de rotación 2D y escalado 2D







Mientras que en 3D añadimos el elemento homogéneo;  las formas matriciales serán:

matrices de rotacion 3D
matrices traslacion 3D escalado 3D
Matrices de rotación, traslación y escalado 3D

Composición de matrices
Una de las principales ventajas derivadas del trabajo con sistemas homogéneos es la composición de matrices. Matemáticamente esta composición se realiza multiplicando las matrices en un orden determinado, (empezando por la última transformación y acabando por la primera), de forma que es posible obtener la denominada matriz de transformación neta MN resultante de realizar sucesivas transformaciones a los puntos. De este modo, bastará con multiplicar la MN a cada punto del modelo para obtener directamente su posición final. Por ejemplo, si P es el punto original y P′ es el punto transformado, y T1 ··· Tn son transformaciones (rotaciones, escalados, traslaciones) que se aplican al punto P, podemos expresar la transformación neta como:

P′ = Tn × ··· × T2 × T1 × P
P′ = MN ×P

Dejadme para finalizar os recomiende una buena web donde todo esto está muy bien explicado y con más detalle: Wolfram.MathWorld

jueves, 8 de mayo de 2014

Creando mis propios marcadores

ARToolKit pone a nuestra disposición una serie de marcadores reconocibles y utilizables por sus funciones. Pero, ¿por qué no utilizar nuestros propios marcadores? ¿por qué no personalizar o utilizar nuestro logo o nuestras imágenes favoritas como patrón de un marcador?


Existen muchos métodos para realizar un marcador. De hecho, lo más sencillo es partir de una plantilla de marcador en blanco y con cualquier programa de imagen, insertar nuestro patrón en su interior. Pero con cuidado, tenemos que respetar algunas restricciones si queremos que nuestra marca pueda ser detectada sin problemas. Al final del post os dejo algunos consejos.


generacion marcadores
Generación de marcadores con mk_patt
Una vez creado el marcador, tiene que ser reconocido por ARToolKit y entrenado para generar su fichero de datos correspondiente. Para ello, ARToolKit proporciona una sencilla aplicación, mk_patt, que nos pedirá el fichero de parámetros de la cámara y abrirá una ventana de vídeo. Entonces mostraremos a la cámara nuestro marcador impreso y aparecerán en su contorno una esquina roja y otra verde. Tenemos que orientar la cámara hasta situar el contorno rojo en la esquina superior izquierda de nuestro marcador y, en ese momento, pulsar el botón izquierdo del ratón para generar el fichero del marcador. Este fichero se introduce como dato en nuestros programas ARToolKit.

Este proceso de generación de marcadores están perfectamente definidos en el apartado de documentación de la web de ARToolKit. Os dejo también un acceso a la plantilla de marcador en blanco.

Consejos sobre marcadores

  • El patrón debe ser sencillo. El rango de detección se ve afectado por la complejidad de la marca. Los patrones simples (con grandes áreas de color blanco o negro) son detectados mejor.
  • No debe tener simetrías.
  • Cuanto mayor es la resolución del patrón reescalado, mayor es la precisión de ARToolKit, pero requiere más capacidad de cómputo para realizar las operaciones.
  • El tamaño físico de la marca afecta directamente a la facilidad de detección; a mayor tamaño de marca, mayor distancia puede ser cubierta. Por ejemplo, marcas de 7 cm de lado pueden ser detectadas hasta una distancia máxima de 40 cm (a una resolución de 640x480 píxels). Si aumentamos el tamaño de la marca a 18cm, ésta será detectada hasta una distancia de 125cm.
  • Finalmente las condiciones de iluminación afectan enormemente a la detección de las marcas. El uso de materiales que no ofrezcan brillo especular, y que disminuyan el reflejo en las marcas mejoran notablemente la detección de las marcas.



Calibrando mi cámara

La calibración de una cámara representa un paso importante para procesos de reconstrucción 3D o sensorización mediante una cámara. Con la calibración de la cámara se consiguen estimar los parámetros intrínsecos y extrínsecos de la misma los cuales son necesarios para realizar la reconstrucción 3D del entorno y situar la cámara en el mismo. Por este motivo, una de las primeras cosas que tenemos que hacer es calibrar nuestra cámara.


Existen muchos métodos de calibración de una cámara. Normalmente, el proceso se puede realizar en dos pasos. Primero se estima la matriz de proyección y después se estiman los parámetros intrínsecosextrínsecos de la cámara a partir de ella. Durante el proceso,  la gran mayoría de métodos utilizan plantillas de líneas o puntos.

Calibración en ARToolKit

ARToolKit propone dos programas de calibración con los que poder establecer los parámetros intrínsecos de la cámara (punto central de la imagen, distorsión de la lente, longitud focal, relación de aspecto y de inclinación), que tendrán que almacenarse en el fichero camara_para.dat y que se entran como datos a nuestros programas ARTollKit. Estos ficheros son:

  • Calib_dist utiliza la plantilla calib_dist.pdf para medir el punto central de la cámara y el factor de distorsión de la lente. En diferentes posiciones y orientaciones de la plantilla, el programa hará una captura de pantalla y en ella marcaremos con el ratón todos los puntos siguiendo un orden (izda -> dcha y arriba->abajo). A continuación la aplicación nos muestra el resultado del cálculo de estos parámetros, para que realicemos una comprobación visual. Cada vez que pulsemos con el botón izquierdo del ratón se mostrará una imagen con líneas de color rojo que deben pasar por el centro de cada círculo de calibración. Finalmente, se generará el fichero con los datos.


  • salida de calib_dist
    Salida de Calib_dist

  • Calib_param utiliza la plantilla calib_cpara para medir la longitud focal de la cámara y otros parámetros. Realiza un proceso similar al anterior pero moviendo líneas horizontales y despues verticales hasta hacerlas coincidir con las de la plantilla. El orden (izda -> dcha y arriba->abajo) es igualmente importante.

salida calib_param
Salida de Calib_param

plantillas calibracion ARToolKit
Plantillas de calibración calib_dist y calib_param

Los procesos de calibración están perfectamente definidos en el apartado de documentación de la web de ARToolKit. Os dejo también un acceso a las plantillas de calibración.

ARToolKit: funcionamiento básico

Para los que hayáis leído mi entrada "Mi primera aplicación de Realidad Aumentada" en la que explico a través de un sencillo programa la estructura y funcionamiento básico de una aplicación de Realidad Aumentada, en este post desarrollaré algo más el funcionamiento básico de ARToolKit.


Lo esencial en todas las aplicaciones de Realidad Aumentada, es la  necesidad de calcular correctamente el punto de vista de la cámara, para así poder realizar las operaciones necesarias sobre los objetos virtuales, para que estos se integren correctamente en el mundo real. Es decir, si queremos mostrar objetos virtuales, de modo que el usuario realmente crea que existen en el mundo real, tendremos que realizar transformaciones sobre dichos objetos de modo que el usuario los vea (a través de la cámara o dispositivo de captura utilizado) en la posición  con el tamaño, orientación e iluminación, en que esos objetos serían percibidos por el usuario en el mundo real en caso de que realmente estuvieran allí.

marcador meditel
Marcador meditel
Para ello, herramientas como ARToolKit utilizan unas plantillas (marcadores) de forma cuadrada normalmente, formadas por un cuadrado negro con un cuadrado blanco unas cuatro veces más pequeño en su centro, y un dibujo (patrón) en el interior del cuadrado blanco. Este dibujo debe ser sencillo y evitar las simetrías. Nuestro programa será capaz de detectar una de estas plantillas en las imágenes de vídeo capturadas utilizando mediante un potente algoritmo de detección de contornos implementado en las funciones y utilidades proporcionadas por ARToolKit.

Una vez detectado el marcador en una imagen, a partir de su orientación, posición y tamaño, la aplicación es capaz de calcular la posición y orientación relativa de la cámara real respecto al marcador y dibujar el objeto 3D sobre la imagen capturada de modo que aparezca sobre el marcador en la posición, orientación y tamaño correspondiente al punto de vista de la cámara (perfectamente alineado). Esto siempre que el programador de la aplicación así lo haya decidido, pues las posibilidades son muchas y pudiera ser que una vez obtenida esta información el programador decidiese utilizarla de otra forma, hacer otras operaciones distintas, etc. Para el renderizado del objeto se utilizan librerías externas a ARToolKit (por ejemplo GLUT y OpenGL).

El proceso básico de una aplicación ARToolkit es el siguiente:

proceso detección marcas
Proceso de detección
de  marcas en ARToolKit
  1. Primero se captura un fotograma del mundo real mediante la cámara.
  2. A continuación se detectan marcas. Para ello, la imagen se convierte a escala de grises y se binariza (según parámetro umbral threshold que indica a partir de que gris todo pixel será negro, dejando blancos el resto: compromiso entre rapidez y precisión en detección). después, se detectan todas las posibles marcas y se les asigna mediante un parámetro un valor de fidelidad para decidir cual de las marcas posibles es la real. 
  3. Se normaliza la marca, se detectan los contornos de la región que la contiene (vértices y lados del cuadrado) y después se extrae el contenido de la marca comparándola con patrones de las plantillas de las que se tiene información almacenada.
  4. Si la forma del patrón del marcador detectado coincide con el de la plantilla almacenada, se utiliza la información y el tamaño de marcador y plantilla almacenada para calcular la posición y orientación de la cámara respecto al marcador detectado, y se guarda en una matriz llamada Matriz de Transformación. (en el post AR Conceptos II se explica con detalle cómo es esta matriz). Conociendo las posiciones 2D de las aristas y vértices que definen el marcador 2D y el modelo de proyección de la cámara (matriz de parámetros intrínsecos P) es posible estimar la posición y rotación 3D de la cámara respecto a la marca y, por tanto la matriz de transformación T (matriz de parámetros extrínsecos). Esto se hace mediante un proceso de cálculo iteractivo.
  5. La matriz de Transformación se utilizará para establecer la posición y orientación de la cámara virtual (transformación de la vista), lo que equivale a una transformación de las coordenadas de la marca a las coordenadas de la cámara y por tanto se transforman las coordenadas del objeto a dibujar. Se usa la matriz T (para hacer transformaciones geométricas) e implícitamente la P (para hacer transformaciones de proyección y visualización de la cámara).
  6. Al haber puesto la cámara virtual en la misma posición y orientación que la cámara real, el objeto virtual se renderiza sobre el marcador y se muestra la imagen resultante, que contiene la imagen del mundo real y el objeto virtual superpuesto, alineado sobre el marcador. Se dibujan, por tanto, los objetos 3D alineados con la escena. 
  7. Se realiza el mismo proceso con los siguientes fotogramas.

coordenadas ARToolKit
Sistema de coordenadas de ARToolKit

El siguiente diagrama muestra el funcionamiento que se acaba de describir:
.
proceso AR
Esquema funcional de ARToolKit



miércoles, 7 de mayo de 2014

Mi primera aplicación de Realidad Aumentada

Mi primera aplicación de Realidad Aumentada es un sencillo programa que, una vez compilado, es capaz de reconocer en la escena un marcador generado por nosotros para renderizar sobre él una imagen tridimensional, en mi caso una tetera. Podéis descargaros el código y los ficheros necesarios al final del post.


A continuación os muestro el código de este programa que utilizaré para explicar la estructura y funcionamiento básico de un programa de Realidad Aumentada. El programa reconoce en la escena un marcador ARToolkit creado por nosotros (en otra entrada mostraré como hacerlo) y renderizar sobre él una imagen tridimensional.

  1. #ifdef _WIN32
  2. #include <windows.h>
  3. #endif
  4. #include <GL/gl.h>
  5. #include <GL/glut.h>
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8. #include <AR/gsub.h>
  9. #include <AR/video.h>
  10. #include <AR/param.h>
  11. #include <AR/ar.h>
  12.  
  13.  
  14. // ==== Configuracion de la camara ===============
  15. #ifdef _WIN32
  16. char                *vconf = "Data\\WDM_camera_flipV.xml";
  17. #else
  18. char                *vconf = "";
  19. #endif
  20.  
  21. // ==== Definicion de constantes y variables globales ===============
  22. int    patt_id;                                // Identificador de la marca
  23. double patt_trans[3][4];                       // Matriz de transformacion de la marca
  24.  
  25. // ==== Definicion de funciones =====================================
  26. void print_error (char *error) {  printf(error); exit(0); }
  27.  
  28. // ======== cleanup =================================================
  29. static void cleanup(void) {  
  30.   arVideoCapStop();                      // Para captura de video y libera recursos
  31.   arVideoClose();
  32.   argCleanup();
  33. }
  34.  
  35. // ======== draw ====================================================
  36. static void draw( void ) {
  37.   double  gl_para[16];                   // Matriz 4x4 de OpenGL
  38.   GLfloat mat_ambient[]     = {0.0, 0.0, 1.0, 1.0};
  39.   GLfloat light_position[]  = {100.0,-200.0,200.0,0.0};
  40.  
  41.   argDrawMode3D();                       // Activa modo presentacion 3D
  42.   argDraw3dCamera(0, 0);                 // Activa vista de la camara 3D
  43.   glClear(GL_DEPTH_BUFFER_BIT);          // Limpia buffer de profundidad
  44.   glEnable(GL_DEPTH_TEST);
  45.   glDepthFunc(GL_LEQUAL);
  46.  
  47.   argConvGlpara(patt_trans, gl_para);   // Convierte la matriz de la marca
  48.   glMatrixMode(GL_MODELVIEW);           // para ser usada por OpenGL
  49.   glLoadMatrixd(gl_para);               // Carga l matriz
  50.  
  51.   // Dibuja el objeto 3D
  52.   glEnable(GL_LIGHTING);  glEnable(GL_LIGHT0);
  53.   glLightfv(GL_LIGHT0, GL_POSITION, light_position);
  54.   glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
  55.     glTranslatef(0.0, 0.0, 60.0);
  56.     glRotatef(90.0, 1.0, 0.0, 0.0);
  57.     glutSolidTeapot(80.0);
  58.   glDisable(GL_DEPTH_TEST);
  59. }
  60.  
  61. // ======== init ====================================================
  62. static void init( void ) {
  63.   ARParam  wparam, cparam;        // Parametros intrinsecos de la camara
  64.   int xsize, ysize;               // Tamaño del video de camara (pixels)
  65.  
  66.   // Abre dispositivo de video
  67.   if (arVideoOpen(vconf) < 0) exit(0); 
  68.   if(arVideoInqSize(&xsize, &ysize) < 0) exit(0);
  69.  
  70.   // Carga los parametros intrinsecos de la camara
  71.   if(arParamLoad("data/camera_para.dat", 1, &wparam) < 0)  
  72.     print_error ("Error en carga de parametros de camara\n");
  73.  
  74.   arParamChangeSize(&wparam, xsize, ysize, &cparam);
  75.   arInitCparam(&cparam);                       // Inicializa la camara con "cparam"
  76.  
  77.   // Carga la marca
  78.   if((patt_id=arLoadPatt("data/pattMeditel")) < 0)
  79.     print_error ("Error en carga de patron\n");
  80.  
  81.   argInit(&cparam, 1.0, 0, 0, 0, 0);   // Abre la ventana grafica
  82. }
  83.  
  84. // ======== mainLoop ================================================
  85. static void mainLoop(void) {
  86.   ARUint8 *dataPtr;                     // contenedor de frame activo
  87.   ARMarkerInfo *marker_info;            // informacion del marcador
  88.   int marker_num, j, k;
  89.  
  90.   double p_width     = 120.0;           // Ancho del patron (marca)
  91.   double p_center[2] = {0.0, 0.0};      // Centro del patron (marca)
  92.  
  93.   // Captura un frame de la camara de video
  94.   if((dataPtr = (ARUint8 *)arVideoGetImage()) == NULL) {
  95.     // Si devuelve NULL es porque no hay un nuevo frame listo
  96.     arUtilSleep(2);  return;  // Duerme el hilo 2ms y salimos
  97.   }
  98.  
  99.   argDrawMode2D();
  100.   argDispImage(dataPtr, 0,0);    // Dibuja lo que ve la camara
  101.  
  102.   // Detecta la marca en el frame capturado (return -1 si error y sale de programa)
  103.   if(arDetectMarker(dataPtr, 100, &marker_info, &marker_num) < 0) {
  104.     cleanup(); exit(0);  
  105.   }
  106.  
  107.   arVideoCapNext();      // Captura siguiente frame
  108.  
  109.   // Detecta el patron con mayor fiabilidad
  110.   for(j = 0, k = -1; j < marker_num; j++) {
  111.     if(patt_id == marker_info[j].id) {
  112.       if (k == -1) k = j;
  113.       else if(marker_info[k].cf < marker_info[j].cf) k = j;
  114.     }
  115.   }
  116.  
  117. if(k != -1) {                   // Si detecta el patron obtiene transformación
  118. // relativa entre la marca y la cámara real
  119.     arGetTransMat(&marker_info[k], p_center, p_width, patt_trans);
  120.     draw();                       // Dibuja los objetos de la escena
  121.   }
  122.  
  123.   argSwapBuffers();                     // Cambia el buffer con lo que tenga dibujado
  124. }
  125.  
  126. // ======== Main ====================================================
  127. int main(int argc, char **argv) {
  128.   glutInit(&argc, argv);                 // Crea la ventana OpenGL con Glut
  129.   init();                               
  130.  
  131.   arVideoCapStart();                    // Crea un hilo para captura de video y
  132.   argMainLoop( NULL, NULL, mainLoop );  // asocia callbacks...
  133.   return (0);
  134. }

Cualquier aplicación que use ARToolKit tendrá la estructura de programa similar a la siguiente: tres grandes bloques inicialización, bucle principal y finalización (en el código están coloreados en azul, verde y rojo respectivamente):

proceso AR


Inicialización (función init)
Primero abre el dispositivo de vídeo con arVideoOpen (línea 67) y se obtiene el tamaño de la fuente de vídeo mediante arVideoInqSize (línea 68) (se guarda en dos variables entero el ancho y alto del vídeo). A continuación, arParamLoad (línea 71) carga en una estructura ARParam (tercer parámetro) los parámetros intrínsecos de la cámara obtenidos en la etapa de calibración (fichero genérico camera_para.dat válido para la mayoría de webcams; aunque lo ideal es trabajar con un fichero que contenga los parámetros de nuestra cámara para obtener   resultados más precisos).
patron_meditel
Marcador meditel
Veremos cómo crear este fichero específico para cada cámara en una entrada posterior en ARexperience. Después, arParamChangeSize (línea 74) modifica los parámetros intrínsecos para la resolución en píxels con la que trabajará la cámara y se cargan en las estructuras de datos internas de ARToolKit, mediante la llamada a arInitCparam (línea 75).

Se carga el patrón asociado a la marca (línea 78). Finalmente, en la línea 81 se abre la ventana de OpenGL mediante argInit (de la librería auxiliar Gsub de ARToolKit), pasándole como primer parámetro la configuración de la cámara. El segundo parámetro indica el factor de zoom (en este caso, sin zoom).

Bucle principal
El primer lugar, se recupera un frame de la cámara de vídeo mediante la función arVideoGetImage (la llamada devuelve un puntero a un buffer donde se encuentra la imagen capturada). Si se llama a la función con mayor frecuencia de la soportada por la cámara, se duerme el hilo 2ms (línea 96) y se vuelve a ejecutar el mainLoop.

A continuación, se dibuja en la ventana (en modo 2D) el buffer que acabamos de recuperar de la cámara (línea 100) para que arDetectMarker (línea 103) localice las marcas en el buffer de entrada. El segundo parámetro de valor 100 se corresponde con el valor umbral de binarización de la imagen (a blanco y negro, explicaremos el proceso de binarización de la marca en una entrada posterior). Esta función nos devuelve en el tercer parámetro un puntero a una lista de estructuras de tipo ARMarkerInfo, que contienen información sobre las marcas detectadas (junto con un grado de fiabilidad de la detección), y como cuarto parámetro el número de marcas detectadas.

De esta forma, ARToolKit nos devuelve “posibles” posiciones para cada una de las marcas detectas. Incluso cuando estamos trabajando con una única marca, es común que sea detectada en diferentes posiciones (por ejemplo, si hay algo parecido a un cuadrado negro en la escena). ¿Cómo elegimos la correcta en el caso de que tengamos varias detecciones? ARToolKit asocia a cada percepción una probabilidad de que lo percibido sea una marca, en el campo cf (confidence value). Como se puede comprobar, todos los campos de la estructura ARMarkerInfo se refieren a coordenadas 2D, por lo que aún no se ha calculado la posición relativa de la marca con la cámara.

ARMarkerInfo
Campos de la estructura ARMarkerInfo

Así, en las líneas 109-115 se guarda en la variable k el índice de la lista de marcas detectadas aquella percepción que tenga mayor probabilidad de ser la marca (cuyo valor de fiabilidad sea mayor). Mediante la llamada a arGetTransMat (línea 119) se obtiene la matriz de transformación relativa entre la marca y la cámara (matriz 3x4 de doubles); es decir, se obtiene la traslación y rotación de la cámara con respecto de la marca detectada. Para ello es necesario especificar el centro y ancho de la marca. Esta matriz será finalmente convertida al formato de matriz homogénea de 16 componentes utilizada por OpenGL mediante la llamada a argConvGlpara en la línea 47. Finalmente, se renderizará una imagen 3D mediante primitivas OpenGL para uso en aplicaciones de Realidad Aumentada. (líneas 51-58).

Finalización y función Main
En la función cleanup se liberan los recursos al salir de la aplicación. Se hace uso de funciones de ARToolKit para detener la cámara de vídeo, y limpiar las estructuras de datos internas de ARToolKit. En la función main se registran los callbacks mediante la función argMainLoop. En este ejemplo, se pasa como primer y segundo parámetro NULL (correspondientes a los manejadores de ratón y teclado respectivamente). Por su parte, se asocia la función que se estará llamando constantemente en el bucle principal mainLoop.

Salida del programa


ARToolKit
Salida del programa 
Esta es la salida del programa y los siguientes enlaces permiten descargar el código y el marcador utilizado (imagen para impresión y datos para el programa) y los ficheros de inicialización de la cámara. Todos los ficheros deberán estar en ...artoolkit\bin\Data.

código del programa
marcador meditel
ficheros cámara