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

No hay comentarios:

Publicar un comentario