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!