Spaces:
Running
Running
| #!/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() | |