Práctica

Práctica

La práctica se divide en tres fases. En cada una de ellas hay una serie de tareas que el alumno debe realizar.

Las especificaciones de cada una de las fases de la práctica se detallan a continuación:

Práctica: Preparación

  • La base de la práctica es el programa de ejemplo NiUserTracker, que se encuentra localizado en ../OpenNI/Samples/NiUserTracker.
  • El programa se ubica en el fichero main.cpp, pero hay, a su vez, otros dos ficheros importantes que son SceneDrawer.h y SceneDrawer.cpp. Estos ficheros contienen la clase SceneDrawer que se encarga de pintar las imágenes y la información en pantalla, pero que también se encarga de realizar gran parte del procesado del programa.
  • El fichero Makefile genera un ejecutable para este código en el directorio ../OpenNI/Samples/Bin. El nombre del ejecutable es Sample_NiUserTracker.
  • El funcionamiento del programa es como sigue:
  • Detección de usuarios en el mapa de profundidad usando un nodo UserGenerator con capacidad esqueletal (SkeletonCapability).
  • Extracción de la pose de los usuarios.
  • Representación en pantalla del mapa de profundidad, con los usuarios coloreados en diferentes colores, mensajes de estado para cada usuario y una figura de alambre (líneas de color) que muestra el esqueleto detectado para cada persona.
  • El primer paso para la realización de la práctica es entender cómo funciona este programa. Para la realización de esta tarea el alumno cuenta con la documentación aportada en esta Unidad de Aprendizaje, junto a la API completa de OpenNI.
  • Una vez entendido y probado el programa, el alumno debe preparar un nuevo programa en el directorio Samples, que será el que se utilice a partir de este punto para realizar todas las modificaciones y funcionalidades añadidas. Para hacer ésto:
  • Se creará una copia de la carpeta ../OpenNI/Samples/NiUserTracker en ../OpenNI/Samples/NiUserTracker_OCW.
  • Se modificará el fichero Makefile de la nueva carpeta para que al compilar se cree un ejecutable con nombre Sample_NiUserTracker_OCW en la carpeta ../OpenNI/Samples/Bin. Durante toda la práctica, sólo se modificará el contenido de ../OpenNI/Samples/NiUserTracker_OCW.
Solución: Fichero Makefile modificado.

¿Que es eso de GLES?

En los códigos se ven bastantes partes donde se ejecuta un código u otro en función de si se ha definido o no la constante USE_GLES. ¿Qué es eso?

Práctica: Detector de manos

  • En esta segunda parte de la práctica el alumno debe modificar el programa (NiUserTracker_OCW) para localizar, y representar en pantalla, las posiciones donde se sitúan las manos del usuario. Para completar esta parte de la práctica, se sugieren los siguientes pasos:
  • Añadir una variable global en el programa que indique si se quiere, o no, pintar información sobre las posiciones de las manos en pantalla.
  • Ampliar el manejador de eventos de teclado de la ventana GLUT ( función void glutKeyboard (unsigned char key, int x, int y) en main.cpp) para procesar un nuevo evento de pulsación de tecla que active/desactive la representación en pantalla de las posiciones de las manos.
  • En SceneDrawer.cpp hay una función, DrawDepthMap, que gestiona la información que se debe mostrar en pantalla. Al final de esa función hay una parte que se encarga de mostrar el esqueleto de la persona, llamando a otra función implementada en SceneDrawer.cpp (DrawLimb). El alumno debe modificar la función DrawDepthMap añadiendo un caso parecido a este último (un if basado en la condición de la que se hablaba en el paso 1).
  • En el caso creado, se debe acceder a las posiciones de las manos ( usando la función g_UserGenerator.GetSkeletonCap().GetSkeletonJointPosition(...) ), y transformarlas de posiciones 3D a coordenadas de píxeles, usando la función g_DepthGenerator.ConvertRealWorldToProjective(...) de la misma forma que se usa en la función DrawLimb.
  • Finalmente, se deben escribir las coordenadas de las manos en pantalla, pero sólo si su valor de confianza es mayor de 0.5. Para ello se creará una cadena de caracteres con la información que se quiere mostrar, se colocará dicha cadena en la posición de la mano usando la función GLUT glRasterPos2i(..), y se pintará con la función GLUT glPrintString(..). En SceneDrawer.cpp hay ejemplos de uso de estas funciones, ya que se emplean para pintar la información sobre los usuarios.
Solución: Ficheros main.cpp y SceneDrawer.cpp modificados.

Práctica: Juego del recolector

  • En la parte final de la práctica, el alumno debe realizar una nueva implementación del juego Recolector, parecida a la que se hizo en la Unidad B1_UD3, pero con especificaciones más exigentes: ahora, sólo se pueden recoger cuadrados con las manos. Las nuevas especificaciones del juego son las siguientes:

Juego 'Recolector'

  • Objetivo del juego: El jugador debe recoger tantos cuadrados naranjas como sea posible.
  • Normas del juego: El jugador se situará delante de la Kinect y cogerá los cuadrados naranjas que vayan apareciendo en el mapa de profundidad. Para 'coger' estos cuadrados el jugador debe acercar una mano a ellos. El cuadrado se considera 'recolectado' si la distancia en píxeles del centro del cuadrado al centroide 2D de la mano (proyección del centroide 3D en el mapa de profundidad) es de menos de 10 píxeles.
Cuando un cuadrado es recolectado, desaparece, el jugador se suma un punto, y aparece un nuevo cuadrado naranja en otro punto (aleatorio) de la imagen.
El número de cuadrados recolectados se muestra en la esquina superior izquierda de la imagen, tal y como se muestra en la siguiente captura de pantalla:

  • Duración del juego: En principio el juego no tiene duración determinada. Son los jugadores los que deciden cuánto dura la partida, siendo vencedor quien más cuadrados recolecta. Otra opción es fijar un número de cuadrados (por ejemplo, 15) y medir el tiempo que tarda cada jugador en recolectarlos.
  • Variantes opcionales:
  • Puede fijarse un número determinado de cuadrados que deben recoletarse, y mostrar en pantalla los cuadrados que quedan por recolectar (como una cuenta hacia atrás).
  • Puede controlarse el tiempo, de modo que el juego fije una duración. Pasado el tiempo fijado, se indican los cuadrados recolectados y se espera a otro jugador.
  • Pueden aparecer cuadrados de otro color que no deben ser recolectados.
  • Puede aparecer más de un cuadrado naranja a la vez.
  • El tiempo restante puede aumentar cada vez que se recolecta un cuadrado naranja, o disminuir si se recolecta, por error, un cuadrado de otro color.
  • Se puede fijar un tiempo máximo de 'permanencia' de cada cuadrado. Pasado ese tiempo el cuadrado desaparece, haya sido recolectado o no.
  • Dos jugadores: En este caso aparecen cuadrados de dos colores distintos, y cada jugador debe recolectar los suyos (cuidado con los choques, empujones, etc.)

Para la programación de esta parte final de la práctica, se sugiere continuar las modificaciones que se emprendieron en la segunda parte, añadiendo los siguientes pasos al proceso:

  • En la función glutDisplay() de main.cpp, que se ejecuta para cada refresco de pantalla, se debe comprobar si el cuadrado ha sido recolectado o no. Ello requerirá definir variables globales para las coordenadas 2D de las manos izquierda y derecha, y para el centro del cuadrado. También harán falta variables globales para contar el número de cuadrados recolectados, y para indicar si hay cuadrado en pantalla o no.
  • Si no hay cuadrado en pantalla debe escogerse una posición aleatoria para su centro, y ativar la variable correspondiente.
  • Una vez detectado si un cuadrado ha sido recolectado, debe desaparecer de pantalla (lo que implica desactivar la variable correspondiente), y se aumenta la cuenta de cuadrados recolectados. 
  • En la función DrawDepthMap() de SceneDrawer.cpp hay que añadir código para pintar los cuadrados naranjas, cuando éstos son visibles. Una posible opción es insertar en esa función algo parecido al siguiente código, en el que, básicamente, se recorre el mapa de profundidad rellenando a naranja los píxeles que pertenecen al cuadrado.
Solución: Ficheros main.cpp y SceneDrawer.cpp modificados.

Manos invisibles

¿Qué ocurre si se dejan de mostrar las coordenadas de las manos? ¿Sigue funcionando bien el programa?

Violaciones de segmento por imágenes

Existen muchas formas de producir una violación de segmento en un código. Una muy común cuando se trabaja con imágenes está producida por los intentos de acceder a píxeles fuera de rango.

Por ejemplo, imagina que quieres pintar una línea hacia la izquierda de 50 píxeles de largo, y el punto inicial de la línea es el píxel (5,300). Está claro que la línea no va a caber entera en la imagen (se saldrá por el margen izquierdo). Pero eso el programa no lo sabe... ¡e intentará pintar la línea del píxel (5,300) al píxel (5-25,300)=(-20,300), a no ser que lo evitemos!

 

Obra publicada con Licencia Creative Commons Reconocimiento 3.0