简介:一套基于PyQt5与OpenPose的太极拳姿态识别系统源码及配套数据集,面向计算机相关专业毕业设计、课程设计及项目实战学习者。项目源自大三阶段的大作业,经导师指导并获评99分,代码完整可直接运行,能对太极拳动作进行实时关键点检测与姿态识别,并通过可视化界面直观展示,兼顾教学与训练场景。
压缩包共116个文件,总大小1.74MB。其中的80张jpg图片作为姿态样本数据集,13个py文件构成系统主程序,另有pyc缓存、xml配置、txt说明等辅助文件,目录结构清晰,便于学习OpenPose关键点检测与PyQt5界面搭建流程,也利于二次开发或功能扩展。
已有103人参与学习下载。借助这份完整方案,读者可快速上手一个GUI+姿态识别项目,从数据准备、模型调用到界面交互均有参考,适合需要快速完成毕设或课设方案的学生,也适合想练习Python项目实战的入门者。
1. 太极拳姿态识别系统:PyQt5 加 OpenPose 到底能做出什么程度
姿态识别这几年在毕设和课设里出现的频率越来越高,但大多数项目停留在「调用 OpenPose 输出一张骨架图」的程度,离「能演示、能答辩、能说清楚原理」还差一个可视化界面和一套判定逻辑。这套基于 PyQt5 + OpenPose 的太极拳姿态识别源码,把模型推理、关键点提取、关节角度计算和界面交互串成了一条完整的链路:打开程序、加载视频或摄像头、实时显示骨架叠加画面、给出姿态比对结果。对正在选 Python 毕设题目的人来说,它最大的价值不是 OpenPose 本身,而是把「算法模型」和「桌面应用」之间的工程粘合层补齐了——这部分恰恰是教科书里最少讲到、自己做最容易翻车的地方。适合有两三个月时间、想避开纯 web 开发、希望做一款本地可运行桌面应用的人。
2. OpenPose 姿态估计原理与模型选型:从 18 个关键点到角度特征
2.1 OpenPose 的骨架输出:COCO 18 点和 BODY_25 怎么选
OpenPose 的核心输出是人体关键点坐标。默认的 COCO 模型输出 18 个关键点,从鼻子、肩膀、手肘一路到脚踝;BODY_25 则是 OpenPose 自己的 25 点格式,比 COCO 多了脚趾、颈部切分等细节。做太极拳识别时,我建议优先用 COCO 18 点。原因很直接:太极动作的判定集中在手腕、肘、肩、髋、膝这几个大关节上,18 点完全覆盖,而 BODY_25 的多余关键点对动作判断没有实质性帮助,还增加了后处理复杂度。更重要的是,COCO 模型的推理速度比 BODY_25 快,在 CPU 环境下这个差距能直接影响界面响应。
关键点从 OpenPose 的poseKeypoints里读出后,是一个[人数, 关键点数, 3]的数组,第 3 维是 x、y 坐标和置信度。拿到这个数组后,第一步不是直接算角度,而是过滤低置信度关键点。置信度低于 0.3 的点通常意味着遮挡或错检,直接参与角度计算会产生很大的跳变。
COCO_JOINTS = { "nose": 0, "neck": 1, "r_shoulder": 2, "r_elbow": 3, "r_wrist": 4, "l_shoulder": 5, "l_elbow": 6, "l_wrist": 7, "r_hip": 8, "r_knee": 9, "r_ankle": 10, "l_hip": 11, "l_knee": 12, "l_ankle": 13, "r_eye": 14, "l_eye": 15, "r_ear": 16, "l_ear": 17 } def get_joint(keypoints, person_idx, joint_name): """从 OpenPose 输出中取单个关键点坐标,低置信度返回 None""" conf = keypoints[person_idx][COCO_JOINTS[joint_name]][2] if conf < 0.3: return None x = keypoints[person_idx][COCO_JOINTS[joint_name]][0] y = keypoints[person_idx][COCO_JOINTS[joint_name]][1] return (x, y)这段代码的关键在conf < 0.3这个阈值。0.3 是我常用的下限,在室内单人或双人场景下比较稳;如果遮挡严重,可以降到 0.2,但要接受更多误检。关节索引来自 COCO 的固定顺序,写死成字典是为了让后面调get_joint时不用记数字索引,代码可读性好很多。
2.2 模型推理配置:net_resolution 与 number_people_max 的实际影响
OpenPose 的 Python 接口通过WrapperPython配置推理参数,这几个参数直接影响速度和稳定性。
params = { "model_folder": "./models/", "net_resolution": "368x368", "number_people_max": 1, "keypoint_threshold": 0.3, "keypoint_threshold2": 0.3, } op_wrapper = op.WrapperPython() op_wrapper.configure(params) op_wrapper.start() datum = op.Datum() datum.cvInputData = frame op_wrapper.emplaceAndPop(op.VectorDatum([datum])) keypoints = datum.poseKeypointsnet_resolution控制输入网络的图像尺寸,368x368 是速度与精度的折中点。如果你用的是 NVIDIA 显卡,可以调成 656x368 提升小关节精度;纯 CPU 跑建议降到 320x256,否则界面会卡到没法看。number_people_max设成 1 是一个容易被低估的设置——太极拳通常单人练习,限制人数能让 OpenPose 把更多计算资源集中到单个人身上,减少多人检测带来的抖动。keypoint_threshold是过滤低置信度关键点的最后一道防线,它配合前面代码里的置信度判断,双保险。
2.3 从坐标到角度:用余弦定理描述关节姿态
关键点坐标本身不能直接说明「这个动作对不对」,需要转成关节角度。比如肘关节角度,取肩、肘、腕三个点,以肘为顶点,用余弦定理计算。这个角度是姿态判定的基础特征,不受人体在画面中大小和位置的影响。
import math def calc_angle(point_a, point_b, point_c): """以 point_b 为顶点,计算三个关键点构成的关节角度(度)""" if point_a is None or point_b is None or point_c is None: return None ba = (point_a[0] - point_b[0], point_a[1] - point_b[1]) bc = (point_c[0] - point_b[0], point_c[1] - point_b[1]) dot_val = ba[0] * bc[0] + ba[1] * bc[1] norm_ba = math.hypot(ba[0], ba[1]) norm_bc = math.hypot(bc[0], bc[1]) if norm_ba == 0 or norm_bc == 0: return None cos_val = dot_val / (norm_ba * norm_bc) cos_val = max(-1.0, min(1.0, cos_val)) return math.degrees(math.acos(cos_val))这里有一个边界问题:acos的输入必须在 -1 到 1 之间,但浮点运算中dot_val / (norm_ba * norm_bc)可能因为坐标精度出现 1.0000001,不 clamp 到合法范围会直接返回nan。我在项目里遇到过一次,整个界面的角度显示变成空,查了半天才发现是这个原因。另一个注意点是分母为 0——两个点坐标完全重合时,比如 OpenPose 把肩和肘检成同一个位置,除零异常会中断程序,所以返回None比抛异常更合理。
3. PyQt5 可视化界面实现:线程、画布与实时推理的联动方式
3.1 界面模块划分:控制区、视频区、结果区
PyQt5 界面的常见误区是一上来就堆布局代码,结果控制逻辑和界面代码混在一起,后面加功能特别痛苦。我这个项目里把界面按功能切成三个区:左侧是视频显示区,用 QLabel 实时刷新帧;右侧顶部是控制区,放「打开视频」「开始识别」「暂停」「退出」四个按钮和一个模型路径输入框;右侧底部是结果区,用 QTableWidget 显示各个关节角度,用 QLabel 显示当前识别的招式名称和相似度。
界面类的初始化里把 OpenPose 配置和 PyQt5 控件解耦。OpenPose 的初始化逻辑放在单独的init_openpose()方法里,界面只负责调用,不直接处理模型细节。这样做的实际好处是:换模型或者改推理参数时只动一个方法,不影响界面代码。改参数时需要重新初始化 OpenPose,程序里调用一次init_openpose()即可,不用重启界面。
3.2 用 QThread 跑推理:避免界面卡死的核心设计
直接在 GUI 主线程里跑 OpenPose 推理是大忌。单帧 CPU 推理耗时几百毫秒,界面会冻结成「未响应」状态,用户以为程序崩了。正确的做法是把视频读取和推理放到 QThread 里,主线程只负责接收结果并刷新界面。
class PoseWorker(QThread): frame_ready = pyqtSignal(object, object) def __init__(self, video_path, op_wrapper): super().__init__() self.video_path = video_path self.op_wrapper = op_wrapper self.running = True def run(self): cap = cv2.VideoCapture(self.video_path) while self.running: ret, frame = cap.read() if not ret: break datum = op.Datum() datum.cvInputData = frame self.op_wrapper.emplaceAndPop(op.VectorDatum([datum])) if datum.poseKeypoints is not None: self.frame_ready.emit(frame, datum.poseKeypoints) cap.release()frame_ready信号携带原始帧和关键点数组两个对象。这里不推荐在子线程里直接操作界面控件,因为 PyQt5 的控件不是线程安全的,跨线程赋值轻则闪烁,重则崩溃。所有界面更新都通过信号槽机制切回主线程执行。另外一个细节是self.running标志位——停止识别时把running置为 False,run方法里的 while 循环会自然退出,比直接调用terminate()安全得多。terminate()是强制杀掉线程,视频文件和 OpenPose 内部状态可能来不及清理,容易留下僵尸进程。
3.3 骨架叠加绘制:坐标缩放与 QPainter 的使用
OpenPose 返回的关键点坐标是原图像素坐标,直接画到 QLabel 上时要用setScaledContents(True)适配控件大小,但关键点坐标不会跟着缩放,导致骨架画在错误位置。解决方式是记录显示区域与原图的缩放比例,绘制骨架前先对坐标做变换。
def paint_skeleton(self, frame, keypoints, scale_x, scale_y): rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) height, width, channel = rgb_image.shape bytes_per_line = channel * width qt_image = QImage(rgb_image.data, width, height, bytes_per_line, QImage.Format_RGB888) pixmap = QPixmap.fromImage(qt_image) painter = QPainter(pixmap) painter.setPen(QPen(QColor(0, 255, 0), 3)) point_list = [] for person_idx in range(keypoints.shape[0]): for joint_name in ["l_shoulder", "l_elbow", "l_wrist"]: pt = get_joint(keypoints, person_idx, joint_name) if pt is not None: scaled_pt = (int(pt[0] * scale_x), int(pt[1] * scale_y)) point_list.append(scaled_pt) painter.drawEllipse(scaled_pt[0], scaled_pt[1], 6, 6) painter.end() self.video_label.setPixmap(pixmap)scale_x和scale_y在控件尺寸变化或视频尺寸变化时重新计算,否则骨架会错位。绘制在这里只画了左肩、左肘、左腕三个点做演示,完整项目里有一个BODY_BONES连接表,把 18 个点的相邻关系都列出来循环绘制。绘制逻辑单独抽成方法,主线程拿到frame_ready信号后调用一次即可,不要让绘制代码侵入信号槽函数里。
4. 数据集与姿态分类逻辑:标注格式、相似度阈值与训练边界
4.1 数据集的目录组织与标注结构
这套资源附带的数据集按招式动作组织,每个招式一个文件夹,里面是视频帧或连续帧提取的关键点数据。关键点数据有两种常见存法:一种是 OpenPose 直接输出的 npy 文件,保存poseKeypoints数组;另一种是逐帧标注的 JSON 文件,里面记录关键点坐标和置信度。我拿到这套资源时先检查了标注文件的组织方式——按「招式名/视频名/帧号」的层级拆分,对后续训练和比对都友好。如果你是自己做数据集,建议也用这个结构,因为它方便写目录遍历脚本。
比对标准动作时,需要从数据集里提取每个招式的参考角度向量。比如「左搂膝拗步」这一式的标准角度向量是[左肘角, 左肩角, 左膝角, 右膝角],存成 CSV 或 JSON。识别时计算当前帧的角度向量与标准向量之间的距离,低于阈值判定为匹配。
4.2 角度相似度计算:欧氏距离还是余弦相似度
角度向量的相似度计算有两种常用方案,它们的适用场景不同。
import numpy as np def euclidean_similarity(current_angles, reference_angles, threshold=30.0): """欧氏距离:角度差平方和的平方根""" if len(current_angles) != len(reference_angles): return 0.0 diff = np.array(current_angles) - np.array(reference_angles) distance = np.sqrt(np.sum(diff ** 2)) score = max(0.0, 1.0 - distance / threshold) return score def cosine_similarity(current_angles, reference_angles): """余弦相似度:关注角度向量的方向一致性""" vec1 = np.array(current_angles) vec2 = np.array(reference_angles) norm1 = np.linalg.norm(vec1) norm2 = np.linalg.norm(vec2) if norm1 == 0 or norm2 == 0: return 0.0 return np.dot(vec1, vec2) / (norm1 * norm2)欧氏距离对角度绝对差异敏感,适合「幅度接近才算匹配」的判定,比如手臂是否抬平;余弦相似度忽略整体幅度,只关心各关节角度的比例关系,适合动作整体变形但相对姿态一致的情况。太极拳对动作幅度有标准要求,我用欧氏距离更多。threshold=30.0是度数意义的阈值——如果角度向量在每一维上的平均偏差约 30 度,相似度降为 0。这个值需要配合你实际采集的数据反复试,我最初设 50 导致错误动作也判定成功,后来降到 30 才把误报压下去。
4.3 训练边界:这是「识别系统」不是「深度学习训练项目」
必须明确一点:这套资源的核心价值在于「用 OpenPose 提取关键点 + 用角度匹配做判定」,而不是端到端的 CNN 姿态识别训练。数据集的作用是提供参考动作的关键点数据,供角度比对和阈值调试使用,不是拿来训练 OpenPose 模型。如果你在毕设答辩中说「我训练了 OpenPose」,会被问到训练数据规模、loss 曲线、验证集精度,而这套流程里根本没有这些环节。正确的表述是「使用 OpenPose 预训练模型提取姿态特征,基于关键点角度特征进行动作匹配识别」。这个边界想清楚,整个项目的技术路线才立得住。
4.4 数据集规模不足时的增强策略
如果附带的数据集太小,或者你自己采集的视频里有些动作帧被漏检,常见做法是做关键点层面的增强,而不是图像层面的增强。具体有三种:对关键点坐标做小幅度平移、以髋部中心为基准做镜像翻转获得另一侧动作、对角度向量做小幅随机扰动生成更多参考样本。镜像翻转与太极的左右式对应关系尤其有用——左式和右式在角度特征上呈镜像对称,不需要额外采集视频就能扩充一倍的参考数据。关键点增强的缺点是会掩盖真实的姿态误差,增强后需要在原始视频上抽帧验证识别效果,别只盯着增强样本上的数字好看。
5. 避坑与常见问题排查:PyQt5、OpenPose 与数据路径的三个重灾区
5.1 labelme 无法安装 PyQt5:环境依赖冲突的典型案例
现象:在 Anaconda 环境里先装了 labelme,再执行pip install PyQt5,提示安装失败,或者安装成功后 import 报错,Qt 相关插件加载异常。
原因:labelme 依赖 PyQt5/PyQt4 的特定版本,环境里如果先存在旧版 Qt 绑定库(PyQt4 或 PyQt5 的其他小版本),新安装的 PyQt5 会尝试复写已有组件,Anaconda 的包管理器经常把依赖解析成冲突状态。有些情况是 pip 和 conda 混用导致 Qt 库被装到了不同 site-packages 路径,Python 启动时加载混乱。
解决:我一般建议为这类项目单独建虚拟环境,在新建的 conda 环境里先装 PyQt5 再装 labelme,或者反过来但不要在一个已装大量 Qt 组件的旧环境里操作。干净环境下pip install PyQt5==5.15.x通常一次通过。labelme 需要的 PyQt 和主项目需要的 PyQt5 如果版本不兼容,就放弃同环境共存,用两个环境分开跑。
5.2 无法加载 pyopenpose 模块:PYTHONPATH 与编译版本不对
现象:代码from openpose import pyopenpose as op报ModuleNotFoundError,或者运行时提示找不到op.WrapperPython这个类。最神秘的一种是:conda 环境里已经pip list看到 openpose,但 import 后某些 api 属性缺失。
原因:OpenPose 官方 Python 绑定的构建方式分两类。一类是预编译包,直接对应某个 Python 版本和操作系统,换环境版本就不兼容;另一类是源码编译,编译完成后 pyopenpose 的 .so 文件在编译输出目录里,必须设置PYTHONPATH指向该目录,Python 才能找到它。pip list能看到的 openpose 条目很可能是 conda 自建的派生物,并不包含WrapperPython的绑定。
解决:优先使用与 PyPI 或官方 release 匹配的预编译版本,锁定 Python 3.7 或 3.8(OpenPose 1.7.0 的官方预编译包对这两个版本支持最完整)。如果走源码编译,每次启动脚本前在sys.path.append里加上编译出来的 bin 目录。我在环境里排查过类似问题,最终靠把 PYTHONPATH 显式写进项目启动脚本,而不是依赖系统环境变量,才彻底解决。
5.3 CPU 推理导致界面卡死:线程和分辨率要一起调
现象:程序启动后,点击「开始识别」,窗口立即无响应,标题栏出现「未响应」,几秒后恢复但操作依旧迟滞。
原因:推理跑在主线程,单帧耗时过长。OpenPose 在 CPU 上 用默认net_resolution=656x368的配置处理高清视频,单帧推理耗时可能超过 1 秒。PyQt5 主线程被循环阻塞,事件循环无法处理按钮点击和窗口重绘。单纯加 QThread 能把界面保住,但如果网络分辨率不降,推理耗时依旧长,线程信号大量堆积,内存占用持续上升。
解决:双管齐下。线程方面按前面第 3 章的 QThread 方式把推理剥离出去;性能方面把net_resolution降到320x256或256x256,视频帧在送入推理前先cv2.resize缩小。控制线程信号频率的做法是添加一个 Qt 定时器,固定每 100 毫秒向工作线程请求一次最新结果,避免有信号就打。如果项目以后换机器跑,优先检查这两处。
5.4 关键点抖动导致角度跳变:限幅滤波比加卡尔曼更实用
现象:同一招式在连续帧里,手臂角度忽大忽小,轻微晃动就会让相似度分数从 90 分跌到 30 分,界面上的角度数字高频闪烁。
原因:OpenPose 每帧独立推理,无时间维度的稳定性约束。手部小范围抖动在像素坐标上变化不大,但换算成角度后会被放大;加上低置信度关键点偶尔被过滤掉,calc_angle返回 None,界面直接用上一次值或空值,数字就会跳变。
解决:对角度序列做滑动窗口平均或一阶低通滤波。我用的是一阶低通,angle_smooth = 0.7 * angle_current + 0.3 * angle_previous。0.7 的权重让响应快但又不至于抖动;如果对实时性要求低(比如离线分析),可以换窗口大小为 5 的滑动平均,平滑效果更好但会带来 2 到 3 帧延迟。要在读数稳定和响应速度之间做取舍,没有参数能同时满足两者。
6. 从跑通到能演示:验证姿态识别准确度的具体方法与调参技巧
先把稳定关节角度作为第一优先级。对每个视频样本,跑完识别后在结果区检查四组关键角度——左肘角、右肘角、左膝角、右膝角,看它们在连续帧里是否平滑。如果某个角度反复出现 None 值,说明该关键点的置信度经常低于阈值,要么调低keypoint_threshold,要么换测试视频避免遮挡。角度平滑参数和阈值调整可以用下面的方式集中管理。
ANGLE_PARAMS = { "threshold": 30.0, "smooth_factor": 0.7, "keypoint_conf": 0.3, "show_joint_names": True, } def smooth_angle(new_angle, old_angle, factor=0.7): if new_angle is None: return old_angle if old_angle is None: return new_angle return factor * new_angle + (1 - factor) * old_angle这是我在机构 ID 里查到的典型参数组合:threshold控相似度的宽松程度、smooth_factor控时序平滑的响应速度、keypoint_conf控关键点过滤门槛。你改模型、换数据集或换测试视频后,需要重新微调这三个参数,没有一劳永逸的组合。
验证准确度时,准备两个测试集:正样本是标准动作视频,负样本是明显错误动作(比如手臂完全下垂或乱挥),跑通后分别记录相似度分数。好的参数应该让正样本分布在高分区,负样本集中在低分区,中间留出清晰分界。如果正负样本分数重叠严重,优先查角度向量里是否混入了不稳定的关节,比如脚踝、手腕这类小关节,考虑把它们从向量中剔除,只保留大关节角度。
向答辩演示环境移植时,把项目打包成 exe 可以让现场演示更稳定,不用依赖开发环境里的各种 PATH 设置。打包前把 OpenPose 的 models 目录和配置参数路径改成相对路径,避免换机器后模型路径失效。我第一次打包跑完后在另一台电脑上测试,程序启动直接崩,就是因为模型文件夹用的绝对路径——换成相对路径后问题消失。从那以后,我每次做姿态识别项目都会强制走一遍「参数调整 → 正负样本验证 → 打包前路径检查」的三步流程,省掉了大量现场翻车的可能性。希望帮到你。
本文还有配套的精品资源,点击获取