API de OpenNI para HMC
API de OpenNI para HMC
La librería OpenNI proporciona una serie de funciones específicamente pensadas para extraer la pose de una persona. El núcleo de estas funciones, como se ha dicho, lo constituye el algoritmo de Shotton et al., con lo que la extracción de pose de OpenNI no precisa marcadores, y tampoco usa un modelo de la persona. La codificación de la pose consiste en una lista de centroides 3D, cada cual correspondiente a un punto de interés en la persona (articulaciones, efectores finales, etc).
A continuación, se detallan las clases más relevantes que el usuario debe manejar para extraer la pose 3D de una persona a partir de los mapas de profundidad que ofrece el sensor Kinect. Para una API completa, el alumno debe visitar el siguiente enlace. En cualquier caso, para facilitar la búsqueda de información, en cada clase listada se proporciona un enlace directo a su sección en la API de OpenNI (basta con pinchar en el nombre).
- Clase Context.
El contexto (Context) es el espacio de trabajo donde la aplicación OpenNI crea el grafo de producción. El contexto se puede entender como una partición del disco, o un directorio, donde OpenNI almacena todo lo que necesita. Para usar cualquier aplicación OpenNI, es necesario crear e inicializar un contexto. Por ejemplo, con la siguiente instrucción:
rc = g_context.InitFromXmlFile(SAMPLE_XML_PATH, g_scriptNode, &errors);
se crea un contexto a partir de la información de configuración almacenada en el fichero .xml proporcionado.
- Clase UserGenerator (nodo).
Un nodo de este tipo genera datos relacionados con los usuarios identificados en la escena. Cada usuario se describe independientemente, lo que permite realizar acciones específicas sobre diferentes usuarios.
En principio, cualquier objeto que se mueva delante del sensor Kinect es, potencialmente, un usuario. La librería OpenNI, por defecto, asume que el usuario es humano, así que, además de moverse, el objeto ha de tener un tamaño mínimo para ser procesado como usuario potencial. Cada usuario potencial (cada objeto de cierto tamaño que se mueve delante del sensor Kinect) recibe un identificador (User ID) único. El nodo UserGenerator usa estos identificadores para acceder a datos específicos de cada usuario a través de sus diferentes métodos. Por ejemplo:
xn::UserGenerator::GetUserPixels() Proporciona los píxeles correspondientes a cada usuario (básicamente devuelve un mapa de píxeles donde los píxeles de un usuario toman el valor User ID, mientras que los que no pertenecen a ningún usuario toman el valor 0).
xn::UserGenerator::GetCoM() Proporciona el centro de masas de un usuario, en 3D.
Un nodo UserGenerator se puede implementar de varias maneras, aunque la implementación típica usa la información del mapa de profundidad para extraer información sobre el usuario (usando el algoritmo de Shotton et al., fundamentalmente). Es por ello que el nodo UserGenerator suele depender del nodo DepthGenerator.
Además de utilizar sus métodos para acceder a diferentes informaciones sobre los usuarios, como se ha visto, el nodo UserGenerator también se encarga de administrar una serie de funciones de retrollamada (más conocidas por el término inglés: Callback Functions) que se ejecutan asíncronamente como respuesta a eventos relacionados con los usuarios. Entre los eventos más comunes se encuentran:
- Detección de nuevo usuario ('New User').
- Un usuario detectado sale temporalmente de la escena ('User Exit').
- Un usuario detectado vuelve a la escena ('User Reenter').
- Un usuario detectado se pierde definitivamente ('User Lost').
Como ejemplo, la siguiente instrucción registra las funciones User_NewUser y User_LostUser (definidas por el usuario) como funciones Callback para los eventos 'NewUser' y 'LostUser' del nodo g_UserGenerator. nRetVal es un booleano que indica si la operación ha ido bien o no.
nRetVal = g_UserGenerator.RegisterUserCallbacks(User_NewUser, User_LostUser, NULL, hUserCallbacks);
- Clase SkeletonCapability.
Esta clase proporciona a un nodo UserGenerator la capacidad de seguir la pose de un usuario mediante una representación esqueletal. Cada clase SkeletonCapability está asociada a un único nodo UserGenerator, y cada nodo UserGenerator puede tener como mucho una representación esqueletal (una clase SkeletonCapability).
Antes de usar las funciones de esta clase para un nodo UserGenerator, conviene comprobar que el nodo, efectivamente, tiene la capacidad de realizar representaciones esqueletales. Ésto se hace mediante la siguiente llamada:
XnBool xn::ProductionNode::IsCapabilitySupported(XN_CAPABILITY_SKELETON); (el UserGenerator es hijo de ProductionNode y, por tanto, hereda la función IsCapabilitySupported).
En caso de que el nodo lo soporte, se puede acceder a la clase SkeletonCapability mediante la función:
SkeletonCapability xn::UserGenerator::GetSkeletonCap();
- Clase PoseDetectionCapability.
Esta clase provee al nodo UserGenerator con la capacidad de detectar cuándo el usuario se coloca en una cierta pose. Cada clase PoseDetectionCapability está asociada a un único nodo UserGenerator, y cada nodo UserGenerator puede tener asociado como mucho una única clase PoseDetectionCapability.
Al igual que con la clase SkeletonCapability, antes de usar esta clase hay que comprobar que el nodo UserGenerator tiene esa capacidad meidante la siguiente llamada:
XnBool xn::ProductionNode::IsCapabilitySupported(XN_CAPABILITY_POSE_DETECTION);
Por lo demás, esta clase se limita a producir un evento ('Pose Detected') cuando percibe que el usuario se ha colocado en la pose definida. En principio, el programador se encarga de definir esta pose. En la práctica, hasta hace poco sólo existía una pose posible que pudiese cargarse en esta clase (llamada "Psi"), que se usaba para arrancar la detección esqueletal en la Kinect©.
La siguiente llamada es la que se usa para hacer que comience la búsqueda de la pose "Psi" en el usuario nID, contenido en el nodo g_UserGenerator:
gUserGenerator.GetPoseDetectionCap().StartPoseDetection("Psi",nID);
Y ésto es un ejemplo de función Callback para el evento 'Pose Detected'. En este caso, la función se limita a detener la búsqueda de pose (pues ya se ha encontrado la pose) y a informar al usuario con un mensaje por consola:
void XN_CALLBACK_TYPE Pose_Detected(xn::PoseDetectionCapability& pose, const XnChar* strPose, XnUserID nID, void* pCookie) {
printf("Pose %s for user %d\n", strPose, nID);g_UserGenerator.GetPoseDetectionCap().StopPoseDetection(nID);
}
Hasta hace poco, la detección esqueletal en la librería OpenNI requería que el usuario adoptase esta pose. En las últimas versiones de la librería ya no es necesario hacer ésto, y la pose puede extraerse de la clase SkeletonCapability sin necesidad de colocarse previamente en ninguna pose determinada.
Pose "Psi" usada para inicializar la Kinect©.
En el siguiente programa de ejemplo se muestra en detalle cómo utilizar estas clases en un aplicación básica.