上一篇介紹手勢概念與揮手演算法。本文將建立可重用的手部追蹤類別庫,並以此為基礎完整實作除揮手外的七大經典 Kinect 手勢:懸浮按鈕、下壓按鈕、磁性按鈕、劃動、磁性幻燈片、垂直捲動、通用暫停。
設計目標:高內聚、低耦合。自動管理骨骼串流 → 座標轉換 → 視覺回饋 (CursorAdorner) → 事件路由到 UI 元素。
| 類別 | 職責 |
|---|---|
| KinectCursorEventArgs | 承載 X/Y/Z 座標與 CursorAdorner 參考的路由事件參數 |
| KinectInput | 定義 5 大路由事件:CursorEnter/Leave/Move/Activated/Deactivated |
| CursorAdorner | 繼承 Adorner,永遠置頂顯示手勢游標,支援動畫 (漸層位移) |
| KinectCursorManager | 單例,核心引擎:骨骼平滑、主手判定、座標映射、HitTest 路由事件 |
private static Skeleton GetPrimarySkeleton(IEnumerable skeletons) {
return skeletons.Where(s => s.TrackingState == SkeletonTrackingState.Tracked)
.OrderBy(s => s.Position.Z) // 最近者優先
.FirstOrDefault();
}
private static Joint? GetPrimaryHand(Skeleton skeleton) {
var right = skeleton.Joints[JointType.HandRight];
var left = skeleton.Joints[JointType.HandLeft];
if (right.TrackingState == JointTrackingState.Tracked) {
if (left.TrackingState != JointTrackingState.Tracked) return right;
return left.Position.Z > right.Position.Z ? right : left; // Z 較小(較近)者為主手
}
return left.TrackingState == JointTrackingState.Tracked ? left : null;
}
private void UpdateCursor(Joint hand) {
var depthPt = kinectSensor.MapSkeletonPointToDepth(hand.Position, kinectSensor.DepthStream.Format);
float x = depthPt.X * window.ActualWidth / kinectSensor.DepthStream.FrameWidth;
float y = depthPt.Y * window.ActualHeight / kinectSensor.DepthStream.FrameHeight;
float z = depthPt.Depth; // 毫米
HandleCursorEvents(new Point(x, y), z);
cursorAdorner.UpdateCursor(new Point(x, y));
}
private void HandleCursorEvents(Point pt, double z) {
var element = GetElementAtScreenPoint(pt, window); // VisualTreeHelper.HitTest
if (element != null) {
element.RaiseEvent(new KinectCursorEventArgs(KinectInput.KinectCursorMoveEvent, pt, z) { Cursor = cursorAdorner });
if (element != lastElementOver) {
lastElementOver?.RaiseEvent(new KinectCursorEventArgs(KinectInput.KinectCursorLeaveEvent, pt, z) { Cursor = cursorAdorner });
element.RaiseEvent(new KinectCursorEventArgs(KinectInput.KinectCursorEnterEvent, pt, z) { Cursor = cursorAdorner });
lastElementOver = element;
}
}
}
繼承 Button,將 CursorEnter 映射為 Click 事件,讓傳統按鈕邏輯無縫遷移。缺點:無法區分「有意」與「無意」懸停,引出下方改良版本。
原理:DispatcherTimer 計時,進入啟動、離開停止、超時觸發 Click。
視覺回饋:游標漸層動畫倒數。
public class HoverButton : KinectButton {
public double HoverInterval { get; set; } = 2000; // 2 秒
protected override void OnKinectCursorEnter(...) { timer.Start(); cursor.AnimateCursor(HoverInterval); }
protected override void OnKinectCursorLeave(...) { timer.Stop(); cursor.StopCursorAnimation(); }
void timer_Tick(...) { timer.Stop(); RaiseEvent(new RoutedEventArgs(ClickEvent)); }
}
原理:記錄進入時手部深度 handDepth,若當前 Z < handDepth - Threshold (預設 100mm) 視為下壓。
public class PushButton : KinectButton {
public double PushThreshold { get; set; } = 100;
double _baseDepth;
protected override void OnKinectCursorEnter(...) => _baseDepth = e.Z;
protected override void OnKinectCursorMove(...) { if (e.Z < _baseDepth - PushThreshold) RaiseEvent(new RoutedEventArgs(ClickEvent)); }
}
核心創新:手進入區域 → 游標自動吸附至按鈕中心 (動畫過渡) → 手離開 → 游標釋放回手部位置。
技術難點:需計算按鈕在視覺樹中的絕對中心座標,並暫時覆寫游標追蹤 (UpdateCursor(pt, true))。
protected override void OnKinectCursorEnter(...) {
var center = GetButtonCenter(); // TransformToAncestor(Window)
var lockPt = new Point(center.X - cursor.Width/2, center.Y - cursor.Height/2);
AnimateCursorToLockPosition(e, cursor, lockPt); // DoubleAnimation Canvas.Left/Top
}
protected override void OnKinectCursorLeave(...) {
AnimateCursorAwayFromLockPosition(e, cursor, lockPt); // 動畫回手部位置並解除覆寫
}
在 KinectCursorManager 內建 GesturePoint 追蹤清單 (X, Y, Z, Time)。核心邏輯 HandleGestureTracking:
swipeDeviation → 失敗清空。swipeTime → 移除最舊點 (滑動視窗)。swipeLength (正負表方向) → 觸發 swipeDetected 事件。混合體驗:磁性吸附 (無需等待) + 劃動觸發 (立即回應)。
實作:繼承 MagnetButton,OnKinectCursorEnter 向 Manager 註冊劃動參數與事件,swipeDetected 觸發 Click、swipeOutofBoundDetected 取消註冊。
public class MagneticSlide : MagnetButton {
protected override void OnKinectCursorEnter(...) {
InitializeSwipe(); // 向 Manager 註冊參數與事件
base.OnKinectCursorEnter(...);
}
void kinectMgr_swipeDetected(...) { DeInitializeSwipe(); RaiseEvent(new RoutedEventArgs(ClickEvent)); }
void kinectMgr_swipeOutofBoundDetected(...) { DeInitializeSwipe(); RaiseEvent(new SwipeOutOfBoundsEventArgs()); }
}
原理:計算肩-肘-腕向量與垂直線夾角。
區間設計:中性區 ±20° (無動作) → 向上/向下分級觸發捲動速度。
優勢:避免水平劃動的「回手抵銷」問題,長時間操作不易疲勞。

左臂抬起 45° (與軀幹夾角)。實作同垂直捲動,僅換左手關節點與角度閾值。微軟建議作為暫停/選單喚出保留手勢,避免與應用手勢衝突。
整合上述控制項於單一視窗,即可驗證所有手勢互動流程。


本文從架構級的手部追蹤庫出發,逐層建構七大經典手勢控制項,涵蓋計時、深度、吸附、軌跡、角度等核心技術。這套類別庫可直接引入專案,加速 Kinect 自然使用者介面開發。下一階段將探索臉部辨識、全像投影與第三方引擎整合,敬請期待!