Presentado por: Andrés Roberto Gómez Vargas
Código: 200310587
En el presente documento se presenta el desarrollo de un demo en el que se logra que una proyección responda a los movimientos del usuario, manteniendo dicha proyección en el usuario.
La realidad aumentada lleva años desarrollándose sin haber alcanzado gran cantidad de aplicaciones que se estén usando constantemente en la vida cotidiana. La punta de lanza para el aprovechamiento de nueva tecnología y nuevos desarrollos suele ser la industria de los videojuegos y el entretenimiento, siendo esta la que inicialmente suele experimentar con las posibles aplicaciones de dicha tecnología cuando no lo hace el ejército primero. Últimamente se han visto algunas aplicaciones de dicha tecnología en algunos juegos, llevando la realidad a mundos virtuales, buscando la inmersión del jugador en estos a través de modelos digitales que los representan con los movimientos lo más parecidos posibles a los realizados por su cuerpo en la realidad. Esto lo hemos visto con el auge de las nuevas formas de interacción traídos al púbico por los grandes productores de consolas, como son Nintendo con el Wii, Microsoft con el sensor Kinect para el Xbox 360 y Sony con su sensor Move para su consola PS3.
Por otro lado se han realizado trabajos en la búsqueda de llevar información digital al mundo real, como complemento y apoyo para la realización de tareas complejas o como interfaz para la visualización de información compleja o masiva de manera más palpable y digerible. Esta tarea se suele llevar a cabo a través de lentes que le inyectan información a la imagen de la realidad que se está viendo, siendo esta información restringida para aquellos que están portando dichos lentes. También se suele llevar a cabo haciendo uso de cámaras que toman la imagen de la realidad, la cual es enseguida procesada para incluir información complementaria. Esto se suele realizar tanto en PC’s como en equipos móviles, haciendo uso de marcas QR entre otros métodos. Finalmente se tienen casos en los cuales se proyecta información a las superficies sobre las cuales sea relevante, nuevamente tomando información de base con algunos sensores que pueden variar entre cámaras lectoras de códigos QR nuevamente, y sensores de luz infrarroja.
Es así como el siguiente paso lógico para los videojuegos tras habernos transportado a otros mundos, es el de traer las aventuras a este, haciendo que los objetivos a alcanzar, y los espacios a explorar se encuentren en este mundo. Se han realizado algunos avances en los cuales se desarrollan pequeños juegos de interacción entre objetos virtuales que dependen de la posición de objetos tangibles en la realidad, tales como códigos QR impresos en algún objeto. También se han realizado pequeños juegos en los cuales se proyectan pequeños personajes u objetos en una superficie y estos interactúan entre sí.
En este demo hemos puesto como objetivo el desarrollar un primer paso hacia la posibilidad de implementar una aplicación (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.
El objetivo puntual de este demo es 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.
El procedimiento realizado fue el siguiente:
Primero se limpió la imagen de fondo, dejando únicamente la imagen de los jugadores con un único color.
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] = 255; 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 procedió a invertir la imagen del esqueleto y de fondo alrededor del eje vertical para permitir que los movimientos proyectados coincidieran con los movimientos realizados por el jugador.
for (int j = 0; j < this.depthFrame32.Length; j += (this.imageWidth)*4) { for (int i = j + (this.imageWidth) * 4 - 1, n = j; i >= j; i -= 4, n += 4) { this.depthFrame32[i - 3] = this.iDepthFrame32[n]; this.depthFrame32[i - 2] = this.iDepthFrame32[n + 1]; this.depthFrame32[i - 1] = this.iDepthFrame32[n + 2]; this.depthFrame32[i] = this.iDepthFrame32[n + 3]; } }
if (this.ShowJoints) { // Render Joints foreach (JointMapping joint in this.jointMappings.Values) { Brush drawBrush = null; switch (joint.Joint.TrackingState) { case JointTrackingState.Tracked: drawBrush = this.trackedJointBrush; break; case JointTrackingState.Inferred: drawBrush = this.inferredJointBrush; break; } if (drawBrush != null) { Point centro = joint.MappedPoint; centro.X = 640-centro.X; drawingContext.DrawEllipse(drawBrush, null, centro, JointThickness * this.scale, JointThickness * this.scale); } } //Override head JointMapping joint1; JointMapping joint2; if (this.jointMappings.TryGetValue(JointType.Head, out joint1) && this.jointMappings.TryGetValue(JointType.ShoulderCenter, out joint2) && joint1.Joint.TrackingState == JointTrackingState.Tracked && !(joint1.Joint.TrackingState == JointTrackingState.Inferred)) { Brush drawBrush = null; drawBrush = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0)); if (drawBrush != null) { Point centro = joint1.MappedPoint; centro.X = 640 - centro.X; drawingContext.DrawEllipse(drawBrush, null, centro, (joint1.MappedPoint.Y - joint2.MappedPoint.Y) * this.scale, (joint1.MappedPoint.Y - joint2.MappedPoint.Y) * this.scale); } } }
private void DrawBone(DrawingContext drawingContext, JointType jointType1, JointType jointType2) { JointMapping joint1; JointMapping joint2; // If we can't find either of these joints, exit if (!this.jointMappings.TryGetValue(jointType1, out joint1) || joint1.Joint.TrackingState == JointTrackingState.NotTracked || !this.jointMappings.TryGetValue(jointType2, out joint2) || joint2.Joint.TrackingState == JointTrackingState.NotTracked) { return; } // Don't draw if both points are inferred if (joint1.Joint.TrackingState == JointTrackingState.Inferred && joint2.Joint.TrackingState == JointTrackingState.Inferred) { return; } // We assume all drawn bones are inferred unless BOTH joints are tracked Pen drawPen = this.inferredBonePen; drawPen.Thickness = InferredBoneThickness * this.scale; if (joint1.Joint.TrackingState == JointTrackingState.Tracked && joint2.Joint.TrackingState == JointTrackingState.Tracked) { drawPen = this.trackedBonePen; drawPen.Thickness = TrackedBoneThickness * this.scale; } Point punto1 = joint1.MappedPoint; punto1.X = 640 - punto1.X; Point punto2 = joint2.MappedPoint; punto2.X = 640 - punto2.X; drawingContext.DrawLine(drawPen, punto1, punto2); }
Al realizar los primeros intentos de calibrar la imagen proyectada con la ubicación del jugador se detecto que la luz proyectada en la cara sería un problema, por lo cual se realizaron cambios para lograr que nada fuera proyectado a esta.
//Override head JointMapping joint1; JointMapping joint2; if (this.jointMappings.TryGetValue(JointType.Head, out joint1) && this.jointMappings.TryGetValue(JointType.ShoulderCenter, out joint2) && joint1.Joint.TrackingState == JointTrackingState.Tracked && !(joint1.Joint.TrackingState == JointTrackingState.Inferred)) { Brush drawBrush = null; drawBrush = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0)); if (drawBrush != null) { Point centro = joint1.MappedPoint; centro.X = 640 - centro.X; drawingContext.DrawEllipse(drawBrush, null, centro, (joint1.MappedPoint.Y - joint2.MappedPoint.Y) * this.scale, (joint1.MappedPoint.Y - joint2.MappedPoint.Y) * this.scale); } }
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 usuario.
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.
Es necesario realizar ajustes manuales a la dirección a la que apunta el proyector para que la imagen se sitúe apropiadamente sobre el usuario.
Con el presente demo he podido comprobar las posibilidades que pone a nuestra disposición el Kit de Desarrollo para Kinect proveído por Microsoft.
Hemos encontrado que la velocidad de respuesta del software y hardware del dispositivo nos presenta cierto delay de reacción, a la vez que hemos visto que el sensor tiene ciertas limitantes a la hora de identificar el “esqueleto” del usuario, en especial cuando este se agacha o arrodilla y cuando una parte del cuerpo se sobrepone a otra de forma muy cercana (ponerse la mano en el torso).
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.
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.
Aún así he comprobado que es posible desarrollar con este una primera aproximación a la aplicación que se tenía en mente a la hora de realizar este demo, un juego que haga del jugador el “dispositivo de entrada y salida” al mismo tiempo.