Detección de gestos horizontales con Kinect

El esqueleto detectado de una persona a través de Kinect posee la información de las coordenadas (x, y) de todas sus articulaciones (esqueleto).

Observando estas coordenadas a través del tiempo se puede llegar a reconocer patrones de movimientos (gestos). Estos gestos se utilizan, en la mayoría de casos, para interactuar con un software. Es decir un programa es capaz de identificar los gestos de un usuario y actuar en consecuencia.

Por ejemplo para identificar el desplazamiento lateral de la mano, basta con mirar las coordenadas de la articulación correspondiente y ver si después de un tiempo, ha habido un incremento de X en una dirección notable. Evidentemente hay que considerar que el incremento de Y sea mínimo, o lo que es lo mismo, esté dentro de un threshold aceptable.

En mi caso, para una aplicación que realicé en la que mediante gestos horizontales de la mano cambiabas ropa de un maniquí, la implementación algorítmica fue más o menos así:

estructura( posicion:punto2D; tiempo: tiempo;): Gesto;
var gestos:Gesto[];

Para cada intervalo de tiempo de observación:
{
    var longitud:entero = longitud(gestos);
    Si(longitud >= 30)
        ShiftGesture();
    gestos[longitud - 1].posicion = PosicionActualMano();
    gestos[longitud - 1].tiempo = TiempoActual();

    var dX:entero = CalculaDXValido();
    var dY:entero = CalculateDYValido();

    Si(dX >= tDX && dY <= tDY)
    {
        print("Gesto lateral reconocido");
        ResetearGestos();
    }
}

La idea es tener un array de gestos donde se almacena cada cierto tiempo la posición de la mano y el tiempo. Cuando este array se llena, la primera posición se pierde (ShiftGesture), y todos los valores que se leen a continuación se introducen en la última posición.

Seguidamente se evalúan a partir del array de gestos los incrementos en X e Y (CalculaDXValido y CalculaDYValido) para eliminar falsos positivos como el que daría una mano que salte de una posición a otra en un corto periodo de tiempo. Si aún así ambos pasan los thresholds, existe un gesto horizontal.

Parece algo impreciso, sin embargo, he estado probando un par de juegos para ver que tal está el tema de reconocimiento de gestos en el sector profesional. Y la verdad que me ha decepcionado bastante. Por poner un ejemplo, Dance Central de XBOX 360. Te hacen repetir los movimientos de un monigote en la pantalla a modo de baile. Sin embargo basta con clavar las articulaciones o extremidades que marca el movimiento. Quiero decir, ves al monigote bailando que da envidia de lo bien que lo hace, pero como jugador con mover un pie y una mano me marca el movimiento correctamente. Hay que echarle mucha imaginación...

5

De todas formas no he sido el único que se ha sentido frustrado en ese sentido. Recuerdo que al principio, el software de ocio implementado para Kinect dio bastante de que hablar por casos como el que se muestra en el siguiente vídeo, dónde un buen hombre decide jugar a ganar sin mover un pelo, literalmente:

 

Primeros pasos con 'Kinect SDK'

Hace tiempo que empecé a trastear el SDK de Kinect.

Como se indica en la página, han dispuesto una interface con la que se puede obtener, entre otras cosas, el stream de profuncidad y el tracking de rígidos humanos a través de los sensores de Kinect.

Además de la documentación básica, se adjuntan una sèrie de samples (proyectos) en Visual C++ explicados paso a paso de una forma bastante clara.

Así pues setear algo como esto:

es relativamente rápido de hacer aprovechando parte del código del sample de seguimiento de esqueleto.

Edición de video con Sony Vegas

Sony Vegas es un programa de edición de vídeo similar a Adobe Premiere.

Vegas, a través de pistas (tracks) permite montar diferentes secuencias de vídeo y audio ordenadas por el zOrder asociado a dichas pistas. Para suavizar la unión de éstas secuencias se utilizan las transiciones, que permiten que una secuencia empiece antes de que acabe otra utilizando un efecto determinado.

Aparte del montaje básico y las transiciones también se pueden aplicar efectos. No tan buenos, desde mi punto de vista, como After Effects, pero suficientes para producciones no muy exigentes en cuanto a post-producción.

Hace un par de días hice un vídeo para promocionar la nueva tecnología del probador virtual que estamos haciendo de cara a esta temporada. Bastante simple, sin sincronización de música con las imágenes, pero suficiente para su propósito.

El montaje es el siguiente:

V2promo

Y el resultado:

Normalmente el render del vídeo se renderiza sin compresión y posteriormente se comprime con algún códec. Yo suelo utilizar MeGUI (más adelante quizás haga un minitutorial de su funcionamiento) para comprimir el vídeo a h264/MP4.

Obviamente nadie me enseñó a utilizar Vegas. Para aprender es suficiente con ponerle ganas y dar un par de vueltas por internet.

Este lo hice el primer año trabajando en Virtualtwo por que me venía de gusto. Se trata de un montaje de la serie ITCrowd. Pinté pixel a píxel los monigotes y los fui encajando como pude en capturas de pantalla de un vídeo que baje por Youtube(el de la cabecera original de ITCrowd), de ahí la calidad pésima.

Más elaborado fueron los vídeos de World of Warcraft que hice. Se basan en juegos de arenas (jugadores contra jugadores) con antiguos compañeros, los cuáles grababa con Fraps directamente mientras jugaba y luego pasaba a Vegas para realizar el montaje. Hay bastante trabajo de fondo, no sólo de Vegas. Utilicé varias herramientas: Photoshop, Combustion, 3dMax, AfterEffects y un par de programas para visualizar modelos de World of Warcraft.

Los vídeos están subidos al servidor de WarcraftMovies. Tuvieron bastante aceptación, cerca de un cuarto de millón de vistas.

Link al segundo video

Como ser una buena chroma girl.

Sdf

Aunque parezca mentira, no estoy enseñando el nuevo sistema de camuflaje del ejército estadounidense. Es el viejo sistema de chroma para separar un objeto, persona o entidad de un fondo en una imagen. Si os fijais bien y cruzais la vista durante unos segundos, vereis en el centro de la imagen y debajo del sombrero a la chica que se dedica a probarse la ropa para fotografiarla de cara al probador virtual. Increíble no?

Es admirable la evolución de la post-producción de la primera temporada del probador a esta última.

AS3 - Probador virtual de El Corte Inglés

Introducción:

Por fin es primavera en El Corte Inglés (ECI)! Muchos odiareis este anuncio. No os falta razón, es muy malo.

Dejando a un lado el exquisito gusto que tienen la mayoría de compañías para promocionarse en televisión hoy en día, aprovecharé para comentar a grandes rasgos parte del proyecto que define el probador virtual de ECI, donde mediante una interfaz web se pueden visualizar múltiples combinaciones de ropa del catálogo online que ofrece ECI sobre un maniquí virtual.

Describiré sólo el apartado del renderizador (el encargado de mostrar el modelo y las prendas de ropa) muy por encima y sin entrar en detalle, no hay que olvidar que es un software privativo. Además hay que tener en cuenta que el probador se compone en esencia de un front-end (interfaz gráfica web para los usuarios), un back-end (gestor de contenidos) y finalmente el renderizador. En definitiva, no es más que la punta del iceberg de un proyecto enorme, ambicioso y lo mejor de todo, diseñado y programado por 3 personas.

Este es el resultado final:

Main

Es gracioso cuando me pongo a hacer memoria.

Cuando entré a trabajar en Virtualtwo, hace un año más o menos, me pasaron el proyecto de un renderizador en Flex casi acabado para la temporada primavera / verano e implementado por otra persona ajena a la empresa. Era un completo desastre: 600 KB de archivo swf, código ininteligible, leaks de memoria por doquier, tiempos de carga exagerados, en fin lo que no se debe hacer nunca. Sin embargo debido a los plazos de entrega extremadamente cortos y a la dependencia del anterior front-end con éste, nos vimos obligados a arrastrar esa abominación de renderizador hasta el día de hoy. 

Para esta temporada he rehecho el renderizador por completo en AS3 (flash), ahora ocupa 20KBs, el código es modular (está compuesto de 4 clases no de 40), procesa 14 piezas de ropa a la velocidad de 1 pieza en el renderizador anterior, realiza garbage collection, y lo mejor de todo, al anterior swf se le debía pasar la estructura de imágenes montada en cada comunicación, el actual acepta 1 imagen independiente y él mismo se configura su estado, es decir realiza mucho más trabajo en menos cantidad de código. Todo esto en dos semanas, *gracias de nuevo* a las deadlines impuestas por el cliente.

A pesar de que AS3 sigue siendo un desastre en lo que a se refiere a programación multi-thread (no tiene directamente) y que su gestión de eventos es bochornosa, tiene algunos pros a considerar. Por ejemplo, el procesamiento en tareas simples para imágenes responde de una manera aceptable. No obstante el punto decisivo que forzó el desarrollo con dicho lenguaje fue la necesidad de tener un renderizador lo más rápido posible que funcionara en todos los navegadores, desde ie7 hasta Safari. Es decir, consideré la opción de realizarlo en JS con canvas, pero sólo con pensar en la existencia de Internet Explorer volví a poner los pies en el suelo.

El renderizador está estructurado en 4 grandes clases:

  • Renderer: clase principal que inicializa el renderizador, instancia a las demás clases, gestiona los eventos y la comunicación con el front-end.
  • Queue: gestiona las peticiones del usuario y carga todas las imágenes necesarias adjuntas a esas peticiones.
  • Status: contiene el estado en cada momento del renderizador.
  • Render: se encarga de presentar el resultado final y calcular la superposición de prendas de ropa mediante máscaras.

 

Renderer:

Es el core de la aplicación flash, en él se instancian y controlan las clases Queue, Status y Render.

Su constructor inicializa dichas clases y prepara el entorno en base a una estructura de capas, donde cada prenda de ropa tendrá asignada su posición de profundidad mediante la propiedad zOrder de su capa asociada. Así pues, el resultado que se muestra en pantalla será un conjunto de imágenes organizadas por capas que se ordenan en base al zOrder de éstas.

Layerstack

La inicialización además se encarga de preparar la comunicación con el front-end mediante eventos que controlan asíncronamente las siguientes acciones:

  • Vestir / desvestir una pieza de ropa.
  • Cambiar el fondo del probador.
  • Cambiar el modelo del maniquí.
  • Desnudar al maniquí.
  • Zoom.
  • Rotación del modelo.
  • Configuración del modelo (peinados y variación de pies y manos según la prenda que se vista).
  • Screenshots pasados a arrays de bytes para procesarlos con JS.
  • Iluminación de piezas de ropa concretas.

Cada una de estas acciones es cargada y procesada en una instancia de la clase Queue, donde una vez finalizado este proceso es devuelta al Renderer para facilitar el resultado al objeto de la clase Render.

 

Queue / Status:

Queue (pila de instrucciones) es el módulo encargado de interpretar las acciones. Realiza los cambios internos de estado y carga las imágenes asociadas a cada acción si así se requiere.

Cuando una acción entra en la pila, se lanzan las cargas de las imágenes asociadas a ésta. Cuando todas las acciones de la pila están completamente cargadas, lo cual implica que las imágenes asociadas han sido descargadas del servidor, la pila devuelve las acciones con sus datos adjuntos al Renderer.

Status controla en cada momento el estado del programa, almacena los objetos vestidos, la configuración del maniquí, el estado del zoom, de la rotación, etc., provocando que Renderer y Queue actuen en consecuencia.

 

Render:

Una vez Renderer obtiene los resultados de Queue, invoca a los métodos de Render para visualizarlos. Teniendo en cuenta que Renderer se encarga de posicionar correctamente las imágenes en la pila de capas ordenadas por un zOrder, el problema más importante que queda por resolver es la visualización de la superposición entre dos o más prendas de ropa. Es decir, al superponer piezas, hay que tener en cuenta si se encuentran en una capa con zOrder menor y sin embargo poseen más área que las de superior zOrder.

El problema se solucionó con máscaras que se acumulan por zOrder.

Mask

Sin embargo a pesar de ser, generalmente, eficaz y rápido, tiene ciertos inconvenientes como el que muestra la imagen anterior. Para ciertas zonas del cuerpo (colisiones de extremidades con cuerpo) és imposible aplicar una máscara. Por ejemplo en la imagen anterior, si se aplicara máscara en la zona señalada, se eliminaría parte de la manga.

Jugando con ZBrush

Hacia tiempo que quería echarle un ojo a Zbrush.

Ako-puppet

A diferencia de programas como 3D Studio Max o Maya, el modelado de Zbrush se basa en un principio totalmente diferente. Se nota de lejos que es una herramienta creada para diseñadores. Modelar un objeto no es más que esculpir un cuerpo mediante pinceles.

El atractivo principal del programa, bajo mi punto de vista, es la facilidad para llevar a cabo el modelado. Sin embargo, se me está haciendo bastante duro de digerir, la interfaz es algo menos intuitiva a nivel de usuario que otros programas. Su funcionalidad se centra demasiado en shortcuts, ideal para  expertos, pero tedioso para los que empezamos.

En fin quien quiera saber más del tema: [Pixologic - Zbrush]

En cuanto al render que acompaña este post:

El monigote de trapo está modelado en zbrush, el texturizado, sombreado, iluminación y objetos varios en 3D Max. 

 

Detector de caras y algo de tiempo libre...

Mediante OPENCV y la pequeña clase que organicé en C++ para manipular el misil (ir), he creado un detector de caras de personas en Visual C++ para que de forma automática proporcione al misil la posición del objetivo.

Primero pondré algo de resultados visibles y luego pasaré a resumir un poco cómo se lo hice.

(download)

Lo que se ve ahí, es la salida del programa de OpenCV. El detector de caras tiene cierto margen de error, depende de si la cara está algo tapada o si la orientación de ésta es muy brusca puede no detectarla, sin embargo es bastante robusto, incluso con gafas y a una distancia considerable hace bingo.

En un principio es un vídeo nítido. Pero para darle un toque personal, le he distorsionado el histograma para alterar el contraste y la calidad, puesto algún texto sin sentido para adornar y como no, tintado de rojo al más puro estilo de un T-800.

El código para estos efectos no es muy complicado de entender (frame_copy es una imagen de la webcam en un instante de tiempo y frameSizeImg es una imagen png completamente roja):

// Effects
// Histogram / contrast
IplImage *out = cvCreateImage ( cvGetSize(frame_copy),IPL_DEPTH_8U,1);
cvConvertImage(frame_copy,out);
cvEqualizeHist(out,out);
                        
// Red filter (in Memoriam of Arnold)
cvConvertImage(out, frame_copy);
cvSetImageROI(frame_copy, cvRect(0,0,frame_copy->width,frame_copy->height));
cvAddWeighted(frame_copy, 1, frameSizeImg, 1, -100, frame_copy);
cvResetImageROI(frame_copy);

// Text
cvPutText (frame_copy,"ALL DEFENSE SYSTEMS",cvPoint(20,150), &font, cvScalar(255,255,255));
cvPutText (frame_copy,"ACTIVE STATUS 3583A",cvPoint(20,170), &font, cvScalar(255,255,255));
cvPutText (frame_copy,"I/O LEVELS AT MAX",cvPoint(20,180), &font, cvScalar(255,255,255));
cvPutText (frame_copy,"-------------------",cvPoint(20,186), &font, cvScalar(255,255,255));
cvPutText (frame_copy,actionStr,cvPoint(20,198), &font, cvScalar(255,255,255));
cvPutText (frame_copy,"DEVICE LOADED",cvPoint(20,220), &font, cvScalar(255,255,255));
cvPutText (frame_copy,"CHECKSUM: 4390",cvPoint(500,210), &font, cvScalar(255,255,255));
cvPutText (frame_copy,"SEARCH AND DESTROY MODE",cvPoint(426,240), &font, cvScalar(255,255,255));
cvPutText (frame_copy,"FULL ISO LEVELS",cvPoint(500,250), &font, cvScalar(255,255,255));
cvPutText (frame_copy,acquireStr,cvPoint(frame_copy->width/2 - 140,frame_copy->height - 40), &font2, cvScalar(255,255,255));

En cuanto a la detección de caras, me he basado en el método de Viola-Jones.

OpenCV automatiza el proceso de reconocimiento de caras hasta tal punto que basta con 4 o 5 líneas de código para poner en marcha algo estándar. Que a mi, por el momento, me vale.

El core del proyecto es esta linea:

CvSeq* faces = cvHaarDetectObjects( img, cascade, storage,
                                    2, 2, CV_HAAR_DO_CANNY_PRUNING,
                                    cvSize(40, 40) );

Donde img es una imagen de la cámara en el instante de la detección de la cara y cascade el objeto en el que se almacena la información de las HaarCascades del XML de las caras que se debe abrir previamente. El resto de parámetros son opciones para alterar la detección según se desee.

Una vez montados, por una parte, el detector de caras y, por la otra, el controlador del misil, hay que ponerlos a trabajar juntos. Task fácil. No tiene mucho misterio programando con objetos y clases. 

Video de regalo:

Control por libusb de un USB Missile Launcher

Siempre quise tener un arma de destrucción masiva en mi poder. Cómo imaginar que iban a ser mis compañeros de trabajo (Pere, Sergi) quienes me facilitaran el acceso a una de ellas.

El arma en cuestión:

Launcher

Bromas aparte y habiendo disparado ya unos cuantos misiles a la cabeza de alguien manualmente con el software que venía en la misma caja del lanza misiles, se me ha ocurrido una aplicación para éste.

Por qué no hacer que el lanza misiles auto-apunte a targets adquiridos por una webcam?. Suena bien. -Espera, esos targets serán humanos? Es obvio.

Para empezar se necesitará tener el control sobre el usb. Para ello, he utilizado la libreria libusb. A partir de ahí se pueden realizar llamadas a cualquier dispositivo usb que haya sido identificado previamente con su productID y su vendorID.

El sistema es bastante sencillo, primero se busca e instancia el dispositivo usb que tenga el productID y vendorID deseados:

for(bus = usb_get_busses(); bus; bus = bus->next)
{
        //para cada dispositivo del bus
        for(device = bus->devices; device; device = device->next)
        {
                //comprobar productID y vendorID
                if(device->descriptor.idVendor == 6465 &&
                        device->descriptor.idProduct == 32801)
                {
                        USBMissileLauncher *newLauncher = new USBMissileLauncher(device);
                        mMissileLaunchers.push_back(newLauncher);
                }
        }
}

 

Una vez esté instanciado el dispositivo, se pueden llamar las funciones de inicialización de libusb:

usb_init();
usb_find_busses();
usb_find_devices();

Con esto tenemos al lanza misiles listo para escuchar órdenes.

Navegando un poco por google, encontré el mapeo en hexadecimal de cada orden que utiliza la función usb_control_msg().

  • 0x1  / 0x2 : arriba / abajo.
  • 0x4 / 0x8: izquierda / derecha.
  • 0x10: disparo.
  • 0x0: stop.

Sin embargo, pese a que el movimiento es directo, para poder disparar he tenido que enviarle, antes, dos comandos que le sirven de "init" al lanza misiles. También son parámetros de la función usb_control_msg en formato de  buffer[8].

{'U', 'S', 'B', 'C', 0, 0, 4, 0}

{'U', 'S', 'B', 'C', 0, 64, 2, 0}

Una vez enviados, se interpreta el disparo correctamente.

Ahora que se puede controlar el usb a mi antojo por source, lo adaptaré a un algoritmo de visión por computador para que de en blanco sin necesidad de dirigirlo manualmente. Pero eso será cuando encuentre otro hueco en mi agenda.

Stay tunned!

PFC: OpenGL Simple Engine

Lo que empezó como un tutorial montado encima de otro acabó siendo el 75% de mi proyecto de final de carrera. El objetivo de éste era representar de forma fluida en un espacio 3D un IOM (Individual Oriented Model) basado en un banco de peces.

Para explicarlo de forma sencilla, un IOM no es más que un modelo matemático que simula el comportamiento de un gran conjunto de individuos a través de las acciones de éstos. El gran problema que se presenta con un IOM es la alta capacidad computacional necesaria para solventar la mayoría de problemas que representan. Es decir, pensad en un conjunto de 100.000 individuos, donde el comportamiento de cada individuo se calcula a través de las acciones de individuos cercanos (vecinos). Ya de por sí, es bastante complejo a nivel de cómputo. Bien, pues ahora añadidle el cálculo necesario para visualizar todo esto en un entorno 3D.

Así pues el desafío que planteaba el proyecto en sí, era diseñar un visualizador que respondiera adecuadamente. Es decir, se pudiera mostrar un conjunto entero de individuos a una tasa de frames (FPS) aceptable.

Peces

Como cumplí el objetivo del proyecto bastante antes del plazo de entrega, le di unas cuantas manos de pintura:

  • Le añadí un sistema de consola para poder configurar el entorno mediante variables. Muy al estilo quake, de hecho las variables, tenían su cierto estilo (r_fishDetail, r_showFPS, ...) que se le va a hacer, soy un nostálgico.
  • Cree un programa externo en entorno windows para configurar el entorno. Digamos que una consola para torpes.
  • Implementé algoritmos de colisiones, frustum culling y física simple a la cámara. Rediseñé la clase de movimiento para que se pudiera correr, saltar, caer y como no acelerar a base de strafe jumps.
  • Por último diseñé un lector de scripts que interpretaba shaders (efectos de textura) para después aplicarlas en el entorno.

Por ejemplo, strafe jump:

 

Los scripts de shaders estarían compuestos por estructuras como ésta, el engine se encarga de interpretarlos y ejecutarlos con las extensiones ARB de OpenGL:

sea1/flame1.jpg
{
        NUMERO_TEXTURAS 1
        TEXTURA1
        {
                NOMBRE flame1.jpg
                OPACIDAD 0.9
                BLENDFUNC 2 GL_SRC_ALPHA GL_ONE
                VELOCIDAD_SECUENCIA 0.1
                SECUENCIA 8 flame1.jpg#flame2.jpg#flame3.jpg#flame4.jpg#flame5.jpg#flame6.jpg#flame7.jpg#flame8.jpg
        }
}

 

En el siguiente video se puede ver un entorno renderizado con shaders y posteriormente sin ellas:

 

Algunas pinceladas con el ratón...

Desde que me dejaron probar la trial de 3D Studio 4 (anterior a la serie MAX), he ido modelando y animando como he podido. No he asistido a ningún curso, aprendí en dificultad hardcore, es decir, coges el software, y lo pruebas una y otra vez. Luego más tarde cuando conseguí conectarme a la red pude seguir tutoriales. Aún recuerdo como iba con cajas de diskettes a la universidad para grabarme de todo: archivos, modelos, texturas..., en fin que tiempos.

Bueno a lo que iba, partiendo de la base que esto lo tomo como un hobby, he conseguido juntar 3 o 4 imágenes de renders que tenía tiradas por algún cd de datos. Es una lástima que la mayoría se hallan perdido.

(download)

Son ejemplos de modelado en low-poly, post-producción, texturización y cell-shading en MAX.