Python開發基礎手語識別(基礎框架版)

一、前期準備

想要實現這些,首先就是要模擬出來一個大致的框架,方便后續開展,下面的就是隨便寫的一個框架,大家湊合看看就行,基本上是這個意思:

from tkinter import *w = Tk()
w.title("手語識別(簡易)")
w.geometry("805x640")l1 = Label(text='此窗口實時顯示\n攝像頭拍攝畫面', font=("微軟雅黑", 20),width=25,height=15,relief='groove', borderwidth=2)
l1.place(x=0, y=0)l2 = Label(text='此窗口實時顯示\n手部骨骼繪畫', font=("微軟雅黑", 20),width=25,height=15,relief='groove', borderwidth=2)
l2.place(x=400, y=0)l3 = Label(text='此窗口實時顯示手語識別結果', font=("微軟雅黑", 20),width=50,height=3,relief='groove', borderwidth=2)
l3.place(x=0, y=530)w.mainloop()

運行效果大概也就這樣:

?解決了框架的問題之后,就要開始進一步的實現框架里面的內容了。

二、程序實現

1.相關庫

目前大多數的寫法基本上都是是用open-cv和PIL庫來實現,但是PIL庫容易暴雷,很抽象,實際開發中不建議使用PIL庫進行開發,這里就更推薦使用Pillow庫,因為因為原始PIL開發停滯,Pillow?是其友好分支,功能兼容且持續維護,安裝Pillow?即可替代PIL使用。

簡單說明一下open-cv和Pillow的相關用法

open-cv核心語法

1. 圖像讀取與顯示import cv2# 讀取圖像(返回 BGR 格式的 NumPy 數組)
img = cv2.imread("image.jpg")  # 路徑支持中文,需用 UTF-8 編碼# 顯示圖像(需配合 cv2.waitKey() 使用)
cv2.imshow("Image Window", img)
cv2.waitKey(0)  # 0 表示無限等待,按任意鍵關閉窗口
cv2.destroyAllWindows()  # 銷毀所有窗口2. 圖像基本操作# 轉換為灰度圖
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 縮放圖像(插值方法可選:cv2.INTER_AREA 適合縮小,cv2.INTER_LINEAR 適合放大)
resized_img = cv2.resize(img, (640, 480), interpolation=cv2.INTER_LINEAR)# 旋轉圖像(繞中心旋轉 45 度,縮放因子 1.0)
(h, w) = img.shape[:2]
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, 45, 1.0)
rotated_img = cv2.warpAffine(img, M, (w, h))3. 視頻處理(攝像頭實時流)# 打開攝像頭(參數 0 表示默認攝像頭,1 表示外接攝像頭)
cap = cv2.VideoCapture(0)while True:ret, frame = cap.read()  # ret 為布爾值,表示是否讀取成功if not ret:break# 在視頻幀上繪制矩形cv2.rectangle(frame, (100, 100), (300, 300), (0, 255, 0), 2)# 顯示視頻幀cv2.imshow("Video Stream", frame)# 按 'q' 鍵退出循環if cv2.waitKey(1) & 0xFF == ord('q'):breakcap.release()  # 釋放攝像頭資源
cv2.destroyAllWindows()4. 繪圖與標注# 在圖像上繪制文字
font = cv2.FONT_HERSHEY_SIMPLEX  # 字體類型
cv2.putText(img,            # 目標圖像"Hand Detected",# 文本內容(50, 50),       # 文本位置坐標font,           # 字體1.0,            # 字體大小(0, 255, 0),    # 顏色(BGR 格式)2,              # 線條粗細cv2.LINE_AA     # 抗鋸齒
)# 繪制圓形
cv2.circle(img, (200, 200), 50, (255, 0, 0), -1)  # -1 表示填充圓形

Pillow核心語法

1. 圖像讀取與保存from PIL import Image# 讀取圖像(返回 Image 對象)
img = Image.open("image.png")# 保存圖像(自動根據擴展名判斷格式,支持格式轉換)
img.save("output.jpg")  # 從 PNG 轉為 JPEG
img.save("output.png", quality=95)  # 保存為 PNG,設置質量(對支持的格式有效)2. 圖像尺寸與模式操作# 獲取圖像尺寸(寬度, 高度)
width, height = img.size# 轉換圖像模式(如灰度圖、RGB 圖)
gray_img = img.convert("L")  # "L" 表示灰度模式
rgb_img = img.convert("RGB") # 確保為 RGB 模式(某些操作需要)3. 圖像編輯操作# 縮放圖像(使用高質量抗鋸齒)
resized_img = img.resize((200, 200), Image.Resampling.LANCZOS)# 裁剪圖像(左上角坐標 (x1,y1),右下角坐標 (x2,y2))
cropped_img = img.crop((50, 50, 250, 250))# 水平翻轉圖像
flipped_img = img.transpose(Image.FLIP_LEFT_RIGHT)4. 像素級操作與繪圖# 獲取像素值(坐標 (x,y),返回 RGB 元組)
pixel_color = img.getpixel((100, 100))# 修改像素值(將 (200,200) 坐標設為紅色)
img.putpixel((200, 200), (255, 0, 0))  # RGB 格式# 使用 ImageDraw 繪制圖形
from PIL import ImageDrawdraw = ImageDraw.Draw(img)
draw.rectangle([(10, 10), (100, 100)], outline=(0, 255, 0), width=2)  # 繪制矩形
draw.ellipse([(150, 150), (250, 250)], fill=(255, 0, 0))          # 繪制填充橢圓5. 批量圖像處理import os
from PIL import Imageinput_folder = "images/"
output_folder = "processed/"# 創建輸出文件夾(若不存在)
os.makedirs(output_folder, exist_ok=True)for filename in os.listdir(input_folder):if filename.endswith((".jpg", ".png")):file_path = os.path.join(input_folder, filename)with Image.open(file_path) as img:# 統一縮放為 500x500 像素resized = img.resize((500, 500), Image.Resampling.BILINEAR)# 轉換為灰度圖gray = resized.convert("L")# 保存到輸出文件夾gray.save(os.path.join(output_folder, filename))

他們倆的關鍵語法對比

功能OpenCV(Python)Pillow(PIL)
讀取圖像cv2.imread("path")Image.open("path")
顯示圖像cv2.imshow("window", img); cv2.waitKey(0)需要結合 Tkinter/Qt 等 GUI 庫顯示
圖像格式轉換cv2.cvtColor(img, cv2.COLOR_BGR2RGB)img.convert("RGB")
縮放圖像cv2.resize(img, (w,h), interpolation=...)img.resize((w,h), Image.Resampling.LANCZOS)
繪制文字cv2.putText(img, text, (x,y), font, ...)ImageDraw.Draw(img).text((x,y), text, fill=...)
獲取圖像尺寸h, w = img.shape[:2]width, height = img.size

?ps:上述不是很全面,僅作參考

好啦,回到正題,該逐步實現調用過程啦!

?2.攝像頭調用的具體代碼實現

下面的我自己的代碼,先發出來給大伙瞅瞅,稍后詳細解釋代碼
?

import cv2
import tkinter as tk
from PIL import Image, ImageTkdef update_camera():ret, frame = cap.read()if ret:# 水平鏡像翻轉畫面(參數1表示水平翻轉)frame_flipped = cv2.flip(frame, 1)# 攝像頭畫面顯示在l1(添加鏡像)frame_rgb = cv2.cvtColor(frame_flipped, cv2.COLOR_BGR2RGB)frame_resized = cv2.resize(frame_rgb, (400, 400))img = Image.fromarray(frame_resized)imgtk = ImageTk.PhotoImage(image=img)l1.imgtk = imgtkl1.configure(image=imgtk)# 手部骨骼繪制顯示在l2(預留位置)# 識別結果顯示在l3(預留位置)w.after(10, update_camera)# 初始化攝像頭
cap = cv2.VideoCapture(0)# 創建窗口
w = tk.Tk()
w.title("手語識別(簡易)")
w.geometry("805x640")# Label用于攝像頭畫面(鏡像顯示)
l1 = tk.Label(text='攝像頭加載中...', font=("微軟雅黑", 20), width=25, height=15, relief='groove', borderwidth=2)
l1.place(x=0, y=0)# Label用于手部骨骼繪制(預留)
l2 = tk.Label(text='此窗口實時顯示\n手部骨骼繪畫', font=("微軟雅黑", 20), width=25, height=15, relief='groove', borderwidth=2)
l2.place(x=400, y=0)# Label用于識別結果(預留)
l3 = tk.Label(text='此窗口實時顯示手語識別結果', font=("微軟雅黑", 20), width=50, height=3, relief='groove', borderwidth=2)
l3.place(x=0, y=530)# 啟動攝像頭更新
update_camera()w.mainloop()
cap.release()

好啦,現在來一步一步的理解上面的代碼:

1. 導入依賴庫

import cv2          # 計算機視覺庫,用于攝像頭控制和圖像處理
import tkinter as tk  # GUI 庫,用于創建窗口和界面元素
from PIL import Image, ImageTk  # 圖像處理庫,用于圖像格式轉換以適配 Tkinter

欸?為什么導入tkinter要用tk,直接*不更好嘛?我一開始也是這樣想的,但是這里就有一個很致命的錯誤,因此我在導入這個地方卡了很久很久……

為啥捏??在 Python 中,from tkinter import *?和?import tkinter as tk?是兩種不同的導入方式,前者的*代表了全部導入,這就代表Python 會將?tkinter?模塊中的?所有公有名稱(如?TkLabelButton?等)直接導入到當前命名空間。這意味著:

  • 無需通過模塊名前綴(如?tk.)即可直接使用這些名稱。
  • 如果當前命名空間中已有同名對象(如自定義的?Button?函數),會發生?名稱沖突,導致程序報錯或邏輯混亂。

?如果我直接使用的話,就會一直報錯,恰好大伙們還不知道這個小知識點的話,就很難發現自己錯在啥地方!!!!如果真不小心了咋辦?就會出現下面的問題:

  • 覆蓋內置函數或變量
    例如,若代碼中定義了?Tk = "my_string",則?from tkinter import *?會嘗試將?tkinter.Tk(窗口類)導入為?Tk,導致?Tk?被重新賦值為字符串,引發錯誤。
  • 難以追蹤來源
    當代碼中出現?Button?時,無法直接判斷它是?tkinter.Button?還是其他模塊 / 自定義的?Button,增加調試難度。
  • 破壞代碼可讀性
    對于大型項目,未加前綴的名稱會讓讀者難以快速識別其所屬模塊,尤其是在多個模塊被?import *?的情況下。

所以這是一個很抽象的錯誤,也是一個很小的知識點,一般來說,我們在系統性學習python的時候,是直接學的第二種方法,第一種老師也會講,但是不會細講,因為考試也不考,我們平時也接觸不到這些比較難的庫,所以這個方面的小知識點就很容易被忽略。?

至于為啥第二種好,老師也不會說,同樣考試也不考,我就來簡要的說一下,過兩天我整理一下,跟這篇一起發出來:

1. 避免命名污染,確保名稱唯一性

  • 隔離命名空間
    將 Tkinter 的所有名稱(如?TkLabel)封裝在?tk?模塊內,避免與當前代碼中的自定義變量、函數或其他庫(如?custom_widgets)的同名對象沖突。
    示例:若代碼中已有?Button?函數,tk.Button?仍指向 Tkinter 的按鈕類,不會被覆蓋。

  • 明確歸屬
    所有 Tkinter 對象均以?tk.?為前綴(如?tk.Entry),清晰標識其來源,避免混淆。

2. 提升代碼可讀性和可維護性

  • 快速定位來源
    看到?tk.Canvas?即可明確其為 Tkinter 的畫布類,無需查閱導入語句或猜測名稱來源。
    對比from tkinter import *?中?Canvas?的歸屬不明確,可能來自其他模塊。

  • 協作友好
    在團隊項目中,前綴可幫助其他開發者快速識別框架組件,降低理解成本。

3. 減少內存占用與啟動開銷

  • 按需加載
    僅導入?tkinter?模塊本身,而非其所有成員。對于大型模塊,可減少初始加載時的內存占用和啟動時間。
    原理import *?會一次性導入模塊內所有公有對象,而?import as?僅創建模塊引用。

4. 兼容大型項目與復雜場景

  • 多庫共存
    當同時使用 Tkinter 和其他 GUI 庫(如 PyQt、wxPython)時,前綴可避免跨庫名稱沖突。

    import tkinter as tk       # Tkinter 組件前綴為 tk.
    from PyQt5 import QtCore   # PyQt 組件前綴為 QtCore.
    
  • 模塊化開發
    便于將 Tkinter 相關代碼封裝在獨立模塊中,通過?tk.?前綴明確接口邊界,提升代碼組織性。

5. 符合 Python 最佳實踐(PEP 8 規范)

  • 官方推薦
    PEP 8 明確建議避免使用?from module import *,除非是交互式環境或極小型腳本。
    • 理由:命名空間污染可能導致隱性錯誤,且違反 “明確優于隱含” 的 Python 哲學。

2. 核心函數:攝像頭畫面更新

def update_camera():ret, frame = cap.read()  # 讀取攝像頭一幀畫面if ret:  # ret 為 True 表示讀取成功# 水平鏡像翻轉畫面(參數1表示水平翻轉,0為垂直翻轉,-1為水平+垂直翻轉)frame_flipped = cv2.flip(frame, 1)# ---------------------- 顯示原始鏡像畫面到 l1 ----------------------# OpenCV 默認顏色格式為 BGR,需轉為 RGB 以正確顯示frame_rgb = cv2.cvtColor(frame_flipped, cv2.COLOR_BGR2RGB)# 縮放畫面至 400x400 像素(適配窗口大小)frame_resized = cv2.resize(frame_rgb, (400, 400))# 將 OpenCV 的 NumPy 數組轉為 PIL 圖像對象img = Image.fromarray(frame_resized)# 將 PIL 圖像轉為 Tkinter 可用的 PhotoImage 對象imgtk = ImageTk.PhotoImage(image=img)# 將圖像綁定到 l1 標簽,并更新顯示l1.imgtk = imgtk  # 保留引用避免被垃圾回收l1.configure(image=imgtk)# 遞歸調用自身,每 10ms 更新一次畫面(實現實時效果)w.after(10, update_camera)

關鍵細節

  • 鏡像翻轉cv2.flip(frame, 1)?使畫面左右對稱,符合人類視覺習慣。
  • 顏色轉換:OpenCV 的?cv2.cvtColor?將 BGR 轉為 RGB,否則畫面顏色會錯亂。
  • 圖像格式轉換鏈
    OpenCV數組(BGR) → cvtColor → RGB數組 → PIL.Image → ImageTk.PhotoImage → Tkinter顯示
    
    這是在 Tkinter 中顯示 OpenCV 畫面的標準流程。

3. 初始化攝像頭

cap = cv2.VideoCapture(0)  # 0 表示打開默認攝像頭(筆記本內置或外接攝像頭)
  • cv2.VideoCapture(n)?中?n?為攝像頭設備編號,0?通常為默認攝像頭,1?為外接攝像頭。
  • 若攝像頭無法打開,cap.read()?會返回?ret=False,畫面停止更新。

4. 啟動程序主循環和資源釋放

update_camera()  # 調用函數啟動攝像頭畫面更新
w.mainloop()     # Tkinter 主循環,保持窗口顯示
cap.release()    # 釋放攝像頭資源,避免硬件占用
  • w.mainloop()?是 GUI 程序的入口,用于處理用戶交互(如關閉窗口)。
  • cap.release()?必須在主循環結束后調用,否則可能導致攝像頭無法正常關閉。

3.手部骨骼實現?

想要實現手部骨骼,就得來到另一個庫了----MediaPipe庫,MediaPipe 是?Google 開發的開源跨平臺機器學習框架,專注于實時多媒體處理計算機視覺任務,提供預訓練模型和模塊化工具,可快速開發手勢識別、人臉識別等 AI 應用。

核心特點

  1. 多模態感知能力
    • 支持手部追蹤(21 個關鍵點)、人臉檢測(468 個關鍵點)、人體姿態估計(33 個關鍵點)、物體檢測與追蹤等。
  2. 跨平臺與多語言
    • 支持 Python、C++、Java、JavaScript 等語言,覆蓋桌面、移動(Android/iOS)、邊緣設備(如樹莓派)。
  3. 模塊化與實時性
    • 通過 “計算器圖” 靈活組合組件,優化后可在移動端實現?30+ FPS?實時處理。
  4. 開箱即用與輕量級
    • 提供預訓練模型,無需復雜訓練;支持 TensorFlow Lite,適合資源受限設備。

但是呢,也不是使用pip安裝完就能直接使用的,雖然庫內有一個輕型的模型庫,但是我不知道為啥,我就一直報錯,很煩,很抽象,弄了很久,用內部API的時候,雖然成功了,但是更抽象了,就是簡單了將手部輪廓標出來了而以,還不只,連背景的輪廓都標出來了,很難看,建議大伙在用這個庫的時候,老老實實去官網下載模型文件再導入使用,也不要去github找,上面是?MediaPipe 框架的?模型配置文件(定義模型結構、輸入輸出等),并非直接可用的 “預訓練權重文件”。很好分辨,.pbtxt?文件就是,下載模型文件呢就去官方模型倉庫,鏈接:【https://storage.googleapis.com/mediapipe-models/】,要想正常訪建議使用chrome瀏覽器,并且使用快捷鍵【shift+ctrl+n】開啟無痕瀏覽后再嘗試訪問,我也不知為什么,直接訪問就返回【MissingSecurityHeader: Your request was missing a required header. Authorization】,百度了一下才知道原來遇到的錯誤?MissingSecurityHeader: Your request was missing a required header. Authorization?表示請求中缺少必要的?Authorization?認證頭,這通常出現在需要身份驗證的接口調用、云服務訪問或權限控制場景中。說白了就是嘗試訪問的 Google Cloud Storage 鏈接(如?storage.googleapis.com)屬于需要身份驗證的谷歌云資源。當直接通過瀏覽器或工具下載文件時,谷歌可能要求提供?API 密鑰、OAuth 令牌?等認證信息,否則拒絕請求。說人話就是沒有授權,不給你訪問網站。

為了大伙,我直接給下載鏈接放下面,有需要的自行下載即可

https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task

?至于為啥是float16,而不是32,因為float16:表示模型參數的數值精度(平衡模型大小和計算效率),也有?float32?版本(精度更高但體積稍大),一般場景選?float16?即可。咱這小破筆記本真心帶不動32版本的。

好啦,下載完成之后,就要開始下一步了,因為我是用的pycharm寫的,直接用的虛擬環境,存放位置是有一定要求的,以python為例:

Python 項目(純代碼調用)

your_project/
├── models/          # 專門放模型文件
│   └── hand_landmarker.task
└── main.py          # 主代碼

?正常來說是有一個依賴文件的,例如requirements.txt文件,不是很有必要,所以可有可不的

歐克,解決完上述的所有問題之后,就可以開始?實現手部骨骼啦!

import cv2
import tkinter as tk
from PIL import Image, ImageTk
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
import os# 模型路徑
MODEL_PATH = os.path.join("models", "hand_landmarker.task")
if not os.path.exists(MODEL_PATH):raise FileNotFoundError(f"模型文件未找到: {MODEL_PATH}")# 初始化手部檢測器
base_options = python.BaseOptions(model_asset_path=MODEL_PATH)
options = vision.HandLandmarkerOptions(base_options=base_options,num_hands=2,min_hand_detection_confidence=0.3,min_hand_presence_confidence=0.3,min_tracking_confidence=0.3
)
detector = vision.HandLandmarker.create_from_options(options)def draw_hand_skeleton(frame):original_height, original_width = frame.shape[:2]mp_image = mp.Image(image_format=mp.ImageFormat.SRGB,data=cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))results = detector.detect(mp_image)if results.hand_landmarks:  # 確保檢測到手部for hand_landmarks_list in results.hand_landmarks:  # 遍歷每只手的關鍵點列表(外層列表)for landmark in hand_landmarks_list:  # 遍歷單只手的關鍵點(內層列表)x = int(landmark.x * original_width)y = int(landmark.y * original_height)cv2.circle(frame, (x, y), 5, (255, 0, 0), -1)# 繪制骨骼連線(根據關鍵點列表索引)for connection in mp.solutions.hands.HAND_CONNECTIONS:start_idx, end_idx = connectionstart_landmark = hand_landmarks_list[start_idx]end_landmark = hand_landmarks_list[end_idx]start_x = int(start_landmark.x * original_width)start_y = int(start_landmark.y * original_height)end_x = int(end_landmark.x * original_width)end_y = int(end_landmark.y * original_height)cv2.line(frame, (start_x, start_y), (end_x, end_y), (0, 255, 0), 2)return framedef update_camera():ret, frame = cap.read()if ret:frame_flipped = cv2.flip(frame, 1)frame_original = frame_flipped.copy()skeleton_frame = draw_hand_skeleton(frame_original)# 縮放并顯示畫面frame_resized = cv2.resize(frame_flipped, (400, 400))skeleton_resized = cv2.resize(skeleton_frame, (400, 400))l1_img = Image.fromarray(cv2.cvtColor(frame_resized, cv2.COLOR_BGR2RGB))l1.imgtk = ImageTk.PhotoImage(l1_img)l1.configure(image=l1.imgtk)l2_img = Image.fromarray(cv2.cvtColor(skeleton_resized, cv2.COLOR_BGR2RGB))l2.imgtk = ImageTk.PhotoImage(l2_img)l2.configure(image=l2.imgtk)w.after(10, update_camera)# 初始化攝像頭
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)# 創建窗口
w = tk.Tk()
w.title("手語識別(簡易)")
w.geometry("805x640")l1 = tk.Label(w, width=400, height=400, bg="black")
l1.place(x=0, y=0)l2 = tk.Label(w, width=400, height=400, bg="black")
l2.place(x=400, y=0)l3 = tk.Label(w,text='手部骨骼檢測已就緒,請將手放入畫面中...',font=("微軟雅黑", 12),bg="#f0f0f0",width=60,height=2
)
l3.place(x=10, y=530)update_camera()
w.mainloop()# 釋放資源
cap.release()
detector.close()
cv2.destroyAllWindows()

也是直接將把整個代碼直接發出來嗷,下面再詳細分析代碼:
?

代碼整體功能概述

這段代碼實現了一個基于 MediaPipe 的手部骨骼實時檢測與可視化應用。程序通過攝像頭捕獲視頻流,使用 MediaPipe 的手部關鍵點檢測模型識別手部位置和姿態,然后在圖像上繪制關鍵點和連接線,最后通過 Tkinter 界面展示原始畫面和處理后的骨骼畫面。主要功能模塊包括:模型初始化、圖像骨骼繪制、攝像頭畫面更新和 GUI 界面展示。

詳細模塊分析

1. 依賴庫導入與模型初始化
import cv2
import tkinter as tk
from PIL import Image, ImageTk
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
import os# 模型路徑配置
MODEL_PATH = os.path.join("models", "hand_landmarker.task")
if not os.path.exists(MODEL_PATH):raise FileNotFoundError(f"模型文件未找到: {MODEL_PATH}")# 初始化手部檢測器
base_options = python.BaseOptions(model_asset_path=MODEL_PATH)
options = vision.HandLandmarkerOptions(base_options=base_options,num_hands=2,min_hand_detection_confidence=0.3,min_hand_presence_confidence=0.3,min_tracking_confidence=0.3
)
detector = vision.HandLandmarker.create_from_options(options)

關鍵點

  • 依賴庫
    • cv2:處理視頻流和圖像繪制
    • tkinter:創建 GUI 界面
    • PIL:圖像格式轉換
    • mediapipe:提供手部檢測模型
  • 模型配置
    • num_hands=2:最多檢測兩只手
    • min_detection_confidence=0.3:檢測置信度閾值
    • min_tracking_confidence=0.3:跟蹤置信度閾值
2. 手部骨骼繪制函數
def draw_hand_skeleton(frame):original_height, original_width = frame.shape[:2]mp_image = mp.Image(image_format=mp.ImageFormat.SRGB,data=cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))results = detector.detect(mp_image)if results.hand_landmarks:for hand_landmarks_list in results.hand_landmarks:# 繪制關鍵點(藍色圓點)for landmark in hand_landmarks_list:x = int(landmark.x * original_width)y = int(landmark.y * original_height)cv2.circle(frame, (x, y), 5, (255, 0, 0), -1)# 繪制骨骼連接線(綠色線條)for connection in mp.solutions.hands.HAND_CONNECTIONS:start_idx, end_idx = connectionstart_landmark = hand_landmarks_list[start_idx]end_landmark = hand_landmarks_list[end_idx]start_x = int(start_landmark.x * original_width)start_y = int(start_landmark.y * original_height)end_x = int(end_landmark.x * original_width)end_y = int(end_landmark.y * original_height)cv2.line(frame, (start_x, start_y), (end_x, end_y), (0, 255, 0), 2)return frame

關鍵點

  • 圖像預處理
    • 將 OpenCV 的 BGR 格式轉換為 MediaPipe 需要的 RGB 格式
    • 創建?mp.Image?對象用于模型輸入
  • 骨骼繪制邏輯
    • 關鍵點:每個手部 21 個關鍵點,用藍色圓點標記
    • 連接線:使用?mp.solutions.hands.HAND_CONNECTIONS?定義的連接關系,用綠色線條連接關鍵點
    • 坐標轉換:將歸一化坐標(0-1 范圍)轉換為圖像像素坐標
3. 攝像頭畫面更新函數
def update_camera():ret, frame = cap.read()if ret:frame_flipped = cv2.flip(frame, 1)  # 水平翻轉(鏡像效果)frame_original = frame_flipped.copy()# 檢測并繪制手部骨骼skeleton_frame = draw_hand_skeleton(frame_original)# 縮放并顯示畫面frame_resized = cv2.resize(frame_flipped, (400, 400))skeleton_resized = cv2.resize(skeleton_frame, (400, 400))# 轉換為Tkinter可用格式l1_img = Image.fromarray(cv2.cvtColor(frame_resized, cv2.COLOR_BGR2RGB))l1.imgtk = ImageTk.PhotoImage(l1_img)l1.configure(image=l1.imgtk)l2_img = Image.fromarray(cv2.cvtColor(skeleton_resized, cv2.COLOR_BGR2RGB))l2.imgtk = ImageTk.PhotoImage(l2_img)l2.configure(image=l2.imgtk)# 每10ms調用一次自身,實現實時更新w.after(10, update_camera)

關鍵點

  • 鏡像效果cv2.flip(frame, 1)?使畫面更符合用戶習慣
  • 雙窗口顯示
    • 左側窗口(l1):顯示原始攝像頭畫面
    • 右側窗口(l2):顯示繪制了骨骼的畫面
  • 圖像格式轉換
    OpenCV數組(BGR) → cvtColor → RGB數組 → PIL.Image → ImageTk.PhotoImage → Tkinter顯示
    
  • 定時更新w.after(10, update_camera)?實現約 100FPS 的更新頻率
4. GUI 界面初始化
# 初始化攝像頭
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)# 創建窗口
w = tk.Tk()
w.title("手語識別(簡易)")
w.geometry("805x640")# 創建三個標簽分別用于顯示原始畫面、骨骼畫面和提示文本
l1 = tk.Label(w, width=400, height=400, bg="black")
l1.place(x=0, y=0)l2 = tk.Label(w, width=400, height=400, bg="black")
l2.place(x=400, y=0)l3 = tk.Label(w,text='手部骨骼檢測已就緒,請將手放入畫面中...',font=("微軟雅黑", 12),bg="#f0f0f0",width=60,height=2
)
l3.place(x=10, y=530)# 啟動更新循環并進入主事件循環
update_camera()
w.mainloop()# 釋放資源
cap.release()
detector.close()
cv2.destroyAllWindows()

關鍵點

  • 窗口布局
    • 左右并列兩個 400x400 的窗口
    • 底部一個提示文本區域
  • 資源管理
    • 使用?cap.release()?釋放攝像頭資源
    • 使用?detector.close()?關閉模型
    • 使用?cv2.destroyAllWindows()?關閉所有 OpenCV 窗口

ok啦,寫到這里只能算半成品,因為還有模型訓練等等非常麻煩的事情,先寫到這里吧~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/84185.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/84185.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/84185.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

React從基礎入門到高級實戰:React 實戰項目 - 項目一:在線待辦事項應用

React 實戰項目:在線待辦事項應用 歡迎來到本 React 開發教程專欄的第 26 篇!在之前的 25 篇文章中,我們從 React 的基礎概念逐步深入到高級技巧,涵蓋了組件、狀態、路由和性能優化等核心知識。這一次,我們將通過一個…

1991-2024年上市公司個股換手率數據

1991-2024年上市公司個股換手率數據 1、時間:1991-2024年 2、來源:上海證券交易所和深圳證券交易所 3、指標:證券代碼、交易年份、開始日期、截止日期、年換手率(流通股數)(%)、年換手率(總股數)(%)、日均換手率(流通股數)(%)、日均換手率…

RAID存儲技術概述

1 數據存儲架構 數據存儲架構是對數據存儲方式、存儲設備及相關組件的組織和規劃,涵蓋存儲系統的布局、數據存儲策略等,它明確數據如何存儲、管理與訪問,為數據的安全、高效使用提供支撐。 1.1 存儲系統 存儲系統是計算機的重要組成部分之…

LRU 和 DiskLRU實現相冊緩存器

我是寫Linux后端的(golang、c、py),后端緩存算法通常是指的是內存里面的lru、或diskqueue,都是獨立使用。 很少有用內存lru與disklru結合的場景需求。近段時間研究android開發,里面有一些設計思想值得后端學習。 寫這…

可視化預警:如何讓生產風險預警更高效?

你有沒有遇到過這種情況? 明明設備已經開始發熱報警,但操作人員還在繼續運行; 或者某個參數已經接近危險值,卻沒人注意到; 甚至問題早就埋下了隱患,只是當時沒發現…… 這些情況的背后,其實都…

【MPC-C++】qpOASES 源碼編譯與鏈接,編譯器設置細節

qpOASES 源碼編譯與鏈接 克隆源碼 git clone https://github.com/coin-or/qpOASES.gitcd qpOASES mkdir build cd build接下來是構建,有一些細節。 查看 CMakeLists.txt,發現如果不顯示指定 CMAKE_BUILD_TYPE 構建版本,會自動編譯 Release…

【11408學習記錄】考研數學攻堅:行列式本質、性質與計算全突破

行列式 數學線性代數一、對象(元素):向量二、運算三、行列式3.1 第一種定義——行列式的本質定義3.2 行列式的性質性質1:行列互換,其值不變性質2:若行列式中某行(列)元素全為零&…

Qt/C++開發監控GB28181系統/取流協議/同時支持udp/tcp被動/tcp主動

一、前言說明 在2011版本的gb28181協議中,拉取視頻流只要求udp方式,從2016開始要求新增支持tcp被動和tcp主動兩種方式,udp理論上會丟包的,所以實際使用過程可能會出現畫面花屏的情況,而tcp肯定不丟包,起碼…

小木的算法日記-線段樹

🌳 線段樹 (Segment Tree):玩轉區間作的終極利器 你好,未來的算法大師! 想象一下,你正在處理一個巨大的數據集,比如某個電商網站一整天的用戶點擊流。老板突然問你:“下…

Day24 元組和OS模塊

1、元組(有序 不可變 可重復) 管道工程中pipeline類接收的是一個包含多個小元組的列表作為輸入。可以這樣理解這個結構: (1) 列表 []: 定義了步驟執行的先后順序。Pipeline 會按照列表中的順序依次處理數據。之所以用列…

Auto-Coder使用GPT-4o完成:在用TabPFN這個模型構建一個預測未來3天漲跌的分類任務

通過akshare庫,獲取股票數據,并生成TabPFN這個模型 可以識別、處理的格式,寫一個完整的預處理示例,并構建一個預測未來 3 天股價漲跌的分類任務 用TabPFN這個模型構建一個預測未來 3 天股價漲跌的分類任務,進行預測并輸…

Device Mapper 機制

Device Mapper 機制詳解 Device Mapper(簡稱 DM)是 Linux 內核中的一套通用塊設備映射框架,為 LVM、加密磁盤、RAID 等提供底層支持。本文將詳細介紹 Device Mapper 的原理、實現、內核配置、常用工具、操作測試流程,并配以詳細的…

crackme006

crackme006 名稱值軟件名稱aLoNg3x.1.exe加殼方式無保護方式Serial編譯語言Delphi調試環境Win10 64位使用工具x32dbg,ida pro,PEid,DarkDe4破解日期2025-06-05 脫殼 1. 先用PEid查殼 查到無殼 尋找Serial 查詢到編程語言為Delphi 導出Delphi符號表信息到x32dbg&#xff0c…

Conda 創建新環境時報錯 HTTP 502,如何解決?

Conda 創建新環境時報錯 HTTP 502&#xff0c;如何解決&#xff1f; 最近在用 Conda 創建新環境時&#xff0c;突然遇到這樣一個錯誤&#xff1a; CondaHTTPError: HTTP 502 BAD GATEWAY for url <https://mirrors.westlake.edu.cn/ANACONDA/cloud/conda-forge/linux-64/r…

2025最全TS手寫題之partial/Omit/Pick/Exclude/Readonly/Required

隨著 TS 在工作中使用的越來越廣泛&#xff0c;面試的時候面試官也都會加上一兩個 TS 的問題來了解候選人對于 TS 的熟悉程度&#xff0c;其中就有不少手寫題目&#xff0c;比如筆者在字節的一次二面&#xff0c;面試官就問到了我如何實現一個 Pick&#xff0c;在小紅書的一面&…

基于江科大stm32屏幕驅動,實現OLED多級菜單(動畫效果),結構體鏈表實現(獨創源碼)

引言 在嵌入式系統中&#xff0c;用戶界面的設計往往直接影響到用戶體驗。本文將以STM32微控制器和OLED顯示屏為例&#xff0c;介紹如何實現一個多級菜單系統。該系統支持用戶通過按鍵導航菜單&#xff0c;執行相應操作&#xff0c;并提供平滑的滾動動畫效果。 本文設計了一個…

LLMs之StructuredOutput:大模型結構化輸出的簡介、常用方案、前沿框架之詳細攻略

LLMs之StructuredOutput&#xff1a;大模型結構化輸出的簡介、常用方案、前沿框架之詳細攻略 目錄 大模型結構化輸出的簡介 1、特點與難點 大模型結構化輸出的常用方案及對比 1、前沿框架&#xff1a;vLLM 與 XGrammar 大模型結構化輸出的案例應用 大模型結構化輸出的簡介…

Linux中shell流程控制語句

一、if條件控制 1.1 語法解讀 單路決策 - 單分支if語句樣式&#xff1a;if [ 條件 ]then指令fi特點&#xff1a;單一條件&#xff0c;只有一個輸出 雙路決策 - 雙分支if語句樣式&#xff1a;if [ 條件 ]then指令1else指令2fi特點&#xff1a;單一條件&#xff0c;兩個輸出 …

Python學習(8) ----- Python的類與對象

Python 中的類&#xff08;Class&#xff09;與對象&#xff08;Object&#xff09;是面向對象編程&#xff08;OOP&#xff09;的核心。我們可以通過“類是模板&#xff0c;對象是實例”來理解它們的關系。 &#x1f9f1; 一句話理解&#xff1a; 類就像“圖紙”&#xff0c;對…

數據結構-文件

文件是性質相同的記錄的集合。 記錄是文件中存取的基本單位&#xff0c;數據項是文件可使用的最小單位。 操作系統研究的文件是一維的無結構連續字符序列&#xff0c;數據庫中研究的文件是帶有結構的記錄集合。 文件在外存上的4種基本組織方式&#xff1a;順序、索引、散列、鏈…