Proyected Reality - Aplicación

Presentado por: Andrés Roberto Gómez Vargas

Código: 200310587

Introducción y Motivación

En el presente documento se presenta el desarrollo de un proyecto en el que se integra la interacción con un videojuego (entrada y salida) en el jugador como tal.

Objetivos

En este proyecto hemos puesto como objetivo el desarrollar un juego que se desarrolle en la superficie del cuerpo de los jugadores, y que permita la interacción a través del movimiento de las personas. Es así como se desarrolló ¡Quítatela!

En este juego el objetivo es retirarse del cuerpo diversas figuritas que aparecen en este, procurando retirar la mayor cantidad de figuras posibles en el transcurso de un minuto. Las reglas son simples: con la mano se debe impedir que la imagen de las figuras alcance el cuerpo del jugador, lo cual retirará la figura anotando un punto y generando una nueva figura en un nuevo punto del cuerpo del jugador al azar.

Para la realización de este proyecto he realizado una primera iteración en un demo en el cual procuro lograr la proyección de un esqueleto en una persona de forma que el movimiento de estos dos se realice de forma coordinada y sobrepuesta. Esto se logró a partir de una aplicación de ejemplo, “Explorer”, que trae el Kit de Desarrollo para Kinect proveído por Microsoft, en el cual se permite explorar la programación de una pequeña aplicación haciendo uso del esqueleto, la imagen de profundidad, la imagen de colores y la interacción por medio de sonidos. De esta aplicación se hizo uso del módulo que fusiona la imagen de profundidad con el esqueleto.

Esta aplicación sería usada como aplicación de calibración cada vez que se quisiera proyectar en un jugador.

Desarrollo Aplicación

Sobre este demo se empezó a construir el juego de la siguiente manera:

Se eliminó en su totalidad la imagen de fondo y de esqueleto para que no produjeran interferencia en la proyección de las figuras.

      private void DrawBonesAndJoints(DrawingContext drawingContext)
      {
          if (this.ShowBones)
          {
              this.DrawBone(drawingContext, JointType.WristLeft, JointType.HandLeft);
      
              this.DrawBone(drawingContext, JointType.WristRight, JointType.HandRight);
          }
      
          if (this.ShowJoints)
          {
              this.StartGame();
              this.play(drawingContext);
          }
      }
          for (int i16 = 0, i32 = 0; i16 < depthFrame.Length && i32 < this.iDepthFrame32.Length; i16++, i32 += 4)
          {
              int player = depthFrame[i16] & DepthImageFrame.PlayerIndexBitmask;
              int realDepth = depthFrame[i16] >> DepthImageFrame.PlayerIndexBitmaskWidth;
              
              // transform 13-bit depth information into an 8-bit intensity appropriate
              // for display (we disregard information in most significant bit)
              byte intensity = (byte)(~(realDepth >> 4));
          
              if (player == 0 && realDepth == 0)
              {
                  // white 
                  this.iDepthFrame32[i32 + RedIndex] = 0;
                  this.iDepthFrame32[i32 + GreenIndex] = 0;
                  this.iDepthFrame32[i32 + BlueIndex] = 0;
              }
              else if (player == 0 && realDepth == tooFarDepth)
              {
                  // dark purple
                  this.iDepthFrame32[i32 + RedIndex] = 0;
                  this.iDepthFrame32[i32 + GreenIndex] = 0;
                  this.iDepthFrame32[i32 + BlueIndex] = 0;
              }
              else if (player == 0 && realDepth == unknownDepth)
              {
                  // dark brown
                  this.iDepthFrame32[i32 + RedIndex] = 0;
                  this.iDepthFrame32[i32 + GreenIndex] = 0;
                  this.iDepthFrame32[i32 + BlueIndex] = 0;
              }
              else if (player != 0)
              {
                  // dark brown
                  this.iDepthFrame32[i32 + RedIndex] = 0;
                  this.iDepthFrame32[i32 + GreenIndex] = 0;
                  this.iDepthFrame32[i32 + BlueIndex] = 0;
              }
              else
              {
                  this.iDepthFrame32[i32 + RedIndex] = 0;
                  this.iDepthFrame32[i32 + GreenIndex] = 0;
                  this.iDepthFrame32[i32 + BlueIndex] = 0;
              }

Enseguida se introdujo la proyección del puntaje y el tiempo en el pecho del jugador para eliminar la necesidad de cualquier otro dispositivo de salida.

          JointMapping shoulderCenter;
          JointMapping spine;
          
          // If we can't find either of these joints, exit
          if (!this.jointMappings.TryGetValue(JointType.ShoulderCenter, out shoulderCenter) ||
              shoulderCenter.Joint.TrackingState == JointTrackingState.NotTracked ||
              !this.jointMappings.TryGetValue(JointType.Spine, out spine) ||
              spine.Joint.TrackingState == JointTrackingState.NotTracked)
          {
              return;
          }
          
          // Don't show if any of these points are inferred
          if (shoulderCenter.Joint.TrackingState == JointTrackingState.Inferred ||
              spine.Joint.TrackingState == JointTrackingState.Inferred)
          {
              return;
          }
          
          Point puntoArriba = shoulderCenter.MappedPoint;
      
          Point puntoAbajo = spine.MappedPoint;
      
          Point centro = new Point();
      
          centro.X = 640 - puntoArriba.X - 20;
          centro.Y = puntoArriba.Y + (puntoAbajo.Y - puntoArriba.Y) - 20;
      
          DateTime dtCurrentTime = DateTime.Now;
      
          TimeSpan haPasado = dtCurrentTime - this.ShowTime;
      
          if (this.Start)
          {
              FormattedText tiempo = new FormattedText("T: " + haPasado.Seconds, CultureInfo.GetCultureInfo("en-us"), FlowDirection.LeftToRight, new Typeface("Verdana"), 20, trackedJointBrush);
      
              drawingContext.DrawText(tiempo, centro);
          }
          else
          {
              FormattedText tiempo = new FormattedText("T: 0", CultureInfo.GetCultureInfo("en-us"), FlowDirection.LeftToRight, new Typeface("Verdana"), 20, trackedJointBrush);
      
              drawingContext.DrawText(tiempo, centro);
          }
      
          centro.Y = centro.Y - 30;
      
          FormattedText puntos = new FormattedText("P: " + this.ShowPoints, CultureInfo.GetCultureInfo("en-us"), FlowDirection.LeftToRight, new Typeface("Verdana"), 20, trackedJointBrush);
      
          drawingContext.DrawText(puntos, centro);

Se definió el gesto de juntar las manos por encima de la cabeza para iniciar el conteo del tiempo del jugador, siendo este independiente para cada uno. Esto permitió eliminar la necesidad de cualquier otro elemento de entrada para interactuar con la aplicación.

          if (puntoHandRight.Y < puntoHead.Y && puntoHandLeft.Y < puntoHead.Y && puntoHandRight.X - puntoHandLeft.X < 100)
          {
              if (!this.Start)
              {
                  this.Start = true;
          
                  this.ShowTime = DateTime.Now;
          
                  this.ShowPoints = 0;
          
                  Random objeto1 = new Random();
                  int numero1 = objeto1.Next(11);
          
                  this.Bone = numero1;
              }
          }

Finalmente se desarrolló la generación aleatoria de figuras (una a la vez), pudiendo ubicarse estas en cualquiera de las múltiples articulaciones del esqueleto generado por el kinect, sin contar la cabeza a la que eliminamos por las mismas razones que se eliminó en el demo.

          Point centerObject = new Point();
          
          if (this.Start)
          {
              if (this.Bone == 0)
              {
                  centerObject = obtenerPosicion(JointType.ShoulderLeft);
              }
              else if (this.Bone == 1)
              {
                  centerObject = obtenerPosicion(JointType.ShoulderRight);
              }
              else if (this.Bone == 2)
              {
                  centerObject = obtenerPosicion(JointType.ShoulderCenter);
              }
              else if (this.Bone == 3)
              {
                  centerObject = obtenerPosicion(JointType.Spine);
              }
              else if (this.Bone == 4)
              {
                  centerObject = obtenerPosicion(JointType.HipLeft);
              }
              else if (this.Bone == 5)
              {
                  centerObject = obtenerPosicion(JointType.HipRight);
              }
              else if (this.Bone == 6)
              {
                  centerObject = obtenerPosicion(JointType.HipCenter);
              }
              else if (this.Bone == 7)
              {
                  centerObject = obtenerPosicion(JointType.KneeLeft);
              }
              else if (this.Bone == 8)
              {
                  centerObject = obtenerPosicion(JointType.KneeRight);
              }
              else if (this.Bone == 9)
              {
                  centerObject = obtenerPosicion(JointType.ElbowLeft);
              }
              else if (this.Bone == 10)
              {
                  centerObject = obtenerPosicion(JointType.ElbowRight);
              }
          }

Esto se vería complementado por la función que se encarga de establecer si se ha marcado un punto nuevo o no, y de detener el tiempo al final del turno.

          if (this.Start)
          {
              Brush drawBrush = Brushes.Blue;
          
              drawingContext.DrawEllipse(drawBrush, null, centerObject, JointThickness * this.scale * 2, JointThickness * this.scale * 2);
          
              JointMapping rightHand;
              JointMapping leftHand;
          
              double distancia1 = 1000;
              double distancia2 = 1000;
          
              if (this.jointMappings.TryGetValue(JointType.HandRight, out rightHand) ||
              rightHand.Joint.TrackingState != JointTrackingState.NotTracked)
              {
                  distancia1 = Math.Sqrt(Math.Pow(rightHand.MappedPoint.X - (640 - centerObject.X), 2) + Math.Pow(rightHand.MappedPoint.Y - (centerObject.Y), 2));
              }
          
              if (this.jointMappings.TryGetValue(JointType.HandLeft, out leftHand) ||
              leftHand.Joint.TrackingState != JointTrackingState.NotTracked)
              {
                  distancia2 = Math.Sqrt(Math.Pow(leftHand.MappedPoint.X - (640 - centerObject.X), 2) + Math.Pow(leftHand.MappedPoint.Y - (centerObject.Y), 2));
              }
          
              if (distancia1 <= 20 || distancia2 <= 20)
              {
                  System.Media.SystemSounds.Exclamation.Play();
                  this.ShowPoints++;
                  Random objeto1 = new Random();
                  int numero1 = objeto1.Next(11);
          
                  this.Bone = numero1;
              }
          
              if (haPasado.Minutes >= 1)
              {
                  this.Start = false;
              }
          }

Código Fuente

Equipos Utilizados e Instalación y Uso de la Aplicación

Como dispositivos se utilizó un sensor Kinect de Microsoft y un proyector InFocus DQ3120-X2 conectados a un equipo que tiene instalados los drivers para el Kinect.

Para hacer uso del kinect se utilizó el SDK versión 1.0.3.191 de Kinect for Windows.

Para correr la aplicación se puede desde el editor de código o desde el ejecutable que ya viene incluido en el archivo comprimido en la carpeta Explorer\C#\KinectExplorer\bin\Debug.

Si se va a correr desde el editor de código se puede instalar el Microsoft Visual Studio 2010 Express, tras lo cual se procede a hacer la instalación del SDK del Kinect.

Tras haber realizado estas tareas y haber conectado los dispositivos al computador se procede a ubicarlos. El sensor Kinect se ubica dos metros y medio por delante del proyector, y dos metros y medio delante de este se ubicará el jugador.

Habiendo terminado esto se abre el archivo sln de la carpeta Explorer\C#\KinectExplorer y se ejecuta la aplicación (en cualquiera de sus dos modalidades).

Al estar en la aplicación se procede a entrar a la configuración que se encuentra en la parte inferior. En esta se realizan dos cambios en la sección DepthStream: Se Deshabilita la opción Enabled y se ubica en la posición de resolución 640*480.

Se procede a ubicar el ángulo de elevación del sensor en una posición en la que todo el cuerpo quepa en la imagen.

Finalmente se procede a transferir la imagen al proyector y se pone la aplicación en pantalla completa.

Se recomienda correr primero la aplicación demo para poder ubicar la dirección a la que apunta el proyector de forma correcta, y luego si ejecutar el juego.

Artículo Aplicación

Video

Evaluación

Tras realizar algunas pruebas se notó que para el jugador era muy complicado alcanzar puntos como el de las pantorrillas y los pies (el sensor del kinect no interpreta muy bien el esqueleto del jugador cuando este se inclina o arrodilla), y que habían puntos que se anotaban solos como el de las manos (por obvias razones) y el de las muñecas por su cercanía a estas. Es así que se procedió a remover estos puntos de las posibles ubicaciones para las figuras. (Ver código fuente o código de desarrollo)

Tras realizar pruebas de usuario se pudo encontrar que a pesar de que el del kinect tiene ciertas dificultades para interpretar el esqueleto del jugador cuando una parte de este se sobrepone a otra, este problema se puede mitigar pidiendo al jugador que mantenga las manos con las que intenta atrapar la figurita tan apartada como pueda de la parte del cuerpo en la que esta se está proyectando, interceptando la proyección a la distancia.

También se hizo notar que era buena idea aumentar el feedback con sonido, ya que era fácil pasar por alto que se ha anotado un punto, y que el movimiento del usuario debe ser relativamente lento para darle tiempo a la aplicación ya que esta tiene un cierto delay propio del kinect sumado a alguno generado por la aplicación en si. (Ver código fuente o código de desarrollo)

Por último es de notar que la iluminación del lugar es primordial para poder ver la proyección, lo cual se facilita aún más si el jugador lleva ropa blanca.

Es así que a pesar de estas dificultades se encuentra el juego entretenido y se ve en este una base para crear diferentes juegos que unifiquen el dispositivo de entrada y de salida en el mismo jugador. Se observó que puede ser una opción válida el que un jugador se sitúe frente al jugador principal para ayudarle a encontrar más rápidamente los elementos proyectados en su cuerpo.

Mirando desde el montaje mismo de la aplicación se encuentra una gran dificultad en especial a la hora de calibrar la posición del proyector y el sensor, lo cual puede llegar a tomar entre media hora y una hora.

Conclusiones

A partir de este proyecto he verificado la posibilidad de crear un juego que sea entretenido haciendo uso de la proyección en la realidad. En este se ha trabajado en el desarrollo de aplicaciones y métodos que unifiquen el dispositivo de entrada y salida en uno solo, permitiendo realizar gestos que tienden a ser más intuitivos.

Se ha aprovechado el Kit de Desarrollo de Microsoft de manera que cualquier avance de precisión y eficiencia aplicado a este se verá directamente reflejado en esta aplicación, y se debe profundizar en la búsqueda de dispositivos de proyección que sean más prácticos.

En la aplicación se debe buscar el facilitar la calibración, a la vez que el permitir que más de un jugador juegue al tiempo de manera más precisa (actualmente se puede pero al moverse hacia los lados de la proyección se tiende a perder precisión).

Como nuevos posibles objetivos de investigación tenemos el interactuar con un modelo más apropiado para el jugador, como podría ser un modelo en 3D de él mismo, y el uso de varios sensores a la vez para permitir no solo un mejor seguimiento del jugador sino también una proyección que cubra la totalidad del cuerpo de este.

Finalmente se debe ampliar la aplicación permitiendo una mayor variedad de actividades, explorando incluso el trabajo en equipo haciendo uso de la priocepción del jugador que se mueve, con ayuda de otro jugador que puede observar más fácilmente la proyección en este.

proyected_reality_-_kinect.txt · Última modificación: 2012/05/17 19:57 por and-gome
Departamento de Ingeniería de Sistemas y Computación - Facultad de Ingeniería - Universidad de los Andes
CC Attribution-Noncommercial-Share Alike 3.0 Unported
Valid CSS Driven by DokuWiki Recent changes RSS feed Valid XHTML 1.0