Kinect SDK og Reactive Extensions

Kinect SDK og Reactive Extensions

  • Comments 3

Det er første gang at nørderne på arbejdet har sprunget rundt i kontorlandskabet når de debugger, og sælgerne springer for livet når algoritmerne hit, slide og high-kick testes

Det har været en sjov udfordring. Med Kinect SDK'et kan du efterhånden det meste fra C#. Jeg har taget udgangspunkt i Skeletal Viewer Walkthrough demoen og bygget videre på det. Det sjove ved den demo er, at man hurtigt kan begynde at arbejde med de data som repræsenterer skelettet af den eller de personer, som står foran Kinect'en (det der kræver mest er den visuelle repræsentation). Data er groft sagt en liste af koordinater (x,y,z) hvor hvert koordinat hører til et givent led i kroppen. Lidt afhængig af PC'en, så modtager jeg 30 frames (sæt data) i sekundet.

Største udfordring har været: hvordan behandler man mest effektivt alle de data man modtager fra Kinect'en.

Runtime nui = new Runtime();
nui.SkeletonFrameReady +=
 
new EventHandler<SkeletonFrameReadyEventArgs>(nui_SkeletonFrameReady);

Ovenstående er fint, hvis man bare skal kigge på en frame af gangen. Der kommer en kontinuerlig strøm af SkeletonFrameReady events, hvor man i eventargs har data om det eller de skeletter der er i sysfeltet.
Udfordringen kommer, når man gerne vil kigge på en sekvens af frames/eventargs og måske endda have et sliding window, for at kigge på sidste sekunds frames. Behovet opstår, når man gerne vil kigge efter bevægelser (gestures og positurer). 

Reactive Extensions (Rx) har jeg i denne forbindelse stirret mig fuldstændig blind på. For en god gennemgang af Rx, så kan jeg anbefale klart Rx Workshoppen på Channel9.

Men kort sagt, så kan man betragte Rx som en omvendt Enumerable, en Observable. En Enumerable skal du foreach'e over, for at få (pull) data. Med en Observable får du skubbet (push) data ud. Lidt en omvendt tankegang. Desuden er Rx, modsat events, en first-class citizen i .NET!

var o = Observable
 
.FromEventPattern<SkeletonFrameReadyEventArgs>(nui, "SkeletonFrameReady")
 
.Buffer(TimeSpan.FromSeconds(1));

o.Subscribe( 
 
a =>
   
this.Title = String.Format("FPS: {0}", a.Count()); 
 
});

Ovenstående abonnerer på SkeletonFrameReady eventet – man laver eventet til en observable. Med .Buffer() får jeg samlet events op i 1 sekund. Med Subscribe begynder jeg at observere og lambda'en bliver eksekveret når jeg har 1 sekunds data.

I nedenstående bruger jeg sammen mønster, for at subscribe på "nye skeletter" (groupby TrackingID).

Resultatet er, at jeg får en ny observable på et skelet, hver gang der kommer et nyt i synsfeltet. Code Snippet

IObservable<EventPattern<SkeletonFrameReadyEventArgs>> o =  
   Observable.FromEventPattern<SkeletonFrameReadyEventArgs>(nui, "SkeletonFrameReady");

IObservable<SkeletonData> q = 
   o.SelectMany(s => s.EventArgs.SkeletonFrame.Skeletons)
    .Where(w => w.TrackingState == SkeletonTrackingState.Tracked);    

IObservable<IGroupedObservable<int, SkeletonData>> qg = 
    q.GroupBy(g => g.TrackingID);    
qg.Select(s => s.AsObservable()).Subscribe( 
    userObserver => 
    { 
        ObserveUser(userObserver); 
    });

Vi kan arbejde lidt videre med det, for at begynde at analysere på data og finde simple gestures. ObserveUser kaldes med en Observable.
Så hvis jeg vil tracke højre hånd, kigge på data fra de sidste 250 milisekunder, slide vinduet 100 milisekunder så kan det skrives med nedenstående. Der bliver push'et data ud til min subscribe lambda, hvis predikaterne (where) er true. Code Snippet

public IDisposable ObserveUser(IObservable<SkeletonData> user)

    var o = user 
        .Select(j => new { J = j.Joints[JointID.HandRight], ID = j.TrackingID }) 
        .Where(s => s.J.TrackingState == JointTrackingState.Tracked) 
        .Buffer(TimeSpan.FromMilliseconds(250), TimeSpan.FromMilliseconds(100));    
    var dis = o 
        .Where(w => w.Count() > 3) //require at least 3 in sample 
        .Subscribe( 
            u => 
            { 
                //Logic that analyses (hold, move, slide, kick etc) 
            });

Nu er det så man skal tilbage og hive matematikbøgerne frem. Der går lynhurtigt en masse algoritmik i det;-)

God fornøjelse!

  • Hej,

    Jeg får 2 fejl når jeg prøver at implementere disse 2 linjer:

    o.Subscribe(  

     a =>{  

       this.Title = String.Format("FPS: {0}", a.Count());  

     });

    under a får jeg denne fejl:

    Cannot convert lambda expression to type 'System.IObserver<System.Collections.Generic.IList<System.Reactive.EventPattern<Microsoft.Research.Kinect.Nui.SkeletonFrameReadyEventArgs>>>' because it is not a delegate type

    Under a.Count() får jeg denne fejl:

    'System.Collections.Generic.IList<System.Reactive.EventPattern<Microsoft.Research.Kinect.Nui.SkeletonFrameReadyEventArgs>>' does not contain a definition for 'Count' and no extension method 'Count' accepting a first argument of type 'System.Collections.Generic.IList<System.Reactive.EventPattern<Microsoft.Research.Kinect.Nui.SkeletonFrameReadyEventArgs>>' could be found (are you missing a using directive or an assembly reference?)

    ved ikke hvad det er jeg gør forkert, kan du hjælpe ?

  • Hej Lahib,

    Kan du ikke zippe kode og ligge det på et share et eller andet sted (skydrive/dropbox). Så kan jeg lige prøve at kigge på det.

    Det kan være noget versionen på enten Rx eller Kinect SDK. Der er kommet en ny version af SDK'et siden jeg skrev denne post.

    /Henrik

  • Hej Lahib

    using system.linq; gør forskellen. "no extension method for count" - så den mangler reference til system.linq namespace.

    /Henrik

Page 1 of 1 (3 items)