Jump to content
Main menu
Main menu
move to sidebar
hide
Navigation
Main page
Recent changes
Random page
freem
Search
Search
Appearance
Create account
Log in
Personal tools
Create account
Log in
Pages for logged out editors
learn more
Contributions
Talk
Editing
Openai/692fcfd8-4b44-800f-a184-fa098175a6d5
(section)
Add languages
Page
Discussion
English
Read
Edit
Edit source
View history
Tools
Tools
move to sidebar
hide
Actions
Read
Edit
Edit source
View history
General
What links here
Related changes
Special pages
Page information
Appearance
move to sidebar
hide
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
==== : ==== 請整份複製貼上: <syntaxhighlight lang="python"># hand_gesture_1_10.py === Python 3.7 版:手勢 1~10 辨識+教學面板+國語/台語語音 === from cvzone.HandTrackingModule import HandDetector import cv2 import math # 雖然不用 math.dist,但保留 math 以備後續若要加東西 import os import time import pygame import numpy as np === ========= 小工具:2D 距離(取代 math.dist) ========= === def euclidean(a, b): """ 計算 2D 歐幾里得距離 a, b: [x, y, (z)] 形式的 list 或 tuple,這裡只用前兩個值 """ return ((a[0] - b[0]) ''' 2 + (a[1] - b[1]) ''' 2) ** 0.5 === ========== 初始設定 ========== === === 攝影機:0 通常是內建鏡頭;如有外接可換成 1 === cap = cv2.VideoCapture(0) # 若遇到裝置問題,可試 cv2.VideoCapture(0, cv2.CAP_DSHOW) === 手部偵測器(最多 2 隻手,為了辨識 10) === detector = HandDetector(detectionCon=0.7, maxHands=2) === 初始化 pygame 音效系統 === pygame.mixer.init() === 台語 POJ 對照 === tai_map = { 1: "it", 2: "nn̄g", 3: "saⁿ", 4: "sì", 5: "gō͘", 6: "la̍k", 7: "chhit", 8: "peh", 9: "káu", 10: "chap", } === 中文數字對照 === zh_map = { 1: "一", 2: "二", 3: "三", 4: "四", 5: "五", 6: "六", 7: "七", 8: "八", 9: "九", 10: "十", } === ========= 讀取手勢示意圖 ========= === gesture_images = {} for n in range(1, 11): path = os.path.join("gestures", f"{n}.png") if os.path.exists(path): img_g = cv2.imread(path) if img_g is not None: gesture_images[n] = img_g else: print("[提示] 找不到手勢示意圖:{}".format(path)) === ========= 播放語音 ========= === def play_audio(number): """ 播放對應數字的國語 + 台語語音 需要 audio/zh_N.mp3, audio/tai_N.mp3 已存在 """ for lang in ["zh", "tai"]: fn = os.path.join("audio", "{}_{}.mp3".format(lang, number)) if os.path.exists(fn): try: pygame.mixer.music.load(fn) pygame.mixer.music.play() # 簡單阻塞等待播放完畢 while pygame.mixer.music.get_busy(): time.sleep(0.05) except Exception as e: print("[錯誤] 播放 {} 失敗:{}".format(fn, e)) else: print("[提示] 找不到音檔:{}".format(fn)) === ========= 手勢判斷(單手 1~9) ========= === def classify_single_hand_number(hand, fingers): """ 根據單手的 fingersUp 結果判斷數字 1~9 hand: 單手資訊 (包含 lmList, bbox, type 等) fingers: [thumb, index, middle, ring, pinky] (0/1) 回傳 (number, label) 或 (None, "None") """ pattern = tuple(fingers) # 固定手勢模式(不含「只伸食指」,那留給 1/9 特判) mapping = { (0, 1, 1, 0, 0): (2, "2"), # 食 + 中 (1, 1, 1, 0, 0): (3, "3"), # 拇 + 食 + 中 (0, 1, 1, 1, 1): (4, "4"), # 四指伸、拇指收 (1, 1, 1, 1, 1): (5, "5"), # 全部伸出 (1, 0, 0, 0, 1): (6, "6"), # 拇 + 小(shaka) (1, 1, 0, 0, 1): (7, "7"), # 拇 + 食 + 小 (1, 1, 0, 0, 0): (8, "8"), # 拇 + 食(手槍) } if pattern in mapping: return mapping[pattern] # --- 特判:1 或 9(只伸食指)--- if pattern == (0, 1, 0, 0, 0): lm_list = hand["lmList"] # 食指:MCP = 5, PIP = 6, DIP = 7, TIP = 8 mcp = lm_list[5] pip = lm_list[6] dip = lm_list[7] tip = lm_list[8] # 直線距離 MCP→TIP dist_mcp_tip = euclidean(mcp, tip) # 折線距離 MCP→PIP→DIP→TIP dist_mcp_pip = euclidean(mcp, pip) dist_pip_dip = euclidean(pip, dip) dist_dip_tip = euclidean(dip, tip) poly_len = dist_mcp_pip + dist_pip_dip + dist_dip_tip # 粗略規則:如果「折線 / 直線」比例大於 1.25,視為彎曲很多 = 9 if dist_mcp_tip > 0 and poly_len / dist_mcp_tip > 1.25: return (9, "9") else: return (1, "1") return (None, "None") === ========= 判斷整體數字(包含 10) ========= === def classify_number(hands): """ 根據全部手部資料判斷數字 1~10 回傳 (number, label, bbox_for_draw) """ if not hands: return None, "No Hand", None # 先判斷「10」:兩手都比 1 if len(hands) >= 2: nums = [] for h in hands[:2]: fingers = detector.fingersUp(h) n, _ = classify_single_hand_number(h, fingers) nums.append(n) if nums[0] == 1 and nums[1] == 1: bbox10 = hands[0]["bbox"] return 10, "10", bbox10 # 否則只看第一隻手的 1~9 main_hand = hands[0] bbox = main_hand["bbox"] fingers = detector.fingersUp(main_hand) number, label = classify_single_hand_number(main_hand, fingers) if number is None: return None, "Unknown", bbox else: return number, label, bbox === ========= 主迴圈 ========= === last_number = None # 上一次辨識結果,避免每幀都重播聲音 while cap.isOpened(): success, img = cap.read() if not success: break # 偵測手部 hands, img = detector.findHands(img) # 判斷數字 number, label, bbox = classify_number(hands) # 原始畫面大小 h, w, _ = img.shape # 建立右側教學面板(白底) panel_width = 320 panel = 255 * np.ones((h, panel_width, 3), dtype="uint8") # ===== 在教學面板顯示內容 ===== if number is not None and number in tai_map: # 顯示手勢示意圖(若存在) if number in gesture_images: g_img = gesture_images[number] gh, gw, _ = g_img.shape # 讓示意圖高度約畫面 60%,寬度約 panel 90% scale = min((h '' 0.6) / float(gh), (panel_width '' 0.9) / float(gw)) new_w = int(gw * scale) new_h = int(gh * scale) g_resized = cv2.resize(g_img, (new_w, new_h)) # 貼到 panel 置中(上方) x_offset = (panel_width - new_w) // 2 y_offset = 20 panel[y_offset:y_offset + new_h, x_offset:x_offset + new_w] = g_resized # 文字資訊(下半部) text_y = int(h * 0.75) cv2.putText(panel, str(number), (30, text_y), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 0), 2) cv2.putText(panel, "中文:{}".format(zh_map[number]), (30, text_y + 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 2) cv2.putText(panel, "台語:{}".format(tai_map[number]), (30, text_y + 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 2) else: cv2.putText(panel, "請比出 1~10 的手勢", (20, h // 2), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (128, 128, 128), 2) # 在原影像畫偵測框與標籤 if bbox is not None and number is not None: x, y, bw, bh = bbox cv2.rectangle(img, (x, y), (x + bw, y + bh), (0, 255, 0), 2) cv2.putText(img, label, (x, y - 10), cv2.FONT_HERSHEY_PLAIN, 2, (0, 255, 0), 2) # 將攝影機畫面與教學面板合併(左右排) panel = cv2.resize(panel, (panel_width, h)) combined = cv2.hconcat([img, panel]) cv2.imshow("Hand Number Teaching (1-10) - Python 3.7", combined) # 若辨識數字有變化 → 播放語音 if number is not None and number in tai_map and number != last_number: last_number = number print("辨識到數字:", number) play_audio(number) # 按 q 離開 if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() cv2.destroyAllWindows() </syntaxhighlight>
Summary:
Please note that all contributions to freem are considered to be released under the Creative Commons Attribution-ShareAlike 4.0 (see
Freem:Copyrights
for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)