Reconocimiento de posturas con Kinect IV
Continuamos con la serie sobre reconocimiento de posturas con el sensor Kinect de Microsoft. En esta ocasión voy a comentar las clases encargadas de normalizar la posición del cuerpo de la forma comentada en el primer artículo, de manera que obtengamos una serie de datos que constituyen una versión simplificada del esqueleto más apropiada para su análisis con técnicas de reconocimiento de patrones.
En este enlace puedes acceder al primer artículo de esta serie. En este otro puedes descargar el código fuente de la solución KinectGestures, escrita en csharp con Visual Studio 2015.
Las clases encargadas de esta normalización se encuentran en el ensamblado KinectInterface, en el espacio de nombres KinectInterface.Normalization. Existen clases para normalizar los antebrazos, los brazos, las espinillas, las piernas y el torso. Además existe una clase que realiza una normalización de todo el esqueleto completo, haciendo uso de todas estas clases que se encargan de partes del cuerpo separadas.
El resultado, como expliqué en el primer artículo, consiste en uno o más valores que representan los ángulos que forman los diferentes huesos. En el caso del antebrazo, solo existe un ángulo, el que forma el antebrazo con el brazo. En el caso del brazo, que comprende también el hombro, se proporcionan tres ángulos, ya que el brazo puede girar libremente en un círculo alrededor del hombro y además estar más o menos inclinado con respecto al hombro.
En total, para un esqueleto completo, se obtienen 20 valores diferentes para cada posición. Cuatro valores corresponden al torso, tres a cada brazo y pierna, y uno a cada antebrazo y espinilla. La posición de la cabeza, los pies y las manos no es tenida en cuenta para la normalización.
La clase NormalizerBase
La clase NormalizerBase es la clase base abstracta para todas las demás. Consta de una variable _bones, un array de estructuras de tipo BodyPoint, y de un identificador opcional _id, de tipo string. Las propiedades que expone son las siguientes:
public BodyPoint[] NormalizedBones { get; }
public abstract int ZeroIndex { get; }
public string NormalizedPosition { get; protected set; }
protected abstract Vector3D Zero { get; }
protected abstract BodyPart Part { get; }
Con NormalizedBones se obtiene la versión normalizada, es decir, alineada con los ejes de coordenadas, de las articulaciones que utiliza la clase para calcular los ángulos. ZeroIndex representa el índice de la articulación que se utiliza como centro de rotación, es decir, como origen de coordenadas. NormalizedPosition es la cadena con la posición normalizada, una serie de valores enteros separados por punto y coma. Zero y Part se utilizan internamente para indicar las coordenadas de la articulación que se encuentra en el origen y determinar de qué parte del cuerpo se encarga la clase respectivamente.
Todo el proceso de normalización se realiza en el momento de creación de la instancia de la clase, en el constructor, mediante el método Normalize, que deben implementar obligatoriamente las clases derivadas:
public NormalizerBase(BodyVector bones, string id)
{
BodyPoint[] part = bones.GetBodyPart(Part);
_bones = new BodyPoint[part.Length];
_id = id;
part.CopyTo(_bones, 0);
Normalize();
}
El método NormalizedAngle es el que se encarga de devolver el valor correspondiente a un determinado ángulo representado por el valor del seno y del coseno. El círculo completo se divide en 20 sectores, por lo que los valores devueltos varían entre 1 y 20. El resto de métodos se utilizan para realizar rotaciones, inversiones y desplazamientos de las articulaciones.
La clase ForearmNormalizer
Como ejemplo de implementación de una clase de normalización, vamos a ver la clase ForearmNormalizer, que tiene dos subclases derivadas, una para el antebrazo derecho y otra para el izquierdo. Esta clase utiliza las articulaciones de la muñeca, el codo y el hombro para determinar el ángulo que forma el antebrazo con el brazo. El punto que se utiliza como centro de coordenadas es el codo.
El método dónde se realiza todo el trabajo es Normalize, que en este caso está implementado de la siguiente manera:
protected override void Normalize()
{
base.Normalize();
Vector3D prj = _bones[0].Vector.XYNormalizedProjection;
RotateZCC(prj.Y, prj.X);
prj = _bones[0].Vector.ZYNormalizedProjection;
RotateXCC(prj.Z, prj.Y);
prj = _bones[2].Vector.XYNormalizedProjection;
RotateZCC(prj.Y, prj.X);
prj = _bones[2].Vector.ZYNormalizedProjection;
NormalizedPosition = _id + NormalizedAngle(prj.Y, prj.Z);
}
Debemos llamar al método de la clase base en primer lugar para desplazar las articulaciones al origen de coordenadas. A partir de aquí, se trata de rotar los huesos de manera que los dejemos alineados siempre sobre el mismo plano para calcular el ángulo que forman.
Usando los métodos de la clase Vector obtenemos el seno y el coseno que forma el hueso que vamos a usar para girar todo el conjunto, mediante el método UVNormalizedPosition, donde U y V son los ejes de coordenadas que forman el plano. A continuación, usaremos el método RotateA.., donde A representa el eje de giro y .. puede ser CC para un giro contra reloj o TC para un giro a favor del reloj, para girar alrededor del otro eje el ángulo determinado por el seno y el coseno.
Finalmente, asignamos a la propiedad NormalizedPosition el ángulo o ángulos resultantes de la operación.
La clase NormalizedBody
La clase NormalizedBody es una clase auxiliar que nos permite realizar la normalización de todas las partes del cuerpo de una sola vez. Además de proporcionarnos propiedades con la versión normalizada de cada una de las partes por separado, ofrece la totalidad del cuerpo normalizado en la propiedad CompleteBody.
Este es el procedimiento completo de normalización del cuerpo, ejecutado directamente en el constructor de la clase:
public NormalizedBody(BodyVector bv)
{
ForearmNormalizer normbf = new LeftForearmNormalizer(bv, "");
string s = normbf.NormalizedPosition;
LeftForearm = s;
normbf = new RightForearmNormalizer(bv, "");
s += ";" + normbf.NormalizedPosition;
RightForearm = normbf.NormalizedPosition;
ArmNormalizer normba = new LeftArmNormalizer(bv, "");
s += ";" + normba.NormalizedPosition;
LeftArm = normba.NormalizedPosition;
normba = new RightArmNormalizer(bv, "");
s += ";" + normba.NormalizedPosition;
RightArm = normba.NormalizedPosition;
ShinNormalizer normbs = new LeftShinNormalizer(bv, "");
s += ";" + normbs.NormalizedPosition;
LeftShin = normbs.NormalizedPosition;
normbs = new RightShinNormalizer(bv, "");
s += ";" + normbs.NormalizedPosition;
RightShin = normbs.NormalizedPosition;
LegNormalizer normbl = new LeftLegNormalizer(bv, "");
s += ";" + normbl.NormalizedPosition;
LeftLeg = normbl.NormalizedPosition;
normbl = new RightLegNormalizer(bv, "");
s += ";" + normbl.NormalizedPosition;
RightLeg = normbl.NormalizedPosition;
TorsoNormalizer normbt = new TorsoNormalizer(bv, "");
s += ";" + normbt.NormalizedPosition;
Torso = normbt.NormalizedPosition;
CompleteBody = s;
}
Y esto es todo en lo referente a la normalización. En el próximo artículo terminaré está serie mostrando la implementación del programa principal, que utiliza todas estas clases para identificar la posición de los antebrazos del usuario.