Buenas,
en el post anterior comenté alguna de las características principales para comenzar a trabajar con el kit de reconocimiento de gestos que se incluye con el Kinect SDK.
Hoy veremos como utilizar esta funcionalidad en un proyecto WPF. Para esto seguiremos los siguientes pasos:
1. Crearemos un nuevo proyecto WPF. En mi caso lo he llamado [ElBruno.FaceTracking02] y ademas he agregado la referencia a [Microsoft.Kinect].
2. Desde la carpeta de instalación del Kinect SDK Developer Toolkit, agregaremos las referencias a los ensamblados de Face Tracking. La ubicación es similar a:
C:\Program Files\Microsoft SDKs\Kinect\Developer Toolkit v1.5.2\Samples\bin
y se deben agregar las referencias a
- Microsoft.Kinect.Tookit
- Microsoft.Kinect.Tookit.FaceTracking
3. Cambiamos la configuración del proyecto para que compile en modo x86. En las propiedades del proyecto, seleccionamos la seccion “Build” y en Platform Target seleccionamos “x86”, y compilamos el proyecto.
4. Este paso es un poco “curioso” pero en el próximo post pondré una forma más elegante de hacerlo. Desde la carpeta
C:\Program Files\Microsoft SDKs\Kinect\Developer Toolkit v1.5.2\Redist\x86
copiamos los ensamblados
- FaceTrackData.dll
- FaceTrackLib.dll
hacia el directorio de compilación de nuestro proyecto.
5. Agregamos un nuevo UserControl en nuestro proyecto llamado [FaceTrackingViewer] y una clase llamada [SkeletonFaceTracker]. Como tienen bastante código dentro, lo mejor es bajar el zip de ejemplo y copiarlo completo.
6. La clase [SkeletonFaceTracker], el la encargada de utilizando los ensamblados del Toolkit, de reconcer y luego pintar los puntos de la cara. Para esto se suscribe al evento [OnFrameReady] del sensor Kinect y en el mismo utiliza el Toolkit para obtener los puntos de la cara. Mejor ver un poco el código que es bastante autoexplicativo:
1: internal void OnFrameReady(KinectSensor kinectSensor, ColorImageFormat colorImageFormat,
2: byte[] colorImage, DepthImageFormat depthImageFormat,
3: short[] depthImage, Skeleton skeletonOfInterest)
4: {
5: _skeletonTrackingState = skeletonOfInterest.TrackingState;
6: if (_skeletonTrackingState != SkeletonTrackingState.Tracked)
7: {
8: return;
9: }
10:
11: if (_faceTracker == null)
12: {
13: try
14: {
15: _faceTracker = new FaceTracker(kinectSensor);
16: }
17: catch (InvalidOperationException)
18: {
19: Debug.WriteLine("AllFramesReady - creating a new FaceTracker threw an InvalidOperationException");
20: _faceTracker = null;
21: }
22: }
23:
24: if (_faceTracker == null) return;
25: var frame = _faceTracker.Track(colorImageFormat, colorImage, depthImageFormat, depthImage, skeletonOfInterest);
26: LastFaceTrackSucceeded = frame.TrackSuccessful;
27: if (!LastFaceTrackSucceeded) return;
28: if (_faceTriangles == null)
29: {
30: _faceTriangles = frame.GetTriangles();
31: }
32: _facePoints = frame.GetProjected3DShape();
33: }
7. Esta clase posee otra función que es la que se encarga de dibujar la máscara sobre un canvas, donde están los puntos de la cara. Apartir de la colección de puntos, se crea un array de triángulos y se los pinta sobre un contexto de trabajo.
1: public void DrawFaceModel(DrawingContext drawingContext)
2: {
3: if (!LastFaceTrackSucceeded || _skeletonTrackingState != SkeletonTrackingState.Tracked)
4: {
5: _startFaceRecognition = DateTime.MinValue;
6: return;
7: }
8:
9: var faceModelPts = _facePoints.Select(t => new Point(t.X + 0.5f, t.Y + 0.5f)).ToList();
10:
11: var faceModel = _faceTriangles.Select(t => new FaceModelTriangle
12: {
13: P1 = faceModelPts[t.First],
14: P2 = faceModelPts[t.Second],
15: P3 = faceModelPts[t.Third]
16: }).ToList();
17:
18: var faceModelGroup = new GeometryGroup();
19: for (var i = 0; i < faceModel.Count; i++)
20: {
21: var faceTriangle = new GeometryGroup();
22: faceTriangle.Children.Add(new LineGeometry(faceModel[i].P1, faceModel[i].P2));
23: faceTriangle.Children.Add(new LineGeometry(faceModel[i].P2, faceModel[i].P3));
24: faceTriangle.Children.Add(new LineGeometry(faceModel[i].P3, faceModel[i].P1));
25: faceModelGroup.Children.Add(faceTriangle);
26: }
27:
28: drawingContext.DrawGeometry(Brushes.BlueViolet, new Pen(Brushes.Red, 1.0), faceModelGroup);
29: if (_startFaceRecognition == DateTime.MinValue)
30: {
31: _startFaceRecognition = DateTime.Now;
32: }
33: else
34: {
35: if (DateTime.Now.Subtract(_startFaceRecognition).TotalSeconds > 5)
36: {
37: Application.Current.Dispatcher.BeginInvoke(new Action(() =>
38: {
39: System.Threading.Thread.Sleep(300);
40: _startFaceRecognition = DateTime.MinValue;
41: }));
42: }
43: }
44: }
8. Ahora pasamos al visor, que es el que “pinta” esto sobre una superficie [FaceTrackingViewer]. Dentro de la funcion [OnAllFramesReady] donde se procesan los datos del sensor Kinect, se identifica cada uno de los skeletons en un estado [SkeletonTrackingState.Tracked]. Una vez identificado, se crea una nueva instancia de la clase SkeletonFaceTracker y se ejecutan los pasos anteriores para identificar una cara/face. Cada instancia de SkeletonFaceTracker se almacena en una colección.
1: foreach (Skeleton skeleton in this.skeletonData)
2: {
3: if (skeleton.TrackingState == SkeletonTrackingState.Tracked
4: || skeleton.TrackingState == SkeletonTrackingState.PositionOnly)
5: {
6: if (!this.trackedSkeletons.ContainsKey(skeleton.TrackingId))
7: {
8: this.trackedSkeletons.Add(skeleton.TrackingId, new SkeletonFaceTracker() { });
9: }
10:
11: SkeletonFaceTracker skeletonFaceTracker;
12: if (this.trackedSkeletons.TryGetValue(skeleton.TrackingId, out skeletonFaceTracker))
13: {
14: if (this.Kinect != null)
15: {
16: skeletonFaceTracker.OnFrameReady(this.Kinect, colorImageFormat,
17: colorImage, depthImageFormat, depthImage, skeleton);
18: skeletonFaceTracker.LastTrackedFrame = skeletonFrame.FrameNumber;
19: }
20: }
21: }
22:
23: }
9. Finalmente en la función OnRender() del control, se “pinta” cada una de las caras.
1: protected override void OnRender(DrawingContext drawingContext)
2: {
3: base.OnRender(drawingContext);
4: foreach (var faceInformation in this.trackedSkeletons.Values)
5: {
6: faceInformation.DrawFaceModel(drawingContext);
7: }
8: }
10. El ejemplo completo incluye un poco de código en la ventana principal para detectar el sensor kinect y trabajar con el mismo. Y otro par de detalles que comentaré en un next post.
El ejemplo se puede descargar desde http://sdrv.ms/MZY0Eq
Saludos @ La Finca
El Bruno
Leave a comment