lunes, 24 de septiembre de 2012

SDK Kinect for Dummies-2


Después de un tiempo he decidido continuar con las entradas del SDK de Kinect. En la primera entrada veíamos como mostrar la cámara RGB. También podemos ver una charla de introducción al Kinect SDK. En esta ocasión explicaré paso a paso como mostrar el DepthStream.


Veremos dos formas de pintar nuestro DepthStream. En el primer caso pintaremos directamente la intensidad y en el segundo caso al detectar un usuario pintaremos su silueta de un color.

Antes de comenzar vamos a ver las fórmulas para obtener la distancia y el índice del usuario:
- Distancia: int depth = depthPoint >> DepthImageFrame.PlayerIndexBitmaskWidth;
- Usuario/jugador: int player = depthPoint & DepthImageFrame.PlayerIndexBitmask;

En el caso del usuario debemos saber que hay que habilitar el SkeletonStream, aunque aún no hablemos de Skeleton.

Pintar DepthStream:

Comenzaremos como en la anterior entrada buscando nuestro sensor Kinect. En este caso lo realizaremos de una forma diferente.

 
 foreach (var potentialSensor in KinectSensor.KinectSensors)
            {
                if (potentialSensor.Status == KinectStatus.Connected)
                {
                    this.miKinect = potentialSensor;
                    break;
                }
            }


Recorremos la colección de sensores y nos quedaremos con el primer sensor que encontremos conectado.

Una vez obtenido el sensor pasaremos a activar los Streams y encender el sensor.  En mi caso esto lo realizo al cargar la ventana. Además debemos agregar el control del evento, se puede controlar mediante AllFramesReady, este evento se ejecuta una vez que están listos todos los Frames de los stream que estén activos. En este caso gestionaremos el evento DepthFrameReady que tan solo tenemos que esperar a los DepthFrame. IMPORTANTE: en el evento de cierre de la ventana debemos parar nuestro sensor.

if (this.miKinect != null)
            {

                // Habilitamos la cámara elegiendo el formato de imagen.
                miKinect.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);

                miKinect.SkeletonStream.Enable();
                
                this.depthPixels = new short[this.miKinect.DepthStream.FramePixelDataLength];

                this.colorPixels = new byte[this.miKinect.DepthStream.FramePixelDataLength * sizeof(int)];
                // Arrancamos Kinect.
                miKinect.Start();
                // Nos suscribimos al método
                miKinect.DepthFrameReady += DepthImageReady;
            }


A continuación vemos la gestión del evento. En este evento emplearemos las anteriores formulas para pintar. En un principio usaremos la distancia junto con una mascara de bytes para poder pintar en función de esta. En este caso tenemos del DepthFrame un array de 'short' en lugar de un array de 'bytes'. Es sobre los puntos de este array de short sobre los que se debe aplicar las fórmulas. Una vez abierto el frame y obtenida la distancia le aplicamos una mascara para poder obtener la intensidad. Para pintarlo necesitamos un array de bytes de 4 veces el tamaño del array de short. Esto es por las componentes RGB más la componente alfa del RGB, sin embargo solo pintaremos en las posiciones RGB. Por último pintamos el array de bytes en un bitmap.Veamos:

 private void DepthImageReady(object sender, DepthImageFrameReadyEventArgs e)
        {
            using (DepthImageFrame depthFrame = e.OpenDepthImageFrame())
            {
                // Si este es null no continuamos
                if (depthFrame == null) return;
                // Creamos un array de bytes del tamaño de los pixel del frame.
               // byte[] pixels = new byte[depthFrame.PixelDataLength];
                // Copiamos los datos del frame de profundidad.
                depthFrame.CopyPixelDataTo(this.depthPixels);
                // Creamos un nuevo mapa de bits en el que podamos escribir
                this.colorBitmap = new WriteableBitmap(this.miKinect.DepthStream.FrameWidth, this.miKinect.DepthStream.FrameHeight,
                    96.0, 96.0, PixelFormats.Bgr32, null);
                
                // PINTAMOS
                int colorPixelIndex = 0;
                for (int i = 0; i < this.depthPixels.Length; ++i)
                {
                    
                    // discard the portion of the depth that contains only the player index
                    short depth = (short)(this.depthPixels[i] >> DepthImageFrame.PlayerIndexBitmaskWidth);

                    // to convert to a byte we're looking at only the lower 8 bits
                    // by discarding the most significant rather than least significant data
                    // we're preserving detail, although the intensity will "wrap"
                    byte intensity = (byte)(depth & byte.MaxValue);

                        // Write out blue byte
                        this.colorPixels[colorPixelIndex++] = intensity;

                        // Write out green byte
                        this.colorPixels[colorPixelIndex++] = intensity;

                        // Write out red byte                        
                        this.colorPixels[colorPixelIndex++] = intensity;

                    // We're outputting BGR, the last byte in the 32 bits is unused so skip it
                    // If we were outputting BGRA, we would write alpha here.
                    ++colorPixelIndex;
                }

                // Pintar con color a los usuarios
                //ConvertDepthFrame(this.depthPixels, ((KinectSensor)sender).DepthStream, ref this.colorPixels);

                this.colorBitmap.WritePixels(
                   new Int32Rect(0, 0, this.colorBitmap.PixelWidth, this.colorBitmap.PixelHeight),
                   this.colorPixels,
                   this.colorBitmap.PixelWidth * sizeof(int),
                   0);

                imageKinect.Source = this.colorBitmap;


            }
        }

Resultado:

A continuación vamos a ver una función que nos ayuda a pintar al usuario de un color. En esta función usamos los valores por defecto de distancia de kinect: demasiado cerca, demasiado lejos, desconocido. Cada una de estas distancias se pintara de un color. En el resto de los casos usaremos la variable de "jugador" para una mascara que dará color a los usuarios. El jugador/usuario servirá como índice del array. Veamos los valores de los array de la mascara:

// color divisors for tinting depth pixels
        private static readonly int[] IntensityShiftByPlayerR = { 1, 2, 0, 2, 0, 0, 2, 0 };
        private static readonly int[] IntensityShiftByPlayerG = { 1, 2, 2, 0, 2, 0, 0, 1 };
        private static readonly int[] IntensityShiftByPlayerB = { 1, 0, 2, 2, 0, 2, 0, 2 };


Veamos el método de pintado.

// Converts a 16-bit grayscale depth frame which includes player indexes into a 32-bit frame
        // that displays different players in different colors
        private void ConvertDepthFrame(short[] depthFrame, DepthImageStream depthStream,ref byte[] depthFrame32)
        {
            int tooNearDepth = depthStream.TooNearDepth;
            int tooFarDepth = depthStream.TooFarDepth;
            int unknownDepth = depthStream.UnknownDepth;

            // Test that the buffer lengths are appropriately correlated, which allows us to use only one
            // value as the loop condition.
            if ((depthFrame.Length * 4) != depthFrame32.Length)
            {
                throw new InvalidOperationException();
            }

            int colorPixelIndex = 0;
            for (int i16 = 0; i16 < depthFrame.Length; i16++)
            {
                int player = depthFrame[i16] & DepthImageFrame.PlayerIndexBitmask;
                int realDepth = depthFrame[i16] >> DepthImageFrame.PlayerIndexBitmaskWidth;

                if (player == 0 && realDepth == tooNearDepth)
                {
                    // white 
                    depthFrame32[colorPixelIndex++] = 255;
                    depthFrame32[colorPixelIndex++] = 255;
                    depthFrame32[colorPixelIndex++] = 255;
                }
                else if (player == 0 && realDepth == tooFarDepth)
                {
                    // dark purple
                    depthFrame32[colorPixelIndex++] = 66;
                    depthFrame32[colorPixelIndex++] = 0;
                    depthFrame32[colorPixelIndex++] = 66;
                }
                else if (player == 0 && realDepth == unknownDepth)
                {
                    // dark brown
                    depthFrame32[colorPixelIndex++] = 66;
                    depthFrame32[colorPixelIndex++] = 66;
                    depthFrame32[colorPixelIndex++] = 33;
                }
                else
                {
                    // 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));

                    // tint the intensity by dividing by per-player values
                    depthFrame32[colorPixelIndex++] = (byte)(intensity >> IntensityShiftByPlayerR[player]);
                    depthFrame32[colorPixelIndex++] = (byte)(intensity >> IntensityShiftByPlayerG[player]);
                    depthFrame32[colorPixelIndex++] = (byte)(intensity >> IntensityShiftByPlayerB[player]);
                }
                ++colorPixelIndex;
            }
        }


Resultado:

Enlace con el código: http://sdrv.ms/Q2pwO1

NOTA: Está compilado con la versión 2012 de Visual Studio.

25 comentarios:

  1. Hola! Soy una estudiante y estoy intentando calcular las distancias con la camera kinect, estoy empezando en este mundillo y me vendría muy bien un poco de ayuda. Sobre todo me interesa que me aparezcan las distancias en la pantalla.

    Muchisimas Gracias

    ResponderEliminar
    Respuestas
    1. Hola! ¿Estas intentando calcular la distancia de la persona que esta delante del sensor? ¿O de algún otro punto?

      Eliminar
    2. La verdad esque estoy intentando calcular la distancia entre un coche y la camera, o si se puede(que aún no lo se)calcular la distancia entre dos objetos.

      Eliminar
    3. Para ello es necesario un algoritmo que reconozca objetos. Con el SDK de Kinect tan solo tenemos el algoritmo de detección de las personas. Por otra parte se pueden encontrar algoritmos que analizan imágenes para detectar ciertos objetos, una vez encontrado el objeto con kinect podemos encontrar la distancia porque calcula la distancia a todos los puntos. Si ya tienes localizado el coche si te puedo ayudar. Mira si a partir de aquí puedes sacar algo http://www.neoteo.com/openkinect-ya-reconoce-objetos-y-otros-hacks

      Eliminar
  2. Hola, estoy aprendiendo a programar con Kinect y quería preguntar si sabeis cómo se haría para mover el cursor utilizando kinect y no tener que utilizar el ratón. Gracias.

    ResponderEliminar
    Respuestas
    1. Hola, te dejo un ejemplo que he encontrado: http://geekswithblogs.net/mbcrump/archive/2011/07/04/kinecting-the-dots-adding-buttons-to-your-kinect-application.aspx

      Ahora mismo estoy de exámenes, en cuanto termine haré yo mismo un ejemplo y daré más links de como lo resuelven otras personas.

      Gracias por tu comentario =)

      Eliminar
  3. Hola, una vez que activamos el skeleton: mikinect.SkeletonStream.Enable(); para guardar un skeleton hago SkeletonFrame skeleton = mikinect.ColorFrame Ready(); pero no es correcto, quiero obtener el esqueleton para luego sacar x,y de algún punto (mano,cabeza). ¿Cómo se haría? Muchas gracias.

    ResponderEliminar
  4. Hola,el caso del skeleton es especial. Preparo una entrada explicando como pintarlo =). Mientras puede ir viendo ejemplos de otras personas acerca de como pintarlo: http://elbruno.com/2011/11/11/kinect-howto-pintar-un-skeleton-en-wpf/

    ResponderEliminar
    Respuestas
    1. Muchas gracias, lo único que ese ejemplo está hecho con la SDK beta y los comandos son algo distintos, tanto para iniciar el kinect como para trabajar con él. Un poco lío, pero gracias, espero su entrada,ya que los comandos serán los de la última SDK, y lo entenderé mejor.

      Eliminar
    2. A ver si saco tiempo y tengo pronto la entrada para pintar el Skeleton.

      No sé si habrás visto la charla que dí acerca de programación con Kinect: http://cortesfernando.blogspot.com.es/2012/09/kinectizate-introduccion-al-kinect-sdk.html

      En ese enlace tienes un link con el material empleado en la charla. Puedes ver que hay un ejemplo de pintar el Skeleton ,otro de un radar y otro ejemplo, en el que dadas las posiciones de manos y cabeza, te pinta una cara y manos de hulk ;)

      Gracias por tu interés. Con gente así motiva realizar más y mejores entradas en el blog =)

      Eliminar
  5. Hola que tal? estoy aprendiendo a programar con Kinect para un proyecto de la escuela, sabes si hay algun algoritmo para la deteccion del piso o calcular la distancia de algun punto especifico del piso?
    Gracias!! btw muy buen post!

    ResponderEliminar
    Respuestas
    1. Buenos días,
      la verdad es que no tengo ningún algoritmo para lo que me pides. No me lo había planteado. Yo he visto en alguna noticia que algunos investigadores han llegado a hacer un plano 3D de una habitación.

      Si vas más por la parte de gestos y posturas te recomiendo que busques este libro: https://www.amazon.es/gp/product/B009AITHPC/ref=kinw_myk_ro_title

      Gracias por tu comentario.

      Eliminar
  6. Saludos
    Estimado soy estudiante y estoy realizando mi tesis y viendo el video de magia cyber de TED, me dio la idea de realizar algo parecido, pero referente al cuerpo humano, recien estoy empapandome de la kinect, por lo cual necesito saber primero si se puede hacer y segundo una guia por donde empezar, el proyecto trata de que un estudiante podra visualizar un cuerpo humano (por medio del proyector) y el por medio de la kinect podra explorar el mismo (quitar las capas, zoom in zoom out, girar las partes)imagino q esto al igual q el video de TED, lo debo realizar por medio de animacion en 3d y flash?? pregunta debo usar otra camara parte de la kinect?? asi km aparece en el video de TED, y otra si se podra fusinar todo??? ayudame con estas inquietudes y sugerencias porfa, deseo saber si se puede antes de invertir en la compra de los dispositivos y la figuras de 3d y animacion??

    ResponderEliminar
    Respuestas
    1. me olvidaba el poryecto se basa en la realidad aumentada con la kinect, ese mas o menos seria el titulo pero agregandole "el cuerpo humano"

      Eliminar
    2. te he respondido antes de leer esto último. Hago un pequeño apunte también: Hay gente que realiza realidad aumentada con kinect gracias a XNA, aunque esta plataforma está camino de su extinción. Sin embargo, al ser para una tesis quizá aún te interese. Si no, pues con C++ y direct3D

      Eliminar
  7. Buenos días,
    no he visto el vídeo que comentas. Dejame el link y lo veo.

    Mediante kinect podría detectar gestos de zoom, de giro, o para quitar capas. La parte de la animación no la conozco mucho (no me he puesto nunca ha ello).

    En cuanto a Kinect ahora mismo estoy leyendo un libro que aborda el tratamiento de gestos simples, posturas y gestos más complejos. Yo he comprado el libro, si no estas seguro de que vas a usar kinect intenta buscar algún libro gratuito que hable de ello.

    El libro es: Programming with the KinectTM for Windows® Software Development Kit: Add gesture and posture recognition to your applications

    https://www.amazon.es/gp/product/B009AITHPC/ref=kinw_myk_ro_title

    Gracias por tu comentario y espero haberte servido de ayuda.

    ResponderEliminar
    Respuestas
    1. ok gracias en u cuenta de tw lo vi, https://www.youtube.com/watch?v=levKjAAMPUM o este https://www.youtube.com/watch?v=A2Lgw9zFH-Y

      Eliminar
    2. y este q es el q me dice los dispositivos q usa http://www.electrictv.com/?p=16102

      Eliminar
    3. Ya sé, en ese vídeo en algunas partes el trabajo de kinect no es muy grande. Casí todo el peso lo tienes en crear los efectos algo de lo que no tengo mucho conocimiento.

      Al final también va a depender de los efectos que desees, la precisión que quieras, por ejemplo con la última versión del sdk de kinect han mejorado la detección de una persona con varios kinects.

      Si finalmente decides hacer este proyecto y lo consigues, me encataría poder ver algún vídeo del resultado ;)

      Eliminar
    4. Increible el de 'behind the scenes'. Muy bueno, usan una camara extra para tener una mayor resolución. Fijate todo el equipo de personas que tiene para realizar todo el "truco".

      Eliminar
    5. Exacto es increible, por eso mi pregunta si el sdk se puede programar es decir usare el visual studio mas el sdk de kinect, la animacion y los efectos son en flash y algun prog de 3d, kiero saber si se podra fusionar todo, la kinect lo q haria es reconce las manos y los movientos o creo un boton pa girar y el zoom, pero deseo saber con q programas es compatible la programacion de lakinect?? esa es mi inquietud??

      Eliminar
    6. Pues creo que con flash no es posible. Aunque no lo he probado ni he investigado acerca de ello. Yo hasta ahora había visto usar XNA que hace uso interno de DirectX

      De todos modos existen otros drivers aparte del SDK oficial. Por ejemplo el SDK oficial no detecta los dedos, mientras que desde el MIT tienen unos drives y una API libre en la que si detectan los dedos.

      Lamento no poder ser de más ayuda en este tema.

      Eliminar
  8. Como hacer Domotica con Kinect y Arduino... hacer que sensor simplemente sienta la presencia de la persona y poder encender las luces y cuando no sienta la presencia de la persona apagar las luces.

    ResponderEliminar
  9. al igual que muchos estoy aprendiendo a programar en kinect para un proyecto de escuela, y consiste en poder apagar una luz con una seña o encenderla de misma forma de casualidad, ¿tendrias una programacion parecida?

    ResponderEliminar
  10. alguien sabe como obtener el color de un pixel en especifico?

    ResponderEliminar