正如點擊之於 GUI、輕點之於觸控,**手勢是 Kinect 應用的核心互動單元**。但手勢非單純動作,而是「包含資訊的身體運動」(Eric Hulteen & Gord Kurtenbach, 1990)。敲鍵盤非手勢,因按鍵動作本身無意義;揮手道別是手勢,因傳達「再見」訊息。理解此區別,是設計優質自然使用者介面 (NUI) 的起點。
註:直接操作 — 如拖曳、縮放 — 非手勢,屬連續控制。
| 層次 | 核心特質 | Kinect 對應 |
|---|---|---|
| 先驗技能利用 | 使用者忘記學習過程,直覺操作 | 揮手招呼 → 喚醒應用 |
| 媒介隱形 | 複雜技術 (波束成形、骨骼追蹤) 對使用者不可见 | 說「嘿,注意」電腦即回應 |
| 互動模態融合 | 語音、觸控、動態、姿勢無縫切換 | 語音下指令 + 手勢定位 + 姿勢捲動 |
Kinect 獨有第三種模態:**姿勢** — 身體部位靜態關係 (如左臂 45° 暫停)。
來源多元:微軟定義、遊戲廠商創新、PC 開發者實驗。缺乏統一標準,面臨三大阻礙:
良好手勢準則:**可供性** (操作前提示功能) + **回饋** (操作中/後確認結果)。
| 方法 | 原理 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|---|
| 演算法/規則基礎 | 定義幾何/時序條件 (閾值、角度、次數) | 易懂、易寫、易除錯、效能高 | 難處理複雜/模糊手勢、擴充性差、參數調校繁瑣 | 簡單、明確手勢 (揮手、推、劃動) |
| 類神經網路 | 統計機率判斷,多神經元共享特徵 | 處理模糊/複雜動作、擴充性好、效率高 | 建構/除錯難、參數極多需經驗調校、需機器學習知識 | 跳躍、下蹲等多變體態動作 |
| 樣本/模板比對 | 儲存標準骨骼/深度幀序列,比對相似度 | 易新增手勢、處理極複雜手勢、隨用戶資料精進 | 需大量樣本資料、儲存/運算資源大、需涵蓋不同體型/穿著 | 專業動作辨識 (康復、運動訓練) |
**建議**:專案初期以演算法法為主,複雜度提升再引入混合模式。
動作定義:以肘為樞紐,前臂在水平面左右擺動,中間姿勢前臂垂直地面。
觀察規律:
1. 手腕恆在肘部上方 (Y 軸)。
2. 手腕 X 座標相對肘部左右穿越中性區 (閾值 ±0.1m) 來回 ≥ 4 次。
3. 在 5 秒內完成。
enum WavePosition { None, Left, Right, Neutral }
enum WaveGestureState { None, Success, Failure, InProgress }
struct WaveGestureTracker {
public int IterationCount; // 穿越次數
public WaveGestureState State;
public long Timestamp; // 幀時間戳
public WavePosition StartPosition, CurrentPosition;
public void Reset() { ... }
public void UpdatePosition(WavePosition pos, long ts) {
if (CurrentPosition != pos) {
if (pos == WavePosition.Left || pos == WavePosition.Right) {
if (State != WaveGestureState.InProgress) { State = WaveGestureState.InProgress; IterationCount = 0; StartPosition = pos; }
IterationCount++;
}
CurrentPosition = pos; Timestamp = ts;
}
}
}
public void Update(Skeleton[] skeletons, long frameTimestamp) {
foreach (var s in skeletons.Where(s => s.TrackingState != SkeletonTrackingState.NotTracked)) {
TrackWave(s, true, ref trackers[i, LEFT_HAND], frameTimestamp);
TrackWave(s, false, ref trackers[i, RIGHT_HAND], frameTimestamp);
}
}
private void TrackWave(Skeleton s, bool isLeft, ref WaveGestureTracker t, long ts) {
var hand = s.Joints[isLeft ? JointType.HandLeft : JointType.HandRight];
var elbow = s.Joints[isLeft ? JointType.ElbowLeft : JointType.ElbowRight];
if (hand.TrackingState == JointTrackingState.NotTracked || elbow.TrackingState == JointTrackingState.NotTracked) { t.Reset(); return; }
// 超時失敗
if (t.State == WaveGestureState.InProgress && t.Timestamp + 5000 < ts) { t.UpdateState(WaveGestureState.Failure, ts); return; }
// 手在肘上方?
if (hand.Position.Y > elbow.Position.Y) {
// 判斷左右位置
if (hand.Position.X <= elbow.Position.X - 0.1f) t.UpdatePosition(WavePosition.Left, ts);
else if (hand.Position.X >= elbow.Position.X + 0.1f) t.UpdatePosition(WavePosition.Right, ts);
else t.UpdatePosition(WavePosition.Neutral, ts);
// 成功判定
if (t.State != WaveGestureState.Success && t.IterationCount >= 4) {
t.UpdateState(WaveGestureState.Success, ts);
GestureDetected?.Invoke(this, EventArgs.Empty);
}
} else {
// 手落在肘下
if (t.State == WaveGestureState.InProgress) t.UpdateState(WaveGestureState.Failure, ts);
else t.Reset();
}
}
本文建立手勢識別的理論基石與演算法實作入門。下一篇將:
敬請期待《手勢識別 下:基本手勢識別》!