jueves, 19 de diciembre de 2013

Avance FaceAPI y Unity3D

Avance FaceAPI y Unity3D

Luego de enviar múltiples mails a los diferentes desarrolladores sin respuesta se continuó con las pruebas de integración de FaceAPI con Unity3D en dos nuevos ambientes de trabajo. Esta vez se utilizaron una netbook con Windows 7 32 bits y una MacBook Pro. En el caso de la MacBook, se instaló una VirtualBox para poder emular el sistema operativo Windows XP.

Al descargar y probar las primeras versiones del 6dofstreamer, ambas PCs encontraron errores al correr el programa. Luego de un poco de investigación, se comprobó que era un problema de archivos .dll faltantes. Para encontrar cuales eran los archivos faltantes, se utilizó un programa de uso gratuito Dependency Walker. El funcionamiento de la aplicación es muy sencillo, simplemente se arrastra el archivo ejecutable del programa que se desea a la ventana del Dependency Walker y este devuelve una lista detallada de los archivos que el programa utiliza, marcando con un color los faltantes. Se procedió a descargar los .dll faltantes en cada equipo. En el caso de la Macbook con Windows XP también se tuvo que descargar el paquete Microsoft Visual C++ 2008 Redistributable Package (x86).

Al ejecutar las primeras versiones del streamer, ambos equipos volvieron a registrar el error “ API error code -13”. Se pasó a probar con la versión corregida para Windows 7 64 bits. Nuevamente se obtuvieron archivos dll faltantes que hubo que buscar y descargar.

A considerar: Ambos ambientes de trabajo tienen sus contras. En el caso de la netbook, el procesador no es el más apto para ejecutar una aplicación como Unity3D, pero es la única máquina a la que tuve acceso con este sistema operativo (se había intentado con otra que tuvo resultados positivos en cuanto a la comunicación pero cuya cámara no era compatible con FaceAPI). En el caso de la MacBook con Windows XP, al estar trabajando en una máquina virtual los tiempos de reacción eran muy lentos.

La MacBook con Windows XP tuvo resultados negativos, al correr la aplicación se cerraba con un error no definido: “An exception ocurred. Exception Nr.”.

La netbook con Windows 32 bits tuvo resultados positivos, sin indicios de problemas en la comunicación UDP. Se prosiguió a descargar Unity3D y a probar el script de prueba. Los resultados fueron positivos, se logró interactuar con el escenario virtual. Dado que la PC está muy exigida para usar un programa que grabe el escritorio, se analizará con el tutor la posibilidad de registrar los resultados de otra manera.

Finalmente están listas ambas partes del proyecto, solamente falta integrar la interacción al proyecto de prueba de las cuatro cámaras.Esto se logra simplemente poniendole el script de face tracking al objeto Player del mundo virtual que creamos en la publicacion anterior.

Les dejo un video demo de como quedo:




Links de interés:

Integracion Unity3D con FaceAPI

Integración de FaceAPI con Unity 3D

Finalmente se pudo hacer funcionar el 6dofstreamer, la aplicación open source que interactúa con FaceAPI para poder detectar caras utilizando una webcam.

Las pruebas en una PC con Windows 7 64bits fueron exitosas, la aplicación detectó correctamente la cara en diferentes posiciones e imprimió las coordenadas en la consola.


Imagen 1: Toma frontal distante, a la izquierda la consola haciendo un stream con las coordenadas.



Imágenes 2 y 3: Tomas frontales.



Imagen 4: Toma de perfil.



Luego se creó un proyecto en Unity muy simple para probar la detección de caras en está plataforma. El proyecto simplemente contiene un plano con un cilindro y una cámara que representa la vista del jugador. La cámara contiene un script que recibe la información del streamer y utiliza las coordenadas que este le envía para moverse. La transferencia de datos se realiza mediante sockets.


Imagen 5: Screenshot del proyecto en Unity.

Script:

using UnityEngine;
using System.Collections;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
public class script: MonoBehaviour {
/*Creacion del cliente UDP que escucha al puerto 29129*/
private const int listenPort=29129;
UdpClient listener;
       IPEndPoint groupEP;
float pz,ry,ry_old=0;
Vector3 newpos;
void Start() {
listener = new UdpClient(listenPort);
groupEP = new IPEndPoint(IPAddress.Any, listenPort);
}

/*Funcion de actualizacion continua de la posicion de la camara*/
void Update () {
         byte[] receive_byte_array = listener.Receive(ref groupEP);
 string op=Encoding.ASCII.GetString(receive_byte_array, 0, receive_byte_array.Length);
string[] words=op.Split(' ');
pz=(float)Convert.ToDouble(words[3]);
ry=(float)Convert.ToDouble(words[4]);
if(pz<=-0.5 && pz>-0.4)
pz=-50;
else if(pz<=-0.4 && pz>-0.3)
pz=-40;
else if(pz<=-0.3 && pz>-0.2)
pz=-30;
else if(pz<=-0.2 && pz>-0.1)
pz=-20;
else if(pz<=-0.1 && pz>0)
pz=-10;
else if(pz<=-0 && pz>=0)
pz=0;
else if(pz>0 && pz<=0.1)
pz=10;
else if(pz>0.1 && pz<=0.2)
pz=20;
else if(pz>0.2 && pz<=0.3)
pz=30;
else if(pz>0.3 && pz<=0.4)
pz=40;
else if(pz>0.4 && pz<=0.5)
pz=50;
pz=-pz;
if(ry<=-0.5 && ry>-0.4)
ry=-50;
else if(ry<=-0.4 && ry>-0.3)
ry=-40;
else if(ry<=-0.3 && ry>-0.2)
ry=-30;
else if(ry<=-0.2 && ry>-0.1)
ry=-20;
else if(ry<=-0.1 && ry>0)
ry=-10;
else if(ry<=-0 && ry>=0)
ry=0;
else if(ry>0 && ry<=0.1)
ry=10;
else if(ry>0.1 && ry<=0.2)
ry=20;
else if(ry>0.2 && ry<=0.3)
ry=30;
else if(ry>0.3 && ry<=0.4)
ry=40;
else if(ry>0.4 && ry<=0.5)
ry=50;
if(ry_old!=0)
{
newpos.y=ry_old-ry;
}
ry_old=ry;
/*Cambiar la posicion de la camara*/
transform.Rotate(newpos);
gameObject.transform.position = new Vector3(
     gameObject.transform.position.x,
     gameObject.transform.position.y,pz);
}
}



A modo de resumen para entender la prueba, en pasos:

  1. Se corre el archivo del 6dofstreamer socket.exe, que genera un stream de datos de la posición de la cara usando FaceAPI.
  2. Transmite los datos en el puerto 29128 usando UDP.
  3. Desde Unity, la cámara contiene el script que crea un cliente UDP, escucha al puerto 29129 y toma los datos de la posición de la cara en los ejes x, y y z.
  4. Se pasan los datos a la escala necesaria para que coincidan con la pantalla de la aplicación de Unity (usando un script opensource).
  5. Se utiliza la función de Update() para que la posición de la cámara se actualice continuamente.

Al correr el streamer y el proyecto de Unity al mismo tiempo, se detectó un error. Si bien el streamer detecta la cara y muestra las coordenadas en la consola, la aplicación de Unity no respondió. Tras un análisis más detallado, se notó que al abrir el streamer se puede leer el mensaje de error: “Connection closing...Send failed 10093”. Esto da la idea de que hay un problema en la transmisión de datos con sockets.
Para asegurar que no era una cuestión de la PC, se configuró el ambiente en otra PC con Windows 7 64bits. Los resultados fueron los mismos que los anteriores.
Se enviaron mails al desarrollador del streamer y al desarrollador del script que parsea los datos a Unity para consultar posibles soluciones al problema. Al mismo tiempo se buscaron casos de usuarios que hayan tenido el mismo problema.  El primero respondió que podía correrlo sin problema, y que quizás la solución es probarlo en otras PCs. El segundo sugirió correr un script de C# que imprima en consola los datos que recibe del puerto UDP 29129 de la siguiente manera:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
UdpClient udp = new UdpClient(29129);
IPEndPoint ipe = new IPEndPoint(IPAddress.Any, 29129);
while (true)
{
byte[] rcv = udp.Receive(ref ipe);
Console.WriteLine(Encoding.ASCII.GetString(rcv));
}
}
}
}

Al correr el script, no se imprimió nada en la consola, por lo que se puede concluir que el problema está en la comunicación de UDP.

Se continuará investigando para encontrar una solución al problema de comunicación. A su vez, se intentará configurar el ambiente en una MacBook para ver los resultados. En el caso de no poder avanzar más por este camino, se investigarán otras posibilidades como el uso de OpenCvSharp.

Face tracking en Unity3D

Face Tracking en Unity3D

La idea es implementar un algoritmo de Face Tracking para que el usuario pueda interactuar con el programa sin necesidad de utilizar dispositivos periféricos como el teclado o mouse. El software detecta características de la cara como los ojos y la nariz, y en base a su tamaño calcula la distancia entre el usuario y la cámara.

El primer problema es que Unity no soporta ningún tipo de detección de movimientos de manera nativa, por lo que hubo que investigar la mejor manera de integrar la funcionalidad a la plataforma.

Hay diferentes opciones para integrar face tracking a Unity y ninguna está estandarizada, por lo que la investigación se basó en buscar proyectos de desarrolladores independientes y recolectar información sobre sus experiencias. Finalmente se seleccionaron 3 opciones para empezar:

  1. Integración de Unity con OpenCV utilizando EmguCV
  2. Utilización de la librería FaceAPI junto con 6dofstreamer
  3. Headtracking utilizando un Wiimote



1. EmguCV es una librería para utilizar las funciones de OpenCV con .NET. Existen varios proyectos hechos utilizando la librería. Esta opción fue descartada por problemas de compatibilidad con el sistema operativo. Los desarrolladores que la utilizaron afirmaron que por más que las funciones de OpenCV son fáciles de entender y usar, tuvieron muchos problemas para lograr correr correctamente sus aplicaciones en Windows. Sin embargo, de no encontrar una mejor solución cabe la posibilidad de intentar implementar este enfoque.

2. FaceAPI es una librería específica de Face Tracking. 6dofstreamer es un proyecto opensource que utiliza FaceAPI para lograr face tracking con 6 grados de libertad utilizando una webcam. Los datos se exportan utilizando sockets. Tras profundizar en la opción, la mayoría de los usuarios en los foros de Unity recomiendan esta combinación como la mejor manera de integrar face tracking a Unity sin tener que invertir en hardware como Kinect o Wii.

3. Existe un proyecto muy interesante que utiliza un Wiimote de Nintendo para integrar head tracking con Unity. Sin embargo los desarrolladores no tienen ningún tipo de documentación  ni demos de código. Es una opción interesante pero requiere una investigación más profunda.

Se eligió para avanzar con el proyecto la opción 2, dejando las otras dos opciones como alternativas a analizar con más profundidad si la opción elegida fracasa.
Primero se creó un proyecto en Unity con un script que utiliza los datos del streamer para manejar la interacción con el usuario dentro de la aplicación.
Luego se instaló la versión no comercial de la librería FaceAPI. La PC utilizada tiene Windows 7 de 32 bits. Se procedió a ejecutar el 6dofstreamer, para asegurar el correcto funcionamiento de la detección de caras. El programa se ejecuto correctamente y la webcam se encendió, pero en la pantalla solamente se vio una pantalla negra. Probablemente haya habido una incompatibilidad con el modelo de webcam.
Se intentó nuevamente con otra PC, está vez con Windows 7 de 64 bits. Esta vez el streamer no se ejecutó correctamente, obteniendo un error “ API error code -13”.
Se volvió a intentar con otras PCs con el mismo sistema operativo que el anterior, y nuevamente se obtuvo el mismo error. De estas pruebas se dedujo que el 6dofstremer no es compatible con las nuevas versiones de Windows 64 bits.
Se procedió a investigar más en profundidad este error. Resulta que el streamer fue desarrollado en 2010, por lo que todos los usuarios de Windows 7 64 bits obtuvieron el mismo error. Eventualmente se llegó a una posible solución, un desarrollador se dedicó a corregir el streamer para que fuera compatible con Windows 64 bits y Mac, y al parecer varios usuarios pudieron correrlo de manera exitosa.
El problema en este caso fue que el fix fue desarrollado en 2011, y todos los links al proyecto están rotos. El último recurso antes de abandonar esta opción  fue enviar mails al desarrollador y a otros usuarios que tampoco encontraron un link funcional, para averiguar si alguno pudo encontrar el archivo del fix.
Obtuve un mail de un usuario que prometió buscar el archivo en su PC, pero no dio resultado. Finalmente en el día de la fecha (2/09/2013), obtuve un mail del desarrollador del fix con el link actualizado. Las primeras pruebas de detección de caras fueron exitosas.
Se procederá entonces a avanzar con esta opción. El próximo paso es lograr la integración con Unity.

Fuentes:
https://code.google.com/p/6dofstreamer/

Configuración de pantallas múltiples en Unity3D

Configuración paso a paso
A continuación se describe el proceso de configuración de los múltiples monitores en una aplicación de Unity paso a paso.

  1. Creación de un escenario y un jugador conteniendo 4 camaras con Unity3D.
Primero se crea una escena con un plano y un objeto. Ambos están prefabricados y simplemente se colocan en la escena. En el caso del auto, fue descargado del Asset Store de Unity, de donde se pueden descargar objetos prefabricados de numerosas librerías gratuitas.
Imagen 3: Objeto Player en el plano visto desde arriba.


  1. Creación de objeto vacío Cameras
Se crea un objeto Cameras y se le asignan 4 objetos Camera: cámara izquierda, cámara derecha, cámara principal y cámara trasera en distintos ángulos.
Imagen 4: Objeto Cameras con 4 objetos Camera asignados.


  1. Creación de script que asigna el objeto Cameras al Player.
Unity trabaja con C# o JavaScript, para la aplicación se eligió C#. El Script se le asigna al objeto Player.
Se crea un script CameraScript.cs y se crean las variables a utilizar para el manejo de las cámaras.

public class CameraScript : MonoBehaviour
{
public Camera camaraIzquierda;
public Camera camaraFrontal;
public Camera camaraDerecha;
public Camera camaraTrasera;
private GameObject myCamera;
private Transform cameraHeadTransform;

La variable myCamera es del tipo GameObject, la clase base de todas las entidades en Unity. Se utiliza para manejar al objeto Cameras. La variable cameraHeadTransform es del tipo  Transform, esta clase maneja los atributos de posición, rotación y escala de un objeto o GameObject. Se utiliza para guardar la posición de la Camera de Player.

Luego, se asigna el objeto Cameras a la variable myCamera y la Camera de Player (cuyo nombre es “CameraHead”) a la variable cameraHeadTransform en el método de inicialización Start().
void Start ()
{
myCamera = GameObject.Find ("Cameras");
cameraHeadTransform = transform.FindChild ("CamaraHead");
}

Este método es llamado una sola vez antes de que el método Update() sea llamado por primera vez. El método Update() en cambio, se llama una vez por cada frame cuando la aplicación está corriendo.
A continuación le asignamos al objeto Cameras la posición de la cámara del Player y a cada una de las cámaras contenidas en Cameras la rotación de la cámara del mismo. El resultado es que  la posición de las cámaras se actualizará y seguirá al Player cada vez que se actualice un frame.

// Update is called once per frame
void Update ()
{
myCamera.transform.position = cameraHeadTransform.position;
foreach (Transform child in myCamera.transform) {
child.transform.rotation = cameraHeadTransform.rotation;
}

  1. Cálculo de los campos de visión de cada cámara.
Dado que se representará la visión del usuario dividida en cuatro partes (visión frontal, trasera, a izquierda y derecha) debe ajustarse el campo de visión horizontal de las cámaras ya que normalmente están configuradas suponiendo que el usuario utiliza una sola a la vez. Esto se traduce en disminuir la variable del campo de visión de cada objeto Camera, quitándoles parte de la vista periférica para que no se superpongan entre ellas.

El cálculo para determinar el valor de ajuste del campo de visión horizontal (horizontal FOV) se puede obtener si se conoce el campo de visión vertical. Se calcula utilizando la siguiente fórmula:


Donde H es el campo de visión horizontal, V es el campo de visión vertical, w es el ancho de la pantalla y h la altura de la misma. El equivalente en el script es:

float fov=2f * Mathf.Rad2Deg * Mathf.Atan(Mathf.Tan(.5f * Mathf.Deg2Rad * 90) / camaraDerecha.aspect);

El atributo aspect de una Camera es el equivalente a calcular los datos de la pantalla en la fórmula. Luego simplemente se asigna el valor del campo de visión horizontal resultante a cada cámara.
camaraDerecha.fieldOfView=fov;
camaraIzquierda.fieldOfView=fov;
camaraFrontal.fieldOfView=fov;
camaraTrasera.fieldOfView=fov;

  1. Asignar ángulos de visión diferentes a cada cámara.
Por más que cada cámara ya tiene un ángulo diferente, se debe asegurar que estén bien alineadas. Ya que son 4 cámaras, la variación en la rotación de cada cámara será cada 90º.

Figura 1: Esquema del campo de visión de una cámara para una aplicación con 4 cámaras.

Se asignan entonces los ángulos a cada cámara.

camaraFrontal.transform.eulerAngles = new Vector3 (0
, camaraFrontal.transform.eulerAngles.y + 315, 0);
camaraIzquierda.transform.eulerAngles = new Vector3 (0
, camaraIzquierda.transform.eulerAngles.y + 225,0);
camaraDerecha.transform.eulerAngles = new Vector3 (0
, camaraDerecha.transform.eulerAngles.y + 45, 0);
camaraTrasera.transform.eulerAngles = new Vector3 (0
, camaraTrasera.transform.eulerAngles.y + 135, 0);

El resultado de los últimos dos pasos se puede observar en la siguientes imagen.

Imagen 5: Objeto Cameras con las 4 cámaras configuradas correctamente.


  1. División de pantalla en 4 subpantallas utilizando Normalized ViewPort Rectangles.
En este último paso se modifican los valores del ancho y de la posición en X de cada cámara en la pantalla.
Se modifica el ancho de cada cámara para ajustarlo a 1/4 del espacio de la pantalla principal, por lo que sus valores en W serán de 0.25. Finalmente se modifica la posición en X de cada cámara para que queden alineadas una al lado de la otra en la pantalla. Es decir, la cámara izquierda tendrá la posición 0, la principal 0.25, la derecha 0.5 y la trasera 0.75.

El resultado final de configuración de cada cámara se puede ver reflejado en las siguientes imágenes.

Imagen 6: Vista de la cámara principal y sus atributos.

Imagen 7: Vista de la cámara trasera y sus atributos.

Imagen 8: Vista de la cámara izquierda y sus atributos.

Imagen 9: Vista de la cámara derecha y sus atributos.

La imagen final de la aplicación corriendo muestra las cuatro cámaras en simultáneo.




De izquierda a derecha: cámara izquierda, cámara frontal, cámara derecha, cámara trasera



Fuentes: