Kinect SDK – Skeletal tracking  Udostępnij na: Facebook

Autor: Tomasz Kowalczyk

Opublikowano: 2011-09-26

Artykuł ten jest czwartym artykułem z serii Kinect SDK, w którym opisano możliwości sensora Kinect, podłączonego do komputera. Zaprezentowano sposób wykorzystania opcji śledzenia zachowania sylwetki człowieka oraz odpowiedniego reagowania programu na ruchy postaci.

Po zapoznaniu się z treścią artykułu, czytelnik będzie wiedział, w jaki sposób korzystać z opcji tzw. Skeletal tracking. Dowie się również, w jaki sposób Kinect wykrywa zachowanie człowieka, znajdującego się w jego zasięgu. Przedstawione informacje, schematy i opisy będą stanowiły niezbędną pomoc w implementacji reagowania na zdarzenia, wywołane zmianą położenia poszczególnych kończyn człowieka.

Wprowadzenie

NUI Skeleton API dostarcza informacji o położeniu dwóch postaci, znajdujących się naprzeciwko sensora Kinect (wersja Beta Kinect SDK – docelowo ma to być 6 postaci). Dane, pobierane przez sensor, dostarczają informacji o położeniu 20 części ciała, pozwalają one na określenie ich współrzędnych względem sensora.

Punkty te tworzą szkielet postaci (implementacja NUI Skeleton API określa je jako – Joints). Za pomocą tych punktów programista jest w stanie rozpoznać aktualne położenie człowieka względem sensora oraz jego pozę. Dzięki opisywanemu SDK, twórcy oprogramowania są również w stanie wyznaczyć współrzędne poszczególnych części ciała oraz odpowiednio reagować na zmiany ich położenia.

Punkty szkieletu, do których istnieje programowy dostęp, przedstawia Rys. 1

Rys. 1. Punkty ciała zaimplementowane w Kinect SDK Beta.

Przechwytywanie obrazu, z którego pobierane są dane przez NUI Skeleton API, wygląda analogicznie do pobierania obrazu z kamery RGB czy z kamery głębokości, co zostało dokładnie opisane w poprzednich artykułach tej serii.

Tworząc oprogramowanie, bazujące na Kinect SDK, programista posiada dostęp do dwóch metod przechwytywania ramek obrazu: polling model i event model. Należy jednak pamiętać, że niemożliwe jest wykorzystanie tych dwóch modeli jednocześnie.

Działanie Skeletal tracking

Skeletal tracking działa w dwóch trybach: aktywnym i pasywnym. Tryb aktywny w wersji Kinect SDK Beta może być zastosowany do maksymalnie dwóch osób, znajdujących się w zasięgu sensora, natomiast tryb pasywny jest stosowany dla pozostałych czterech sylwetek. Różnica między wymienionymi trybami polega na tym, że sylwetki, śledzone w trybie pasywnym, dostarczają jedynie danych odnośnie swego położenia, natomiast sylwetki śledzone w trybie aktywnym dają nam dostęp do danych, dotyczących poszczególnych punktów (Joint) i ich położenia.

Po inicjalizacji trybów, w których działa Skeletal tracking, dane przesyłane są w ramkach, zawierających tablicę współrzędnych poszczególnych punktów. Programista jest w stanie określić tryb, w jakim znajduje się przechwycona sylwetka. Aby tego dokonać, należy korzystać z bibliotek, zawartych w Kinect SDK. Implementacja jednej z bibliotek za to odpowiedzialnych została zdefiniowana w postaci maszyny stanów, która pozwala przyporządkować dany stan do konkretnego szkieletu:

  • SkeletonTrackingState.Tracked,
  • SkeletonTrackingState.NotTracked,
  • SkeletonTrackingState.PositionOnly.

Każdy punkt (Joint) posiada pozycję względem sensora, definiowaną przez wektor – Vector4(x, y, z, w), w którym to pierwsze trzy atrybuty definiują jego współrzędne, czwarta natomiast pozwala na filtrowanie danych.

Każda przechwycona sylwetka posiada unikalny atrybut TrackingID, który pozwala na rozróżnienie jej pomiędzy kolejnymi przechwytywanymi ramkami.

Dodatkowo NUI Skeleton API dostarcza algorytmy, pomagające w filtrowaniu i wygładzaniu ruchów obiektów programu, będących odwzorowaniem poszczególnych punktów (Joint). Objawia się to tym, iż po przekazaniu odpowiednio ustawionych atrybutów wygładzania, dane pochodzące z sensora są uśredniane, co powoduje zanik efektu wibrowania, spowodowany czułością czujnika (to zjawisko zostanie opisane dokładniej w dalszej części artykułu).

Organizacja kodu, odpowiedzialnego za Skeletal tracking wygląda tak, jak na Rys. 2. Jest ona podobna do tego, jak działa pobieranie obrazów z kamer RGB i głębokości.

Rys. 2. Diagram klas – Skeletal tracking.

Zgodnie ze schematem, przedstawionym powyżej, po zastosowaniu metody, reagującej na zdarzenie pobrania danych do Skeletal tracking, rozróżniane są pojedyncze ramki obrazu. Następnie tworzony jest obiekt, który jest kontenerem dla danych, dotyczących poszczególnych sylwetek, co daje twórcy aplikacji możliwość pobrania danych odnośnie konkretnych punktów (Joint).

Implementacja

Projekt, który zostanie wykonany na potrzeby tego artykułu, będzie zawierał możliwości Skeletal tracking. W trakcie jego realizacji zostaną wykorzystywane trzy kontrolki WPF – ellipse, które będą odpowiadały głowie oraz rękom postaci, znajdującej się przed sensorem. W momencie, gdy postać pojawi się przed sensorem, będzie można zaobserwować na ekranie komputera, w oknie przygotowanej aplikacji, ruch trzech kolorowych elips, odpowiadających ruchom części ciała sylwetki.

Informacja
Wszystkie kody źródłowe projektów, utworzonych w ramach artykułów, będą dostępne na tej stronie.

W pierwszym kroku należy zainicjalizować opcję Skeletal tracking:

nui.Initialize(RuntimeOptions.UseSkeletalTracking);

Powyższy kod wywołuje metodę inicjującą NUI na wcześniej utworzonym obiekcie. Jako argumenty metody należy podać, jakich opcji przechwytywania aplikacja będzie używać, pozwoli to na powołanie instancji obiektów, utworzonych w momencie użycia danego podzespołu sensora.

Zaraz po tym, należy umieścić informację o wykorzystaniu opcji filtrowania i wygładzania (to jest dowolne, lecz w przypadku używania tej opcji należy również umieścić ten fragment kodu zaraz po inicjalizacji opcji Skeletal tracking):

nui.SkeletonEngine.TransformSmooth = true;

Po deklaracji wykorzystania opcji filtrowania i wygładzania, należy jeszcze przekazać do niej wymagane parametry wraz z ustawionymi wartościami dla poszczególnych wielkości:

var parameters = new TransformSmoothParameters
            {
                Smoothing=0.75f,
                Correction=0.0f,
                Prediction=0.0f,
                JitterRadius=0.05f,
                MaxDeviationRadius=0.04f
            };

Powyższy kod tworzy tablicęparameters, która będzie przechowywać ustalone wartości poszczególnych atrybutów, odpowiedzialnych za przeprowadzenie korekcji wygładzania i filtrowania.

W kolejnym kroku, zgodnie z Diagram klas – Skeletal tracking, przedstawionym powyżej, należy dodać zdarzenie przechwytujące dane z kamery, następnie przekazać te dane do metody (nui_SkeletonFrameReady), w której to należy zaimplementować odpowiedzi na zdarzenie:

nui.SkeletonFrameReady+=newEventHandler<SkeletonFrameReadyEventArgs>(nui_SkeletonFrameReady);

Podobnie, jak w projekcie wykonanym w poprzednim artykule, zastosowano tutaj aspekty programowania zdarzeniowego w odpowiedzi na przechwycony obraz.

Teraz kolej na implementację metody obsługującej zdarzenie przechwycenia obrazu, wraz z powoływaniem obiektów klas, wg. zależności zawartej w diagramie:

void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            SkeletonFrame allSkeletons=e.SkeletonFrame;

            SkeletonData skeleton=(from s in allSkeletons.Skeletons
                                     where s.TrackingState == SkeletonTrackingState.Tracked
                                     select s).FirstOrDefault();

            SetEllipsePosition(headEllipse, skeleton.Joints[JointID.Head]);
            SetEllipsePosition(leftEllipse, skeleton.Joints[JointID.HandLeft]);
            SetEllipsePosition(rightEllipse, skeleton.Joints[JointID.HandRight]);
        }

Aby osiągnąć pełną funkcjonalność omawianej aplikacji, wymagane jest jeszcze, aby zaimplementować metodę, której zadaniem będzie ustawienie pozycji kontrolki ellipse, zgodnie z odpowiadającym jej punktom sylwetki śledzonej postaci:

private void SetEllipsePosition(FrameworkElement ellipse, Joint joint)
        {
            var scaledJoint=joint.ScaleTo(640, 480, .99f, .5f);
            Canvas.SetLeft(ellipse, scaledJoint.Position.X);
            Canvas.SetTop(ellipse, scaledJoint.Position.Y);
        }

Tutaj została użyta metodaScaleTo(), pochodząca z biblioteki Coding4Fun, która dostosowuje dwie skale do siebie. Jedną z nich jest skala, w jakiej dane są przechwytywane z sensora, a druga to skala, określająca rozmiar naszej aplikacji. Warto przeprowadzić własne obserwacje, jakie zmiany niosą za sobą modyfikacje tych wartości. Przykładowo zmiana wartości trzeciego parametru (.99f) na wartość (.5f) powinna powodować, że ruchy rękoma i elips, które im odpowiadają, będą dochodziły do krawędzi aplikacji, podczas gdy wcześniej wymagana była zmiana położenia całej sylwetki oraz jej przesunięcie w lewą lub prawą stronę w celu dotarcia do krawędzi aplikacji.

Podsumowanie

W tym artykule opisano opcję Skeletal tracking, która pozwala na śledzenie ruchów sylwetki, znajdującej się przed sensorem Kinect. Zaprezentowano również, jak zaimplementować we własnej aplikacji reakcje na zdarzenia, wywołane ruchem poszczególnych punktów ciała człowieka.

W następnej części zostanie opisana funkcja rozpoznawania mowy czyli Audio API, dostarczona wraz z Kinect SDK Beta i bazująca na Microsoft.Speech.