Python开发基础手语识别(基础框架版)

📅 发布时间:2026/7/4 23:03:36 👁️ 浏览次数:
Python开发基础手语识别(基础框架版)
一、前期准备想要实现这些首先就是要模拟出来一个大致的框架方便后续开展下面的就是随便写的一个框架大家凑合看看就行基本上是这个意思from tkinter import * w Tk() w.title(手语识别简易) w.geometry(805x640) l1 Label(text此窗口实时显示\n摄像头拍摄画面, font(微软雅黑, 20),width25,height15,reliefgroove, borderwidth2) l1.place(x0, y0) l2 Label(text此窗口实时显示\n手部骨骼绘画, font(微软雅黑, 20),width25,height15,reliefgroove, borderwidth2) l2.place(x400, y0) l3 Label(text此窗口实时显示手语识别结果, font(微软雅黑, 20),width50,height3,reliefgroove, borderwidth2) l3.place(x0, y530) 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), interpolationcv2.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): break cap.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, quality95) # 保存为 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 ImageDraw draw ImageDraw.Draw(img) draw.rectangle([(10, 10), (100, 100)], outline(0, 255, 0), width2) # 绘制矩形 draw.ellipse([(150, 150), (250, 250)], fill(255, 0, 0)) # 绘制填充椭圆 5. 批量图像处理 import os from PIL import Image input_folder images/ output_folder processed/ # 创建输出文件夹若不存在 os.makedirs(output_folder, exist_okTrue) 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))他们俩的关键语法对比功能OpenCVPythonPillowPIL读取图像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.sizeps:上述不是很全面仅作参考好啦回到正题该逐步实现调用过程啦2.摄像头调用的具体代码实现下面的我自己的代码先发出来给大伙瞅瞅稍后详细解释代码import cv2 import tkinter as tk from PIL import Image, ImageTk def 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(imageimg) l1.imgtk imgtk l1.configure(imageimgtk) # 手部骨骼绘制显示在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), width25, height15, reliefgroove, borderwidth2) l1.place(x0, y0) # Label用于手部骨骼绘制预留 l2 tk.Label(text此窗口实时显示\n手部骨骼绘画, font(微软雅黑, 20), width25, height15, reliefgroove, borderwidth2) l2.place(x400, y0) # Label用于识别结果预留 l3 tk.Label(text此窗口实时显示手语识别结果, font(微软雅黑, 20), width50, height3, reliefgroove, borderwidth2) l3.place(x0, y530) # 启动摄像头更新 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模块中的所有公有名称如Tk、Label、Button等直接导入到当前命名空间。这意味着无需通过模块名前缀如tk.即可直接使用这些名称。如果当前命名空间中已有同名对象如自定义的Button函数会发生名称冲突导致程序报错或逻辑混乱。如果我直接使用的话就会一直报错恰好大伙们还不知道这个小知识点的话就很难发现自己错在啥地方如果真不小心了咋办就会出现下面的问题覆盖内置函数或变量例如若代码中定义了Tk my_string则from tkinter import *会尝试将tkinter.Tk窗口类导入为Tk导致Tk被重新赋值为字符串引发错误。难以追踪来源当代码中出现Button时无法直接判断它是tkinter.Button还是其他模块 / 自定义的Button增加调试难度。破坏代码可读性对于大型项目未加前缀的名称会让读者难以快速识别其所属模块尤其是在多个模块被import *的情况下。所以这是一个很抽象的错误也是一个很小的知识点一般来说我们在系统性学习python的时候是直接学的第二种方法第一种老师也会讲但是不会细讲因为考试也不考我们平时也接触不到这些比较难的库所以这个方面的小知识点就很容易被忽略。至于为啥第二种好老师也不会说同样考试也不考我就来简要的说一下过两天我整理一下跟这篇一起发出来1. 避免命名污染确保名称唯一性隔离命名空间将 Tkinter 的所有名称如Tk、Label封装在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(imageimg) # 将图像绑定到 l1 标签并更新显示 l1.imgtk imgtk # 保留引用避免被垃圾回收 l1.configure(imageimgtk) # 递归调用自身每 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()会返回retFalse画面停止更新。4. 启动程序主循环和资源释放update_camera() # 调用函数启动摄像头画面更新 w.mainloop() # Tkinter 主循环保持窗口显示 cap.release() # 释放摄像头资源避免硬件占用w.mainloop()是 GUI 程序的入口用于处理用户交互如关闭窗口。cap.release()必须在主循环结束后调用否则可能导致摄像头无法正常关闭。3.手部骨骼实现想要实现手部骨骼就得来到另一个库了----MediaPipe库MediaPipe 是Google 开发的开源跨平台机器学习框架专注于实时多媒体处理和计算机视觉任务提供预训练模型和模块化工具可快速开发手势识别、人脸识别等 AI 应用。核心特点多模态感知能力支持手部追踪21 个关键点、人脸检测468 个关键点、人体姿态估计33 个关键点、物体检测与追踪等。跨平台与多语言支持 Python、C、Java、JavaScript 等语言覆盖桌面、移动Android/iOS、边缘设备如树莓派。模块化与实时性通过 “计算器图” 灵活组合组件优化后可在移动端实现30 FPS实时处理。开箱即用与轻量级提供预训练模型无需复杂训练支持 TensorFlow Lite适合资源受限设备。但是呢也不是使用pip安装完就能直接使用的虽然库内有一个轻型的模型库但是我不知道为啥我就一直报错很烦很抽象弄了很久用内部API的时候虽然成功了但是更抽象了就是简单了将手部轮廓标出来了而以还不只连背景的轮廓都标出来了很难看建议大伙在用这个库的时候老老实实去官网下载模型文件再导入使用也不要去github找上面是 MediaPipe 框架的模型配置文件定义模型结构、输入输出等并非直接可用的 “预训练权重文件”。很好分辨.pbtxt文件就是下载模型文件呢就去官方模型仓库链接【https://storage.googleapis.com/mediapipe-models/】要想正常访建议使用chrome浏览器并且使用快捷键【shiftctrln】开启无痕浏览后再尝试访问我也不知为什么直接访问就返回【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_pathMODEL_PATH) options vision.HandLandmarkerOptions( base_optionsbase_options, num_hands2, min_hand_detection_confidence0.3, min_hand_presence_confidence0.3, min_tracking_confidence0.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_formatmp.ImageFormat.SRGB, datacv2.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 connection start_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 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)) l1_img Image.fromarray(cv2.cvtColor(frame_resized, cv2.COLOR_BGR2RGB)) l1.imgtk ImageTk.PhotoImage(l1_img) l1.configure(imagel1.imgtk) l2_img Image.fromarray(cv2.cvtColor(skeleton_resized, cv2.COLOR_BGR2RGB)) l2.imgtk ImageTk.PhotoImage(l2_img) l2.configure(imagel2.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, width400, height400, bgblack) l1.place(x0, y0) l2 tk.Label(w, width400, height400, bgblack) l2.place(x400, y0) l3 tk.Label( w, text手部骨骼检测已就绪请将手放入画面中..., font(微软雅黑, 12), bg#f0f0f0, width60, height2 ) l3.place(x10, y530) 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_pathMODEL_PATH) options vision.HandLandmarkerOptions( base_optionsbase_options, num_hands2, min_hand_detection_confidence0.3, min_hand_presence_confidence0.3, min_tracking_confidence0.3 ) detector vision.HandLandmarker.create_from_options(options)关键点依赖库cv2处理视频流和图像绘制tkinter创建 GUI 界面PIL图像格式转换mediapipe提供手部检测模型模型配置num_hands2最多检测两只手min_detection_confidence0.3检测置信度阈值min_tracking_confidence0.3跟踪置信度阈值2. 手部骨骼绘制函数def draw_hand_skeleton(frame): original_height, original_width frame.shape[:2] mp_image mp.Image( image_formatmp.ImageFormat.SRGB, datacv2.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 connection start_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(imagel1.imgtk) l2_img Image.fromarray(cv2.cvtColor(skeleton_resized, cv2.COLOR_BGR2RGB)) l2.imgtk ImageTk.PhotoImage(l2_img) l2.configure(imagel2.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, width400, height400, bgblack) l1.place(x0, y0) l2 tk.Label(w, width400, height400, bgblack) l2.place(x400, y0) l3 tk.Label( w, text手部骨骼检测已就绪请将手放入画面中..., font(微软雅黑, 12), bg#f0f0f0, width60, height2 ) l3.place(x10, y530) # 启动更新循环并进入主事件循环 update_camera() w.mainloop() # 释放资源 cap.release() detector.close() cv2.destroyAllWindows()关键点窗口布局左右并列两个 400x400 的窗口底部一个提示文本区域资源管理使用cap.release()释放摄像头资源使用detector.close()关闭模型使用cv2.destroyAllWindows()关闭所有 OpenCV 窗口ok啦写到这里只能算半成品因为还有模型训练等等非常麻烦的事情先写到这里吧