呼び出しモデル: この族は
apply(img, "<op名>", a, b)の 2 つまみ規約ではなく、 ファサード関数(import fullseye; fullseye.hand_landmarks(...))です。 検出結果が dict のリストという構造化データで、a/b の連続つまみに馴染まないため。 検証済みテスト:tests/test_handpose.py(幾何 = 伸びきり 0° / 直角 90° / 描画非破壊 / fail-closed を機械検証)。
写真・動画から手の 21 キーポイント(手首 1 + 各指 4×5)を取り、そこから 指 5 本の屈曲角を計算するための入口です。位置づけは Physical AI ブリッジ —— 「人の動きを見て、ロボットに写す」パイプラインの手版の第一歩です。
hand_landmarks(image) — 画像 → 手ごとの 21 点。画像正規化座標(描画用)と
world 座標(メートル、手の幾何中心原点)の両方を返します。関節角の計算は
遠近で歪まない world 側を使います。finger_flexions(det) — 1 検出 → 指 5 本の屈曲角[deg]。伸びきり ~0°、
直角 90°、握り込み ~180°+。リターゲットの入口(屈曲量を [0,1] に正規化
すればロボットハンドの相反駆動指令の最初の近似になる)。hand_skeleton_edges() — 21 点の骨格結線(手首→付け根、指内、掌の橋)。
描画やグラフ解析(骨格 op 族と同じ語彙)に。draw_hand_landmarks(image, dets) — numpy だけで点+骨を描いた注釈画像。検出だけ optional extra です。mediapipe(Apache-2.0)と手モデル
hand_landmarker.task(~8MB、Apache-2.0)が要り、どちらか欠けると
導入手順つきの明示エラーを送出します(黙って空リストは返しません)。
幾何側(finger_flexions / hand_skeleton_edges / draw_hand_landmarks)は
numpy のみで動きます。
py -3.11 -m pip install mediapipe
curl -L -o %USERPROFILE%/.cache/fullseye/hand_landmarker.task ^
https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task
flowchart LR
img["写真 / 動画フレーム<br/>image(HxW / HxWx3)"] --> det["hand_landmarks<br/>21 点 x 手"]
det --> flex["finger_flexions<br/>指 5 本の屈曲角[deg]"]
det --> draw["draw_hand_landmarks<br/>注釈画像"]
flex --> ret["正規化 [0,1]<br/>→ ロボットハンド指令(構想)"]
det -.->|骨格語彙| skel["hand_skeleton_edges<br/>(junctions/branches と同じ発想)"]
import numpy as np
import handpose as H
# --- 検出(mediapipe + モデルが要る。無ければ導入手順つきエラー)---
# dets = H.hand_landmarks(rgb_image) # list[dict]
# --- 幾何は numpy だけで動く: 合成 world 座標で屈曲角を確かめる ---
w = np.zeros((21, 3))
for f, chain in enumerate(H.FINGERS.values()):
d = np.array([1.0, 0.15 * f, 0.0]); d /= np.linalg.norm(d)
for j, idx in enumerate(chain):
w[idx] = 0.03 * (j + 1) * d # 全指伸びきり(手首から放射直線)
flex = H.finger_flexions({"world_landmarks": w})
assert all(abs(v) < 0.1 for v in flex.values()) # 伸びきり = 0°
mcp, pip, dip, tip = H.FINGERS["index"]
w[dip] = w[pip] + (0.0, 0.0, 0.03) # 人差し指を PIP で直角に折る
w[tip] = w[dip] + (0.0, 0.0, 0.03)
flex = H.finger_flexions({"world_landmarks": w})
assert abs(flex["index"] - 90.0) < 0.1 # 直角 = 90°
print({k: round(v, 1) for k, v in flex.items()})
デモ(画像 1 枚 / Web カメラ 1 フレーム):
examples/hand_tracking_demo.py
finger_flexions は world 前提)。handedness は解剖学的左右(鏡像の自撮り映像では見た目と
逆になり得る)。junctions_skeleton / skeleton_branches3d など)—— 「点と結線で
形を読む」同じ発想の 2D/3D 版。