rock_paper_scissors / debug_gesture.py
trtd56's picture
Initial commit
24836e5
#!/usr/bin/env python3
"""ジェスチャー認識のデバッグスクリプト
カメラから手を検出し、各指の状態と判定結果をリアルタイムで表示します。
"""
import cv2
import numpy as np
from rock_paper_scissors.detection import HandDetector, GestureDetector
from rock_paper_scissors.detection.gesture_detector import (
LandmarkIndex,
FINGER_EXTENDED_ANGLE_THRESHOLD,
FINGER_CURLED_ANGLE_THRESHOLD,
)
from rock_paper_scissors.game.states import Hand
def calculate_finger_angle(landmarks: np.ndarray, mcp_idx: int, pip_idx: int, dip_idx: int) -> float:
"""指の曲がり角度を計算"""
mcp = landmarks[mcp_idx][:2]
pip = landmarks[pip_idx][:2]
dip = landmarks[dip_idx][:2]
v1 = mcp - pip
v2 = dip - pip
norm1 = np.linalg.norm(v1)
norm2 = np.linalg.norm(v2)
if norm1 < 1e-10 or norm2 < 1e-10:
return 180.0
cos_angle = np.dot(v1, v2) / (norm1 * norm2)
angle = np.arccos(np.clip(cos_angle, -1.0, 1.0))
return np.degrees(angle)
def get_finger_debug_info(landmarks: np.ndarray) -> dict:
"""各指の詳細情報を取得"""
info = {}
# 各指の情報
fingers = {
"index": (LandmarkIndex.INDEX_MCP, LandmarkIndex.INDEX_PIP, LandmarkIndex.INDEX_DIP, LandmarkIndex.INDEX_TIP),
"middle": (LandmarkIndex.MIDDLE_MCP, LandmarkIndex.MIDDLE_PIP, LandmarkIndex.MIDDLE_DIP, LandmarkIndex.MIDDLE_TIP),
"ring": (LandmarkIndex.RING_MCP, LandmarkIndex.RING_PIP, LandmarkIndex.RING_DIP, LandmarkIndex.RING_TIP),
"pinky": (LandmarkIndex.PINKY_MCP, LandmarkIndex.PINKY_PIP, LandmarkIndex.PINKY_DIP, LandmarkIndex.PINKY_TIP),
}
for name, (mcp_idx, pip_idx, dip_idx, tip_idx) in fingers.items():
tip = landmarks[tip_idx]
pip = landmarks[pip_idx]
mcp = landmarks[mcp_idx]
# 距離計算
tip_to_mcp = np.linalg.norm(tip[:2] - mcp[:2])
pip_to_mcp = np.linalg.norm(pip[:2] - mcp[:2])
ratio = tip_to_mcp / pip_to_mcp if pip_to_mcp > 0 else 0
# 角度計算
angle = calculate_finger_angle(landmarks, mcp_idx, pip_idx, dip_idx)
# Y座標の比較
tip_above_pip = tip[1] < pip[1]
# 判定
distance_extended = tip_above_pip and ratio > 0.9
angle_curled = angle < FINGER_CURLED_ANGLE_THRESHOLD
# 最終判定(距離ベースで伸びていると判定されても、角度が小さければ閉じている)
is_extended = distance_extended and not angle_curled
info[name] = {
"tip_y": tip[1],
"pip_y": pip[1],
"tip_above_pip": tip_above_pip,
"distance_ratio": ratio,
"angle": angle,
"distance_extended": distance_extended,
"angle_curled": angle_curled,
"is_extended": is_extended,
}
# 親指
thumb_tip = landmarks[LandmarkIndex.THUMB_TIP]
thumb_ip = landmarks[LandmarkIndex.THUMB_IP]
thumb_mcp = landmarks[LandmarkIndex.THUMB_MCP]
tip_to_mcp = np.linalg.norm(thumb_tip[:2] - thumb_mcp[:2])
ip_to_mcp = np.linalg.norm(thumb_ip[:2] - thumb_mcp[:2])
thumb_extended = tip_to_mcp > ip_to_mcp * 1.2
info["thumb"] = {
"tip_to_mcp": tip_to_mcp,
"ip_to_mcp": ip_to_mcp,
"ratio": tip_to_mcp / ip_to_mcp if ip_to_mcp > 0 else 0,
"is_extended": thumb_extended,
}
return info
def main():
print("=" * 60)
print("ジェスチャー認識デバッグツール")
print("=" * 60)
print(f"角度閾値: 伸びている >= {FINGER_EXTENDED_ANGLE_THRESHOLD}°, 曲がっている < {FINGER_CURLED_ANGLE_THRESHOLD}°")
print("距離閾値: 0.9 (tip_to_mcp / pip_to_mcp)")
print("'q' で終了")
print("=" * 60)
hand_detector = HandDetector()
gesture_detector = GestureDetector()
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("カメラを開けませんでした")
return
try:
while True:
ret, frame = cap.read()
if not ret:
break
# 左右反転(鏡像)
frame = cv2.flip(frame, 1)
# 手を検出
hand_data = hand_detector.detect(frame)
if hand_data is not None:
landmarks = hand_data.landmarks
# ジェスチャー判定
gesture, confidence = gesture_detector.detect(landmarks)
# デバッグ情報を取得
debug_info = get_finger_debug_info(landmarks)
# 画面に表示
y_offset = 30
# ジェスチャー結果
gesture_text = f"Gesture: {gesture.value} (conf: {confidence:.2f})"
color = (0, 255, 0) if gesture != Hand.UNKNOWN else (0, 0, 255)
cv2.putText(frame, gesture_text, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2)
y_offset += 35
# 拡張カウント
extended_count = sum(1 for name in ["thumb", "index", "middle", "ring", "pinky"]
if debug_info[name]["is_extended"])
cv2.putText(frame, f"Extended fingers: {extended_count}", (10, y_offset),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
y_offset += 25
# 各指の情報
for finger_name in ["thumb", "index", "middle", "ring", "pinky"]:
info = debug_info[finger_name]
if finger_name == "thumb":
text = f"{finger_name}: ext={info['is_extended']} (ratio={info['ratio']:.2f})"
else:
text = f"{finger_name}: ext={info['is_extended']} (ratio={info['distance_ratio']:.2f}, angle={info['angle']:.0f}°)"
color = (0, 255, 0) if info["is_extended"] else (0, 0, 255)
cv2.putText(frame, text, (10, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1)
y_offset += 20
# ランドマークを描画
for i, lm in enumerate(landmarks):
x = int(lm[0] * frame.shape[1])
y = int(lm[1] * frame.shape[0])
cv2.circle(frame, (x, y), 3, (0, 255, 255), -1)
cv2.putText(frame, str(i), (x + 5, y), cv2.FONT_HERSHEY_SIMPLEX, 0.3, (255, 255, 255), 1)
# コンソールにも出力(1秒に1回)
import time
if not hasattr(main, 'last_print') or time.time() - main.last_print > 1.0:
main.last_print = time.time()
print(f"\n--- {gesture.value} (conf: {confidence:.2f}) ---")
for finger_name in ["thumb", "index", "middle", "ring", "pinky"]:
info = debug_info[finger_name]
ext_str = "○" if info["is_extended"] else "×"
if finger_name == "thumb":
print(f" {finger_name}: {ext_str} (ratio={info['ratio']:.2f})")
else:
print(f" {finger_name}: {ext_str} (ratio={info['distance_ratio']:.2f}, angle={info['angle']:.0f}°, tip_above={info['tip_above_pip']})")
else:
cv2.putText(frame, "No hand detected", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
cv2.imshow("Gesture Debug", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
finally:
cap.release()
cv2.destroyAllWindows()
hand_detector.close()
if __name__ == "__main__":
main()