新闻详情

新闻详情

首页 / 资讯中心 / 详情

PyQt5+OpenPose太极拳姿态识别系统:骨骼关键点与动作评分实战

发布时间:2026/10/1 16:22:11来源:尧图网络
PyQt5+OpenPose太极拳姿态识别系统:骨骼关键点与动作评分实战
简介基于PyQt5和OpenPose的太极拳姿态识别系统可视化界面源码包面向计算机相关专业正在进行毕业设计、课程设计或期末大作业的学生也适合希望借助实际项目提升Python与深度学习应用能力的初学者。系统通过OpenPose完成人体关键点检测配合PyQt5构建可视化交互界面可对太极拳动作进行实时分析与展示整套代码经过导师指导并按高分要求完成确保可直接运行。压缩包共116个文件大小约1.74MB其中80个jpg图片构成姿态样本数据集13个py文件分别负责界面、识别与主流程另含pyc编译文件、xml配置文件及说明文档目录结构清晰便于检索和二次开发。系统涉及OpenPose模型调用、关键点坐标处理、GUI事件循环等知识点文件中的示例图片与配置内容可辅助理解数据组织方式与运行流程。目前已有103人学习下载可作为毕业设计参考实现也可用于快速搭建姿态识别项目原型。1. 先看定位PyQt5OpenPose 姿态识别系统到底解决什么问题拿到这套「基于 PyQt5OpenPose 的太极拳姿态识别系统」源码包别急着双击跑 demo。它解决的核心问题不是“识别人”而是“判断动作标不标准”摄像头前一个人打野马分鬃系统要实时画出骨架关键点给出当前帧与标准动作的相似度评分。整个项目拆开看就是三块——OpenPose 负责从视频帧里估计人体骨骼关键点PyQt5 负责把视频流和叠加骨架画到界面上中间夹一层动作比对逻辑。这套东西适合两类人课程设计和毕业设计需要交可视化系统的学生以及想在企业项目里引入姿态比对能力的工程师。前者可以直接把界面和评分模块当交付外壳把精力花在打磨演示流畅度上后者更该关注数据集组织方式和角度特征设计别被源码里的模型加载代码带偏那部分只是骨架真正决定识别效果的是参考动作的质量和比对规则。需要提醒的是这类源码包给到手里数据集部分往往是有标注或半标注的太极拳视频帧格式和 OpenPose 的关键点输出能不能直接对齐才是后续所有工作能否成立的前提。下面所有章节都围绕“从跑通源码到自己改造”这条线展开。2. OpenPose 在系统里的定位关键点输出、数据格式与选型对比2.1 从姿态识别综述里选型为什么 OpenPose 绕不开翻近几年的人体姿态识别综述OpenPose 永远是绕不开的一个名字。它走的是“自底向上”路线先在整张图上预测所有关键点的热图再用 Part Affinity Fields部件亲和场把属于同一个人的关键点组装起来。这和 MediaPipe、YOLOv8-pose 那种“先检测人框、再回归框内关键点”的“自顶向下”思路有本质区别。太极拳动作里两臂经常在胸前交叉身体自遮挡严重PAF 在处理这类场景时对关键点连接关系的鲁棒性比纯人框回归更好这正是这套系统选它而不是更轻量方案的核心原因。OpenPose 的 Python 绑定封装得比较“直白”核心就几步import cv2 from openpose import pyopenpose as op params { model_folder: ./models/, net_resolution: 656x368, number_people_max: 1, render_pose: 0, } op_wrapper op.WrapperPython() op_wrapper.configure(params) op_wrapper.start() datum op.Datum() image cv2.imread(taichi_frame.jpg) datum.cvInputData image op_wrapper.emplaceAndPop([datum]) keypoints datum.poseKeypoints print(keypoints.shape) # (人数, 关键点数, 3) - (1, 18, 3)最后一行输出非常关键OpenPose 默认 COCO 模型给每个人返回 18 个关键点每个点包含 x、y 坐标和置信度分数三维数组的第三个维度就是(x, y, score)。emplaceAndPop是同步调用执行完当前帧的推理后结果直接写在datum.poseKeypoints里不需要额外等回调。参数层面有几个我每次都会先确认的地方。model_folder指向的目录要放 OpenPose 的预训练模型路径写错会直接抛出文件找不到net_resolution控制网络输入分辨率它直接影响速度和精度平衡后面避坑章节会展开number_people_max设为 1 能让多人组装阶段跳过不必要的计算单人教学场景里这是白赚的性能render_pose设为 0 则关闭 OpenPose 自带的渲染输出因为我们要在 PyQt5 里用 QPainter 自己画骨架不需要它多渲染一遍。提示op_wrapper在整个进程里只需要创建一次。如果每个线程或每帧都新建 Wrapper模型会被反复加载到显存实测显存占用直接翻倍帧率反而下降。2.2 COCO 18 点排布索引表和坐标系约定做姿态比对第一件事不是跑通模型而是把 18 个关键点的索引背熟。OpenPose 的 COCO 模型输出顺序是固定的前面是五官和躯干后面是四肢关节索引关键点索引关键点0鼻子9右膝1脖子10右踝2右肩11左髋3右肘12左膝4右腕13左踝5左肩14右眼6左肘15左眼7左腕16右耳8右髋17左耳特别注意左右是“人物自身视角”的左右站在摄像机对面的人抬起他的右手在画面里看是屏幕左侧但 OpenPose 会给它标成“右腕”索引 4。做界面镜像显示时如果直接把画面做水平翻转骨架标注会和身体错位这是新手最容易翻车的地方。坐标的坐标系以图像左上角为原点x 轴向右y 轴向下单位是像素。OpenPose 返回的坐标是相对输入分辨率归一化之前的原始像素坐标也就是说只要你改过net_resolution输出的坐标仍然对应原图尺寸不需要手动缩放。这一点做得比很多后起之秀省心适合直接拿来做视频帧叠加。2.3 换 MediaPipe 或 YOLOv8-pose 行不行选型对比与性能预算很多人拿到这套源码的第一反应是“OpenPose 安装太麻烦能不能换掉”。能换但要算清楚账。这三条路线我都试过先看实时性和安装成本对比方案检测思路CPU 实时GPU 实时安装成本交叉遮挡表现OpenPose自底向上热图PAF困难可约 10-20 fps高需要编译或找预编译包较好MediaPipe Pose自顶向下可约 30 fps可低pip 直接装一般YOLOv8-pose自顶向下可约 20-30 fps可低pip 直接装一般表面上看 OpenPose 在安装成本上最吃亏但换方案的隐藏成本在数据集和代码结构上。这套源码的数据集标注、参考动作 JSON 格式都是围绕 OpenPose 的 18 点输出组织的换到 MediaPipe 就是 33 点、换到 YOLOv8-pose 是 17 点且索引含义不同所有预处理、角度特征提取、评分模块全部要重写等于把核心逻辑重做一遍。另一个现实问题是精度。MediaPipe 在自遮挡明显时容易出现左右手腕跳变而太极拳的“云手”“抱球”等动作恰恰是两臂交叉最密集的。我做对比测试时OpenPose 在手臂交叉瞬间的关键点置信度下降曲线明显更平缓。所以我的结论是如果只是做人形检测或健身计数换掉 OpenPose 没问题如果要做动作规范性比对还是沿着现有技术栈往下走更稳时间要花在特征工程上而不是重新踩一遍数据对齐的坑。性能预算也得心里有数。net_resolution从默认的656x368降到368x256推理耗时能降低一半左右关键点精度损失在太极拳这种大幅度动作下并不明显。如果 GPU 显存只有 2GB建议直接降到320x240并把number_people_max保持为 1。CPU 上跑 OpenPose 基本是幻灯片的帧率别指望实时做离线视频帧分析可以接受。3. 用 PyQt5 搭可视化界面线程模型、关键点叠加与控件3.1 pyqt5安装与项目目录先把环境从 Lab 里隔离出来PyQt5 本身安装不算复杂但和 OpenPose 的 Python 绑定放在一起就容易出幺蛾子。OpenPose 的绑定依赖特定版本的 numpy 和 protobufPyQt5 又不挑 numpy冲突点主要在 Python 环境被别的项目污染过。我的习惯是直接建独立的 venv不偷懒用全局环境。python -m venv venv_pose source venv_pose/bin/activate # Windows 用 venv_pose\Scripts\activate pip install PyQt5 pip install numpy opencv-pythonPyQt5 安装时如果卡在下载阶段换成国内 PyPI 镜像会快很多。在 PyCharm 里导入这个项目时记得在 Settings - Project - Python Interpreter 里选到venv_pose/bin/python否则 PyCharm 用的是基础解释器装好的 PyQt5 根本 import 不到。这个步骤看着琐碎但隔三差五就有人卡在 ModuleNotFoundError: No module named PyQt5 上多半是解释器没切过来。项目目录我一般按这种方式组织源码包到手后也先重构成这样再动手taichi_pose/ ├── main.py # 程序入口 ├── ui/ │ ├── main_window.py # PyQt5 主界面 │ └── pose_widget.py # 自定义绘制控件 ├── core/ │ ├── openpose_engine.py # OpenPose 封装 │ └── compare.py # 角度特征与评分 ├── models/ # OpenPose 模型文件 ├── dataset/ # 参考动作 JSON └── requirements.txt这样拆分的好处是OpenPose 初始化、界面绘制、比对逻辑互不污染哪个模块出问题单独替换。等会儿你要改评分公式只需要动core/compare.py不用翻界面代码。3.2 PyQt5 界面设计主线程只管画推理交给 QThreadPyQt5 界面卡死的头号原因是把推理这种耗时操作直接放进 GUI 线程。OpenPose 单帧推理在 GPU 上也要几十毫秒CPU 上更是秒级放在主线程里界面事件循环被阻塞表现就是窗口拖不动、按钮点了没反应、黑屏。正确做法是用 QThread 跑推理主线程只负责接收信号、绘制画面。import cv2 from PyQt5.QtCore import QThread, pyqtSignal class PoseThread(QThread): frame_ready pyqtSignal(object, object) def __init__(self, op_wrapper, video_source0): super().__init__() self.op_wrapper op_wrapper self.cap cv2.VideoCapture(video_source) self.running True self.paused False def run(self): while self.running: if not self.paused: ret, frame self.cap.read() if ret: datum op.Datum() datum.cvInputData frame self.op_wrapper.emplaceAndPop([datum]) kpts datum.poseKeypoints self.frame_ready.emit(frame, kpts) self.msleep(33) def stop(self): self.running False self.cap.release() self.wait()frame_ready是一个 pyqtSignal参数类型写成object是因为 OpenPose 的poseKeypoints是 numpy 数组PyQt5 的信号槽机制默认不认识 numpy 类型用object可以绕过类型检查。self.msleep(33)是给循环一个约 33ms 的喘息避免摄像头读取空转烧 CPU但实际推理如果花了 200ms这个 sleep 不会生效循环会被推理时间自然限速。信号接收端在主界面里收到frame和kpts后只做两件事转成 QImage、更新控件。千万别在槽函数里再写耗时逻辑否则 QThread 好不容易省下来的流畅度又会在主线程里丢掉。3.3 QPainter 画骨架与滑杆阈值把 OpenPose 输出变成能看的画面关键点叠加有两种做法一种是用 OpenCV 的cv2.line直接把骨架画在帧上再转 QImage另一种是自定义 QWidget 重写paintEvent。我推荐后者原因很简单QPainter 绘制的抗锯齿和透明度控制更好而且阈值滑杆调整时不需要重新生成整帧图像只需触发重绘。import cv2 import numpy as np from PyQt5.QtWidgets import QLabel from PyQt5.QtGui import QPainter, QPen, QImage from PyQt5.QtCore import Qt class PoseWidget(QLabel): def __init__(self): super().__init__() self._frame None self._keypoints None self._threshold 0.5 def update_data(self, frame, keypoints): self._frame np.ascontiguousarray(frame) self._keypoints keypoints self.update() def paintEvent(self, event): if self._frame is None: return painter QPainter(self) rgb cv2.cvtColor(self._frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) painter.drawImage(self.rect(), qimg) if self._keypoints is not None: self._draw_skeleton(painter, self._keypoints) painter.setPen(QPen(Qt.red, 1)) painter.drawText(10, 30, f阈值: {self._threshold:.2f}) painter.end() def _draw_skeleton(self, painter, keypoints): if keypoints.size 0: return kpts keypoints[0] # 取第一个人 skeleton [ (0, 1), (1, 2), (2, 3), (3, 4), (1, 5), (5, 6), (6, 7), (1, 8), (8, 9), (9, 10), (10, 11), (8, 12), (12, 13), (13, 14), ] painter.setPen(QPen(Qt.green, 3)) for a, b in skeleton: if kpts[a][2] self._threshold and kpts[b][2] self._threshold: painter.drawLine(int(kpts[a][0]), int(kpts[a][1]), int(kpts[b][0]), int(kpts[b][1]))这段代码里藏着两个实际坑。第一QImage(rgb.data, ...)引用的是 numpy 数组的内存如果rgb变量在paintEvent返回后被 Python 垃圾回收QPixmap 绘制时会读到被释放的内存画面会出现花屏或随机色块。所以update_data里我用了np.ascontiguousarray(frame)强制拷贝一份连续内存确保 QImage 持有的是独立数据。第二keypoints[0]假设画面里只有一个人number_people_max1已经限制了单人的场景但如果画面里误入第二个人OpenPose 依然可能输出两个人这里需要加一个人数判断优先选置信度之和较高的那个。阈值滑杆用 QSlider 就可以范围设 0 到 100实际映射到 0.0 到 1.0slider QSlider(Qt.Horizontal) slider.setRange(0, 100) slider.setValue(50) slider.valueChanged.connect(self._on_threshold_changed) def _on_threshold_changed(self, value): self.pose_widget._threshold value / 100.0这个滑杆的作用是过滤低置信度的关键点。背景里有椅子、柱子之类的误检置信度通常会低于 0.3把阈值拉到 0.5 以上就能滤掉大部分噪声但拉太高会连真实关节一起滤掉具体值要根据摄像头画质调。4. 动作比对的实现思路角度特征、余弦相似度与评分4.1 参考动作怎么录采集、去抖、对齐三步走评分的前提是先有一条“标准动作”的参考数据。常见做法是让动作标准的人对着同一摄像头打一遍完整动作用跟识别端一样的 OpenPose 配置提取出每帧的关键点再保存成 JSON。录制时的人位置、摄像头高度、焦距必须和后续使用时保持一致否则后面做角度特征也救不回来。原始关键点序列不能直接用因为打拳速度快慢会导致同一动作在不同视频里帧数不一样。需要做两步处理中值滤波去抖和帧数归一化。中值滤波的作用是消除单帧关键点跳变比如手腕在某两帧突然偏离十几像素帧数归一化是让人人都能对齐到同一时间轴。import numpy as np def resample_sequence(keypoints_seq, target_len100): # keypoints_seq: (T, 18, 3) 的 numpy 数组 T keypoints_seq.shape[0] old_indices np.linspace(0, T - 1, numT) new_indices np.linspace(0, T - 1, numtarget_len) resampled np.stack([ np.interp(new_indices, old_indices, keypoints_seq[:, i, j]) for i in range(18) for j in range(3) ], axis1).reshape(target_len, 18, 3) return resampled def smooth_sequence(keypoints_seq, window5): from scipy.ndimage import median_filter return median_filter(keypoints_seq, size(window, 1, 1))resample_sequence的原理很简单把原序列的帧索引映射到 0 到 T-1 的均匀区间再重新采到固定长度。这里每个关键点的 x、y、score 三个通道分别插值score 通道也会被插值但实际使用时建议只保留原始置信度不被插值改变。smooth_sequence用 scipy 的median_filter只在时间轴方向滤波空间坐标不受影响。4.2 为什么不直接比关键点坐标角度特征与余弦相似度如果直接把关键点坐标拿来算相似度新手会很快发现同一个动作两个人身高不同、离镜头远近不同坐标数值天生对不上。就算同一个人打两遍站位偏差几个像素坐标欧氏距离也会算出很低的分数。坐标是绝对量拿它做对比必须先归一化到同一尺度同一原点这一步很麻烦且容易引入新误差。更稳的做法是提取关节角度。角度只关心“肘关节弯曲多少度”“膝盖弯曲多少度”天然消除了平移和缩放的影响。我一般选取八个角度作为动作特征左右肘角、左右膝角、左右肩髋连线与竖直方向夹角、左右髋膝连线与水平方向夹角。对太极拳来说前四个角度决定四肢姿态后四个角度决定躯干倾角和弓步深浅。import math def vector_angle(a, b, c): ab [b[0] - a[0], b[1] - a[1]] bc [c[0] - b[0], c[1] - b[1]] dot ab[0] * bc[0] ab[1] * bc[1] mod_ab math.sqrt(ab[0] ** 2 ab[1] ** 2) mod_bc math.sqrt(bc[0] ** 2 bc[1] ** 2) if mod_ab 0 or mod_bc 0: return 0.0 cos_val max(-1.0, min(1.0, dot / (mod_ab * mod_bc))) return math.degrees(math.acos(cos_val)) def extract_angle_features(kpts): features [] # kpts: (18, 3)坐标索引参考第2章表格 features.append(vector_angle(kpts[5][:2], kpts[6][:2], kpts[7][:2])) # 左肘角 features.append(vector_angle(kpts[2][:2], kpts[3][:2], kpts[4][:2])) # 右肘角 features.append(vector_angle(kpts[11][:2], kpts[12][:2], kpts[13][:2])) # 左膝角 features.append(vector_angle(kpts[8][:2], kpts[9][:2], kpts[10][:2])) # 右膝角 return features角度都取在 0 到 180 度之间这样特征向量天然落在相近的数值范围内适合直接做余弦相似度。余弦相似度衡量的是两个向量的方向一致性相比欧氏距离它对向量整体幅度的变化不敏感。在角度特征上使用余弦相似度的好处是如果某个人整体关节都比参考动作“偏屈”几度只要形态相似方向一致性依然高分数不会骤降。import numpy as np def cosine_similarity(a, b): a np.asarray(a, dtypenp.float32).flatten() b np.asarray(b, dtypenp.float32).flatten() norm_a np.linalg.norm(a) norm_b np.linalg.norm(b) if norm_a 0 or norm_b 0: return 0.0 return float(np.dot(a, b) / (norm_a * norm_b))4.3 评分公式与置信度加权从单帧分数到动作总分拿到余弦相似度后最简单粗暴的映射是(sim 1) * 50相似度 0.8 对应 90 分1.0 对应满分0.6 对应 80 分。但实际跑通会发现单帧分数噪声极大手腕抖动一下可能掉十几分。我的做法是加一个帧窗口平滑取最近 5 帧的分数平均值作为当前显示分。def frame_score(ref_features, cur_features, feature_scores): # feature_scores: 每个角度对应的平均置信度长度与特征一致 if len(ref_features) ! len(cur_features): return 0.0 valid_scores [s for s in feature_scores if s 0.5] if len(valid_scores) 0: return 0.0 total 0.0 weight_sum 0.0 for ref, cur, s in zip(ref_features, cur_features, feature_scores): if s 0.5: continue angle_diff abs(ref - cur) / 45.0 angle_score max(0.0, min(1.0, 1.0 - angle_diff)) total angle_score * s weight_sum s if weight_sum 0: return 0.0 return total / weight_sum * 100 class ActionScorer: def __init__(self, ref_seq, window5): self.ref_seq ref_seq self.window window self.recent_scores [] def add_frame(self, cur_features, feature_scores): # 找参考序列里最接近当前帧的索引这里简化成直接取当前帧比例位置 ref_idx int(len(self.ref_seq) * min(1.0, len(self.recent_scores) / 100)) ref_features self.ref_seq[ref_idx] score frame_score(ref_features, cur_features, feature_scores) self.recent_scores.append(score) if len(self.recent_scores) self.window: self.recent_scores.pop(0) return sum(self.recent_scores) / len(self.recent_scores)frame_score里我用角度差 45 度作为线性衰减的基准差 0 度得满分差 45 度以上得 0 分。乘上的s是当前角度所涉及三个关键点置信度的平均值置信度低的角度对总分影响小。这个设计比单纯余弦相似度更能定位到“哪个关节没做到位”界面里可以据此给出具体反馈比如“左肘角度偏差 12 度”。需要说明的是ActionScorer里参考帧索引的计算是简化版只按进度比例取参考帧。实际更好的做法是在参考序列上做一个滑动窗口匹配用动态时间规整找最优对齐路径但在最小可用的源码改造里先按比例对齐足够跑通流程再考虑 DTW 优化。这个取舍能省下不少调参时间。5. 避坑与排查跑这套系统的 5 条血泪经验5.1 现象labelme 和 PyQt5 环境打架import 直接崩现象在同一个 Python 环境里先装了 labelme再装 PyQt5运行import PyQt5时直接段错误崩溃或者报Could not load the Qt platform plugin xcb之类的问题。有时连 OpenCV 的cv2.imshow都跟着打不开窗口。原因labelme 依赖的 PyQt5 版本往往停留在旧版它安装时可能覆盖或降级了一些 Qt 运行时库而新装的 PyQt5 和它冲突导致 Qt 平台插件加载失败。另外 labelme 还会拖入特定版本的 numpy和 OpenPose 绑定的 numpy 需求形成隐式冲突。解决把这个项目单独放进 venv和 labelme 彻底隔离。先建虚拟环境再装 PyQt5 和 OpenPoselabelme 留在全局环境或者另一个环境里不要让两边共享同一个 site-packages。如果已经装乱了最简单的方法是删掉 venv 重建不要试图一个个卸载重装那种做法很容易漏掉某个 Qt 动态库。5.2 现象OpenPose 推理太慢界面帧率掉到个位数现象GPU 上跑net_resolution默认的656x368一帧推理要 100ms 以上CPU 上更是要 2 到 3 秒画面跟幻灯片一样用户完全没法跟练。原因net_resolution越大网络计算量指数上升。很多源码包默认值没调直接上了最大分辨率同时render_pose保持默认 1OpenPose 会额外花时间渲染骨架图白白消耗算力。解决把net_resolution从656x368降到368x256speed模式实测推理耗时能降一半左右太极拳这种大幅度动作的关键点精度损失不明显。再确认render_pose设为 0关闭自带的渲染输出。如果显存只有 2GB进一步降到320x240。CPU 机器上不要指望实时建议把分析改成离线逐帧处理文本。5.3 现象骨架错位左右手认反现象人站在镜头前生成的骨架线条交叉错乱有时候左手腕被接到右手肘上做“云手”动作时两臂在胸前交叠的一瞬间骨架线从正确位置跳到另一侧。原因OpenPose 是自底向上的组装逻辑当两个关键点靠得很近时PAF 的匹配也可能判断错误左右手交叉时部件亲和场的连接线索出现歧义导致左右手互换。此外如果输入分辨率太低关节细节被压缩也容易出这种问题。解决先调高输入分辨率到368x256以上看是否缓解。再把number_people_max保持为 1避免多人场景下组装阶段的额外干扰。如果依然错位把置信度阈值从 0.5 提到 0.7让低置信度的连接不被绘制宁缺勿错。摄像头画面里尽量让人体完整可见手部不要超出画面边缘一旦手腕被截断OpenPose 大概率会把残留部分拼到别的关节上。5.4 现象用 PyInstaller 打包成 exe 后模型文件加载失败现象源码在 IDE 里跑一切正常打包成单个 exe 后双击运行程序直接闪退或弹窗提示找不到models目录下的权重文件。原因PyInstaller 打包后工作目录变成 exe 所在的目录源码里写死的./models/变成了相对路径实际指向的不再是原始项目目录。同时模型文件默认不会被自动打进 exe只是留在项目目录里用户换一台电脑就没文件了。解决在代码里用Path(__file__).resolve().parent拼接绝对路径打包时用--add-data把 models 目录一并打进去。更稳妥的是运行时先判断是否处于 PyInstaller 冻结环境用sys._MEIPASS作为资源根目录。做法如下import sys from pathlib import Path def get_resource_path(relative_path): base_path getattr(sys, _MEIPASS, Path(__file__).resolve().parent) return str(Path(base_path) / relative_path) params[model_folder] get_resource_path(models/) /sys._MEIPASS是 PyInstaller 在冻结模式下自动设置的一个临时目录打包进去的数据文件会被解压到这个目录下程序运行结束后自动清理。没有打包的环境里getattr兜底回到源码目录两边都能跑。5.5 现象参考动作评分一直在及格线以下现象自己录了一段标准动作提取骨架保存成参考序列但换一个人来打同一套动作评分永远低于 60 分有时甚至连参考帧里的人都识别得不够稳定。原因参考动作录制时摄像头的角度、距离和测试时不一致导致同一动作在画面里的投影角度天然不同更常见的是参考序列没有做时间轴归一化打拳速度快慢导致匹配时对不上帧一帧偏差就能让角度差扩大 20 度以上。解决录制参考动作时固定架好摄像头并标记站位框让后续测试人站在同一个框内。保存前对参考序列做时间轴重采样到固定帧数前文写的resample_sequence就用在这里。如果换了摄像头角度建议重新录参考数据不要拿着一个角度的参考数据去比另一个角度的视频这属于物理不可调和的问题。最后检查一下参考序列的置信度均值如果都有大量低于 0.5 的点说明参考数据本身就是脏的先去查模型和分辨率问题。6. 进阶用法用关节角度变化率自动切分动作摆脱手动开始/停止6.1 滑动窗口动作起止检测很多源码包的交互逻辑是“点开始识别点结束识别”中间所有帧全参与评分。这在实际跟练场景里很僵硬用户做准备动作时是放松站立的如果这段也纳入评分会拉低整体分数。一个有效的改进是让系统自动判断动作何时开始、何时结束而判断依据不需要复杂模型用关节角度变化率就能做到。太极拳起势前人是静态站桩的各个关节角度几乎不变一旦开始动作角度在一两帧内就会出现明显变化。利用这个特性维护一个角度均值序列的滑动窗口计算窗口内帧间差的平均值超过阈值就认为动作开始。动作结束后人会回到静止状态角度变化率降到阈值以下连续保持一段时间后自动结束。import numpy as np class ActionSegmenter: def __init__(self, window10, start_threshold18.0, end_threshold8.0, end_hold30): self.window window self.start_threshold start_threshold self.end_threshold end_threshold self.end_hold end_hold self.angle_history [] self.state idle # idle / running self.static_count 0 def add_frame(self, angle_features): # angle_features 是前文 extract_angle_features 的输出 if len(angle_features) 0: return self.state mean_angle float(np.mean(angle_features)) self.angle_history.append(mean_angle) if len(self.angle_history) self.window 1: self.angle_history.pop(0) if len(self.angle_history) self.window 1: return self.state prev np.array(self.angle_history[:-1]) curr np.array(self.angle_history[1:]) diff float(np.mean(np.abs(curr - prev))) if self.state idle: if diff self.start_threshold: self.state running self.static_count 0 else: if diff self.end_threshold: self.static_count 1 if self.static_count self.end_hold: self.state idle self.static_count 0 else: self.static_count 0 return self.statestart_threshold取 18 度意味着窗口内帧间角度平均变化超过 18 度才认为动作启动能滤掉呼吸造成的轻微肩部起伏。end_hold取 30 帧代表动作结束后需要连续 30 帧保持低变化才真正结束避免动作中间的小停顿被误判为结束。这套参数对太极拳起势、野马分鬃这类“静-动-静”结构比较合适如果是连续不断的套路表演结束检测的意义不大更多是靠 start 来定位每一段的起点。6.2 验证指标与我的习惯动作切分做得好不好要拿数据说话。我习惯录 3 段完整练习视频人工标注每段的起止帧再和算法输出对比计算起点误差和终点误差。目标一般是误差不超过 5 帧超过就调小window或降低start_threshold。阈值调优时不要只看单条视频至少换 2 个不同体型的人测否则参数会过拟合到录制者本人的动作幅度上。做完这套改造系统就从“手动起停的评分工具”升级成“自动跟练的陪练工具”。我最后悔的是做这个功能时没有把angle_history的长度上限设得足够高导致打长套路时历史队列被过早弹出起点检测偶尔漏掉。现在我的习惯是先定义清楚动作状态机的四个状态idle、starting、running、ending再写代码状态机画在纸上五分钟改起逻辑来少走几小时弯路。希望这套角度特征加自动切分的思路能帮到你少踩我当年踩过的那些坑。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

机考10翻译10单词10:三模块闭环备考法,冲刺英语机考 2026/10/1 18:48:35

机考10翻译10单词10:三模块闭环备考法,冲刺英语机考

机考10 翻译10 单词10,这个组合看起来像是一串简单的数字,但实际上是我总结出来的一套考前冲刺节奏。先说结论:这套方法解决的核心问题,是那些“单词背了但翻译写不出、翻译练了但机考跟不上”的断层感。它不追求每天塞满学习时间…

阅读更多 →
线缆方向术语全解析:正向反向、同向异向在不同场景的含义 2026/10/1 18:48:35

线缆方向术语全解析:正向反向、同向异向在不同场景的含义

你一定遇到过这种情况:老师傅递过来一根线,丢下一句“这根要反向接”,你拿着线愣了半天——反向?哪头对哪头?等你好不容易接好了,图纸上又冒出“异向绞合”,你更懵:我说的是接线的反…

阅读更多 →
AI泡沫下的普通人指南:聪明钱调仓方向与四条务实思路 2026/10/1 18:48:35

AI泡沫下的普通人指南:聪明钱调仓方向与四条务实思路

说实话,过去半年我被问到最多的一句话,不是什么技术问题,而是:“AI泡沫到底会不会破?我现在还能不能上车?”问的人里有正在读研的学生,有做了十年软件开发的工程师,也有手里捏着几十…

阅读更多 →
HelloAgents:从LLM扩展到Function Calling,40行代码搭建智能体 2026/10/1 18:48:35

HelloAgents:从LLM扩展到Function Calling,40行代码搭建智能体

如果你是刚刚把第一个大模型接口调通,让它能顺畅地陪你聊完一整轮,下一步最自然的想法大概就是:能不能让它帮我干点活?哪怕是查个天气、读个文件、搜一下资料,而不是只会吐漂亮话。这个问题,恰恰就是“LLM扩…

阅读更多 →
HelloAgents 7.2 LLM扩展:Agent身份认知与RAG/GraphRAG融合实战 2026/10/1 18:48:35

HelloAgents 7.2 LLM扩展:Agent身份认知与RAG/GraphRAG融合实战

直接说结论:HelloAgents这套框架,我在7.2版本之前一直把它当“玩具”用——跑通对话、接几个工具调用就差不多了。但这个版本开始做LLM深度扩展之后,整套东西才算真正长成了可以上业务的样子。我这次扩展的核心,是把LLM的语义能力…

阅读更多 →
【ACM出版 | 大模型相关】2026年人工智能、机器学习与多模态国际学术会议(AIMLM 2026) 2026/10/1 18:48:29

【ACM出版 | 大模型相关】2026年人工智能、机器学习与多模态国际学术会议(AIMLM 2026)

2026年人工智能、机器学习与多模态国际学术会议(AIMLM 2026) 2026 International Conference on Artificial Intelligence, Machine Learning and Multimodality 会议官网: 2026年人工智能、机器学习与多模态国际学术会议(AIML…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉