iT邦幫忙

2026 iThome 鐵人賽

DAY 25
0
Build on Google AI

Build on Google AI :長者照護 —— 口腔機能訓練 與 延緩認知退化系列 第 25 篇

少年,我看你骨骼精奇。Google MediaPipe Pose、Hand、Gesture、Holistic 你聽過嗎?(中)

  • 分享至 

  • xImage
  •  

一、前言

Day24 四位俠客來到府裡,各自報上了名號。
今天老爺請四位到院子裡過過招:

舉左手的「左」,是誰的左邊?
同樣看手,Hand、Gesture、Holistic 給的結果一樣嗎?
手指比幾,哪一位俠客認得出來?
家人一起入鏡,誰能看到每一個人?

哪一位俠客能最細心觀察出動作呢?
讓我們看看這場演武比試!


二、參數設定

參數架構大致相同:
人數上限和三種門檻,差別在各自的額外輸出設定。

設定 在管什麼 什麼時候調整 Hand、Gesture、Pose Holistic
人數上限 num_hands、num_poses 最多找幾隻手、幾個人 家人一起入鏡 1 固定 1 人
① 找到的門檻 detection 分數多少才算找到 常漏抓調低,常認錯調高 0.5 臉、身體各一個
② 確認的門檻 presence 標特徵點時再確認一次 手一動就不見,試著調低 0.5 臉、身體、手各一個
③ 追蹤的門檻 tracking 影片裡,下一格還是同一個嗎 點常跳掉,試著調低 0.5 沒有
執行模式 running_mode 照片、影片、鏡頭畫面 App 用鏡頭畫面 三種 三種
額外輸出 多給其他結果 用得到才打開 Gesture 手勢分類、Pose 人形遮罩 blendshape、人形遮罩

Gesture 的手勢分類:
可以設分數門檻、只認哪幾種(allowlist)、不認哪幾種(denylist);
只需要「讚」和「張開手掌」的話,用 allowlist 只留這兩種。

三道門檻:

門檻是 0~1 的分數,分數高於門檻,模型才採用這個結果。

① 找到:整張畫面裡有沒有手?看的是信心分數(confidence score)。
② 確認:框起來的地方,真的是手嗎?也是信心分數。
③ 追蹤:下一格的手框,跟上一格重疊得夠不夠?夠才算同一隻手。

手快離開畫面時,要把門檻調低嗎?

實際用 App 時,最常遇到的是手一放下、人往旁邊偏,手就跑出畫面。
這時候把門檻調低,能不能讓模型撐久一點?

拿手掌照片往畫面外平移,用 IMAGE 模式每張各跑一次,
把 ① 找到、② 確認的門檻從 0.1 調到 0.9,看手出框多少時找不到(Hand):

手出框 ① 找到的門檻 ② 確認的門檻
0%、25%、50% 調到 0.9 都找得到 調到 0.9 都找得到
75% 0.8 找得到、0.9 找不到 0.7 找得到、0.8 找不到

手出框 75%,分數都還有 0.7 以上,比預設的 0.5 高很多。

再用 VIDEO 模式,讓手慢慢移出去、再移回來:
出框 80% 時找不到,回程出框剩 70% 就重新找到;
③ 追蹤的門檻設 0.2、0.5、0.8,結果都一樣。

門檻不是瓶頸:手還在畫面裡,預設 0.5 就找得到;手出框太多,調門檻也救不回來。
要讓模型看得到,手要留在畫面裡。

API 不會回傳各道門檻的分數,表中的區間是調門檻推出來的;
這次是一張照片平移合成的結果。


三、模型輸出

(一)兩組座標

每個點都有兩組座標(Holistic 的臉只有 landmarks):

landmarks world landmarks
x、y 0~1,分別除以畫面的寬、高 公尺
z 相對深度,越小越靠近鏡頭 公尺
原點 x、y 在畫面左上角;z 在手腕(Hand)、臀部中點(Pose) 手的幾何中心(Hand)、臀部中點(Pose)
適合拿來 把點畫在畫面上、看手在畫面哪裡 看手、身體本身的形狀

x、y 分別除以寬、高,跟 Day9、Day11 一樣,算距離前要先換回長寬比。

兩組座標,像兩把尺:

  • landmarks 像是拿尺量照片:拍遠了,照片裡的手就變小。
  • world landmarks 像是拿尺量真的手:拍遠拍近,手都一樣大。

所以要回答的問題不同,用的座標也不同:

  • 手在畫面哪裡?舉高了嗎?用 landmarks:
    y 越小,手越高;判斷「舉手」看的就是它。
  • 手指伸直了嗎?手有多大?用 world landmarks:
    第六節數手指就用它。

(二)Hand:左右手+21 個點

每偵測到一隻手,給三樣:
handedness(左手還是右手,和判斷的分數)、landmarks、world landmarks。

Android 呼叫 detect() 之後,這樣拿:

val result = handLandmarker.detect(mpImage)

// 第 1 隻手:左手或右手、分數
val handedness = result.handedness()[0][0]

// 21 個點,x、y 是 0~1
val landmarks = result.landmarks()[0]

// 21 個點,單位是公尺
val world = result.worldLandmarks()[0]

Day24 那張手掌照片(3024×4032),A70 實際拿到的結果:

  • x、y:
    由左到右、由上到下都是 0~1,手腕 (0.27, 0.77) 在左下方。
  • world landmarks:
    手腕到中指尖約 0.18 公尺;同樣兩點換回像素是 2517 像素,
    換一個距離拍,像素就會變。

(三)Gesture:多一樣手勢

handedness、landmarks、world landmarks 跟 Hand 一樣,
但多了 gestures(手勢和分數):

val result = gestureRecognizer.recognize(mpImage)

// 第 1 隻手的手勢:名稱、分數(例如 Thumb_Up、0.73)
val gesture = result.gestures()[0][0]
val name = gesture.categoryName()
val score = gesture.score()

(四)Pose:每個點 5 個值

值 意思
x、y 0~1,分別除以畫面的寬、高
z 以臀部中點為原點,越小越靠近鏡頭
visibility 在畫面裡,而且沒被擋住的機率
presence 在畫面裡的機率,不管有沒有被擋住

可以偵測好幾個人,所以結果是「每個人一組 33 點」:

val result = poseLandmarker.detect(mpImage)

// 第 1 個人的 33 個點:landmarks、world landmarks
val landmarks = result.landmarks()[0]
val world = result.worldLandmarks()[0]

// 15 是左手腕;visibility、presence 是 Optional,沒有值時是 null
val wrist = landmarks[15]
val visibility = wrist.visibility().orElse(null)
val presence = wrist.presence().orElse(null)

上半身那張照片(full 模型),實際拿到的結果(節錄):

lm 15 x=0.7792 y=0.2175 visibility=0.995 presence=0.981
lm 25 x=0.8353 y=0.9462 visibility=0.891 presence=0.559
lm 27 x=0.7369 y=1.0849 visibility=0.265 presence=0.215

Pose、Holistic 這兩段,在 Android 模擬器跑(tasks-vision 1.1.0);
跟電腦 Python 的結果比,四捨五入後最多差最後一位。

  • 15 左手腕:舉起的手在畫面裡,兩個機率都接近 1。
  • 25 左膝:在畫面底邊附近,presence 降到 0.56。
  • 27 左腳踝:y 超過 1,代表在畫面外,presence 只剩 0.21。

畫面外的腳,presence 只有 0.05~0.21:

worldLandmarks 的原點是臀部中點;
坐在桌子後面、臀部看不到時,判斷動作先用 landmarks。

(五)Holistic:拿到結果之後

部位 點數 landmarks world landmarks
臉 478 有 沒有
身體 33 有 有
左手 21 有 有
右手 21 有 有

臉的 52 個表情分數(blendshapes)、身體的遮罩,預設關閉,要用再打開。

Holistic 只看一個人,所以不用像 Pose 先取 [0]:

val result = holisticLandmarker.detect(mpImage)

// 臉 478 點,只有 landmarks
val face = result.faceLandmarks()

// 身體 33 點,跟 Pose 一樣有兩組座標
val pose = result.poseLandmarks()
val poseWorld = result.poseWorldLandmarks()

// 左手、右手各 21 點;沒找到時是空的 List,不是 null
val left = result.leftHandLandmarks()
val right = result.rightHandLandmarks()
if (left.isEmpty()) return

上半身那張照片,實際拿到的結果:

faceLandmarks        478 點
poseLandmarks         33 點  15 左手腕 x=0.7729 y=0.2174 visibility=0.978
leftHandLandmarks     21 點
rightHandLandmarks    21 點

Holistic 怎麼找到手

Holistic 找手時,會參考身體的 15、17、19;
標 21 個特徵點用的是跟 Hand 同一個模型:

寫進 App 前,要知道的事

1. Android 請用 tasks-vision 1.1.0

同一張照片,分別用 1.0.0 和 1.1.0 跑:

1.0.0 1.1.0
建立 Holistic 閃退 正常
畫面裡沒有人,又打開額外輸出 閃退 正常,結果是空的
有人時的數值 一樣 一樣

建立就閃退,#6348 有人回報過;
第二列是照官方 Holistic 範例改了 protobuf 設定、能建立之後測的。
2026/10/07 剛發布的 v1.1.0,release notes 寫了這兩個問題已修正。

2. 手只有「有」或「沒有」

手的 21 個特徵點,本來就沒有 visibility、presence,Hand、Gesture 也一樣。

Hand、Gesture 的 handedness 分數,
是「左手還是右手」判斷得多確定,不是「有沒有手」。

Holistic 沒有 handedness,
要判斷有沒有手,就看左手、右手那兩組有沒有標記出特徵點。


四、舉左手的「左」,是誰的左邊?

(一)結論

這次測下來:

  • 模型的左右,都是「畫面裡那個人自己的左右」(依身體結構分的左手、右手)。
  • 送進模型的影格沒有鏡像 → Left 就是使用者本人的左手。
  • 影格鏡像後 → 左右對調。

跟 Day11 Face Landmarker 的 *Left/*Right 是同一個規則。

(二)實測:原圖 vs 左右翻轉

上半身那張照片(後鏡頭、沒有鏡像,舉起的是我的左手),
原圖和左右翻轉各跑一次:

輸入 Pose 舉起的手腕 Hand Gesture Holistic
原圖 15(左手腕) Left 0.989 Left 0.989 左手
左右翻轉 16(右手腕) Right 0.995 Right 0.995 右手

再用 A70 的前鏡頭,跑官方 Hand 範例(加了印出結果的版本),
每 0.5 秒記一筆左右手(只算有偵測到手的紀錄):

送進模型的影格 我舉左手 我舉右手
鏡像(官方範例的做法) Right 6/6 筆 Left 14/14 筆
不鏡像 Left 8/9 筆 Right 14/14 筆

不鏡像那組有 1 筆是 Right,那一筆的手腕在畫面底邊外。

(三)官方範例會先鏡像

官方的 Android 範例,相機的畫面送進模型之前,會先左右翻轉:

  • Hand:用前鏡頭時翻轉,
    註解寫「flip image if user use front camera」。
  • Gesture:相機的每一格都翻轉,
    註解寫「flip image since we only support front camera」。

兩個範例的畫面都不顯示左右手,範例本身沒有問題;
從範例開始、自己加上左右手判斷時,拿到的會跟使用者本人相反。

(四)提 issue 給官方

Hand、Gesture 指南只寫了
「Handedness represents whether the detected hands are left or right hands.」
沒有說左右以誰為準,也沒有說鏡像會不會讓左右對調。

Day11 提的 #6368,官方已經採納,
在 Face Landmarker 指南的總覽和各平台頁補上了說明。

這次也嘗試提 Documentation issue:
建議在指南加一句:
handedness 指的是畫面裡那個人自己的手;鏡像輸入會讓左右對調。
並附上重現腳本、原圖與翻轉的結果,以及 A70 鏡像、不鏡像的對照。

Issue 連結:#6376


五、同樣看手,結果一樣嗎?

Hand、Gesture、Holistic 都會給每隻手 21 個點。

同一張照片給三位看,結果並排:

照片 Hand Gesture Holistic
近拍:張開手掌(2 張) 找到 找到,手勢 None 找不到
近拍:比讚(3 張) 找到 找到,手勢 Thumb_Up 找不到
上半身:舉左手 找到 2 隻 找到 2 隻,手勢 None 找到 2 隻

(一)Hand 和 Gesture:點一樣

7 隻手的 21 個點(landmarks、world landmarks)、左右手、分數,
兩位給的數值相同,差距是 0。

Day24 提過,Gesture 的手部特徵點模型跟 Hand 是同一套;
只要點,用 Hand;要手勢名稱,用 Gesture,點會一起給。

(二)Holistic:看不到身體,就找不到手

近拍的 5 張,Holistic 一隻手都沒找到。

Holistic 找手時會參考身體的點(Day24);
這次近拍只有手,看不到身體,Holistic 就找不到手。

(三)手腕被遮住:有沒有前一格

Day24 留了一題:影片中途才遮住手腕,Holistic 還找得到嗎?

拿上半身那張照片,遮住舉起那隻手的手腕,比三種情況:

情況 遮罩圖 A 遮罩圖 B
第一張就遮住(新建 Task) 找不到 找到 21 點
先跑沒遮的,再跑遮住的(同一個 Task,IMAGE) 找到 21 點 找到 21 點
VIDEO 模式:先送沒遮的,再送遮住的 找到 21 點 找到 21 點

遮罩圖 A、B 是同一個遮法,遮住的位置也相同,只是分兩次存成 JPEG,
存檔雜訊讓每個像素平均差 0.5。

  • 中途才遮住:
    A、B 都找得到,身體的手腕點(Pose 15)停在上一張的位置(0.773, 0.218);
    新建 Task 時,這個點則被推到前臂(約 0.73, 0.26)。
    看起來 Holistic 會沿用上一格,IMAGE 模式用同一個 Task 時也一樣。

  • 第一張就遮住:
    A 找不到、B 找得到。

    我把「確認有手的門檻」從 0.1 調到 0.9 各跑一次:
    差別不在門檻:A 降到 0.1 還是找不到,B 拉到 0.9 還找得到。

    看起來是存檔的些微雜訊,讓 Pose 猜的手腕位置不同
    (A 在 0.732, 0.265、B 在 0.725, 0.248),
    Holistic 框出來的手部範圍跟著不同,結果就一個找不到、一個找得到。


六、手指比幾,誰認得出來?

(一)Pose:數不了

Day24 提過,Pose 每隻手只有 3 個指根關節的點,
看不出哪根手指伸直,所以數不了手指。

(二)Gesture:沒有數字,認得的手勢也常回 None

Gesture 的 7 種手勢裡沒有數字。

我比 1~5、握拳各拍一張(手指朝上、掌心朝鏡頭),用 A70 跑官方 Gesture 範例:

6 張裡,只有比 1 認成 Pointing_Up;比 2、張開手掌、握拳都回 None。

None 跟手的方向有關嗎?

把 Day24 那張張開手掌的照片轉幾個角度再跑:

13 個角度裡,Open_Palm 有 5 個,都在手指朝上附近(偏右 16° 到偏左 44°);
原圖朝右上 45°、偏右 30°,都是 None。

同一張照片左右翻轉後,認得出來的角度也不同;
比讚也一樣,13 個角度裡有 6 個認得出 Thumb_Up。

每個角度 Hand 都找到了手,None 是「認不出手勢」,不是沒找到手。

手的方向可能會影響結果;
但上面那張手指朝上的張開手掌,還是回 None。
用 Gesture 的手勢,要先拿自己要用的手勢實測。

(三)21 個點:自己數

Hand、Gesture、Holistic 都有 21 個點,可以自己寫規則數伸直的手指。
做法是看每根手指「第一節」和「最後一節」的方向:

  • 伸直:兩節方向差不多,夾角小。
  • 彎起來:最後一節折回來,夾角變大。
  • 拇指:
    除了夾角,還要看拇指尖離食指根夠不夠遠,
    避免拇指收在掌心時,被算成伸直。

角度用 world landmarks(公尺)的 3D 座標算,手離鏡頭遠近,不會讓角度變大變小。

import numpy as np

# 伸直的門檻:手指 60 度、拇指 30 度、拇指張開比 0.6
FINGER, THUMB, SPREAD = 60, 30, 0.6

# 每根手指的 4 個點:根部 → 指尖
JOINTS = [(1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12),
          (13, 14, 15, 16), (17, 18, 19, 20)]

def angle(u, v):
    cos = np.dot(u, v) / (np.linalg.norm(u) * np.linalg.norm(v))
    return np.degrees(np.arccos(np.clip(cos, -1, 1)))

# world:一隻手的 21 個 world landmarks,形狀 (21, 3)
def count_fingers(world):
    p = np.asarray(world)
    # 拇指尖到食指根的距離,除以手掌寬(食指根到小指根)
    spread = (np.linalg.norm(p[4] - p[5])
              / np.linalg.norm(p[5] - p[17]))
    count = 0
    for i, (a, b, c, d) in enumerate(JOINTS):
        # 第一節(a→b)和最後一節(c→d)的夾角
        bend = angle(p[b] - p[a], p[d] - p[c])
        if i == 0:
            straight = bend < THUMB and spread > SPREAD
        else:
            straight = bend < FINGER
        count += int(straight)
    return count


七、家人一起入鏡

家人陪長輩一起做時,畫面裡會有兩、三個人。
我只有一個人,所以把上半身那張照片複製並排,合成兩人、三人的畫面。

(一)人數上限要設夠

設定 兩人 三人
Pose,num_poses=1 抓到 1 人 抓到 1 人
Pose,num_poses=人數 都抓到 都抓到
Hand、Gesture,num_hands=2 只抓到兩人舉起的手 0 隻
Hand、Gesture,num_hands=人數×2 4 隻都抓到,左右正確 0 隻
Holistic 抓 1 人 抓 1 人

  • num_poses、num_hands 要設成人數(手要乘 2):
    設 1 時只抓其中一個人,不一定是左邊那位。
  • 結果的順序不是照畫面左右排:
    三人時,Pose 回傳的是第 3、1、2 個人,要自己依位置排序。
  • Holistic:最多只抓 1 個人;三人時抓過最右邊,也抓過中間那位。

(二)兩個人靠太近,手腕點會偏掉

第 2 個人左右翻轉、變成舉右手時,兩個人舉起的拳頭剛好靠在一起;
Pose 給的兩個手腕點,都往兩人中間偏。

兩人合成 舉起的手腕,跟單人照片的位置差
拳頭靠在一起 51、126 像素
中間留空隙 8、14 像素
兩人都舉左手(拳頭分開) 12、12 像素

(三)三個人並排:找不到手

三人並排的畫面寬 2343 像素,Hand、Gesture 一隻手都沒找到;
把每個人左右多餘的背景裁掉(寬 1407 像素),就找到 5 隻手。

手部模型說明:找手的模型,輸入是 192 x 192。

整張畫面縮成 192 x 192,手在模型看到的畫面裡也跟著縮小,
看起來是手在整張畫面裡只占一小塊,縮小後就找不到了。

家人一起入鏡:彼此坐開一點,每個人在畫面裡要夠大。


八、小結

  • 左右:「畫面裡那個人自己的左右」,鏡像會對調。
  • 同樣看手:
    Hand 和 Gesture 的點一樣;
    Holistic 找手會參考身體的點(這次近拍都沒找到),而且會沿用上一格。
  • 手指比幾:
    Pose 數不了;Gesture 沒有數字,認得的手勢這次也多半回 None;
    21 個點可以自己數,拇指最難判斷。
  • 家人一起入鏡:
    人數上限要設夠、結果自己依位置排;
    兩人的手靠在一起時手腕點會偏,三人並排時找不到手。

要看的東西不同,適合的俠客也不同。


九、預告

明天,請俠客進府:
把 Pose 放進健口動一動,帶長輩「暖身動一動」和「跟著音樂動一動」!

模型的輸出如何變成「舉左手」「拍手」?
家人和長輩一起做,App 怎麼判定誰沒跟上動作?

讓我們明天繼續看下去~

感謝有緣看到這邊的你~
希望佛菩薩也祝福你:🌟幸福平安 健康順遂🌟
南無觀世音菩薩🍀 南無地藏菩薩🏠 南無阿彌陀佛☀️


上一篇
少年,我看你骨骼精奇。Google MediaPipe Pose、Hand、Gesture、Holistic 你聽過嗎?(上)
下一篇
少年,我看你骨骼精奇。Google MediaPipe Pose、Hand、Gesture、Holistic 你聽過嗎?(下)
系列文
Build on Google AI :長者照護 —— 口腔機能訓練 與 延緩認知退化 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言