ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python视觉识别健身动作指导源码:深蹲角度计算与状态机判定

2026/10/2 14:19:13 拓冰建站 浏览量
Python视觉识别健身动作指导源码:深蹲角度计算与状态机判定 简介这份源码资源面向希望将计算机视觉落地到健身场景的Python开发者与健身科技爱好者针对无教练指导下动作不规范、影响训练效果甚至造成损伤的痛点提供一套可运行的健身动作识别与指导方案。压缩包共20个文件约83KB以8个py源码文件为核心配合7个xml界面布局、2个txt说明、1个md文档及license、iml等配置涵盖主程序入口、src模块、utils工具与data数据目录结构清晰便于二次开发。已有88人学习下载。读者可从中获取人体姿态估计、动作识别算法、数据采集处理与实时反馈机制的完整实现思路理解如何借助OpenCV与深度学习框架完成关键点比对与规范性判断并参考模块化设计快速搭建自己的健身指导应用适合作为课程设计、毕业项目或视觉识别练手案例。1. 从一段手机拍摄的深蹲视频说起这套 Python 视觉识别源码到底能干什么健身房里最常见的场景会员对着镜子做深蹲膝盖内扣了自己看不见教练在十米外带别的学员。等发现动作变形一组已经做完膝盖压力也吃满了。这套基于 Python 的视觉识别健身动作指导源码解决的就是这个「没人盯动作」的缺口——用普通摄像头或手机录一段视频程序把人体关键点抽出来算出膝角、髋角、躯干倾角这些量化指标再对照标准动作区间给出「膝盖内扣」「下蹲深度不足」这类判断。它适合三类人想入门计算机视觉但不想啃纯理论的 Python 学习者手里有健身内容想做动作纠错产品的开发者以及体育/康复方向想把动作评估数字化的从业者。整套代码是源码包形式不是调个 API 就完事的黑匣子关键点检测、角度计算、判定逻辑都能改。下面按「跑起来 → 看懂原理 → 改参数 → 避坑」的顺序拆新手能照着复现熟手能直接看到可扩展的边界在哪。2. 环境搭起来Python 版本、依赖与摄像头链路的选型理由2.1 为什么是 Python MediaPipe 这条技术路线动作识别有两条主流路线一条是端到端的动作分类模型输入视频直接输出动作类别另一条是先做人体姿态估计、再基于关键点做几何判定。这套源码走的是第二条。原因很实际——端到端分类模型要标注大量「标准深蹲/错误深蹲」视频个人开发者根本凑不齐数据集而姿态估计模型是预训练好的你只需要在关键点坐标上写角度公式几十行代码就能出判定结果。姿态估计这一层常见做法是用 MediaPipe Pose 或 OpenPose。MediaPipe 的优势是 CPU 上就能跑到实时单帧 30ms 左右装完即用不需要配 CUDAOpenPose 精度略高但依赖重、部署麻烦。源码包默认走 MediaPipe这也是我推荐新手先跑通的原因——先把判定逻辑调对再考虑换更重的模型提精度。提示如果你的机器没有独立显卡别一上来就想着换 YOLO-Pose 或 HRNetMediaPipe 在普通笔记本上跑 720p 视频已经够用先把流程跑通比追求精度重要。2.2 环境安装与依赖清单Python 版本建议 3.8 到 3.10MediaPipe 对 3.11 以上的支持在部分平台还不稳定这是血泪经验——我见过有人在 3.12 上装 mediapipe 直接编译失败。用虚拟环境隔离别污染系统 Python。# 创建并激活虚拟环境python 版本锁在 3.9 比较稳 python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate # 安装核心依赖 pip install mediapipe0.10.9 pip install opencv-python4.8.1.78 pip install numpy1.24.3三个包各司其职mediapipe负责从每帧图像里吐出 33 个人体关键点的归一化坐标opencv-python负责读视频、画骨架、写输出numpy负责角度计算里的向量运算。版本号不是随便写的mediapipe 0.10.x 和 numpy 2.x 有兼容问题锁在 1.24 能避开一堆np.float相关的报错。装完验证一下import mediapipe as mp import cv2 import numpy as np print(mediapipe:, mp.__version__) print(opencv:, cv2.__version__) print(numpy:, np.__version__)三行都打印出版本号说明环境通了。如果 mediapipe 导入报AttributeError或ImportError九成是 numpy 版本冲突回退到 1.24.x 即可。2.3 视频输入链路的两种接法源码支持两种输入本地视频文件和实时摄像头。本地文件适合调试判定逻辑因为可以反复跑同一段视频对比结果摄像头适合演示但调试时动作不可复现容易把自己绕进去。import cv2 # 方式一读本地视频调试阶段首选 cap cv2.VideoCapture(squat_demo.mp4) # 方式二读摄像头索引 0 通常是默认摄像头 # cap cv2.VideoCapture(0) if not cap.isOpened(): raise RuntimeError(视频源打不开检查路径或摄像头索引) fps cap.get(cv2.CAP_PROP_FPS) w int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) h int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) print(f分辨率 {w}x{h}, 帧率 {fps})VideoCapture的参数就是路径或设备索引。调试阶段强烈建议用本地视频把同一段深蹲视频跑十遍看判定结果是否稳定这比对着摄像头反复蹲要高效得多。分辨率建议 720p1080p 在 CPU 上跑 MediaPipe 会掉帧判定逻辑依赖连续帧掉帧会让角度曲线出现跳变。3. 关键点怎么变成判定结果角度计算与动作状态机3.1 从 33 个关键点到三个核心角度MediaPipe Pose 每帧输出 33 个关键点每个点有 x、y、z 和可见度。做深蹲判定真正用到的只有几个髋23、24、膝25、26、踝27、28、肩11、12。核心思路是把三个点连成两条向量算夹角。import numpy as np def calc_angle(a, b, c): 计算 b 点为顶点时向量 ba 与 bc 的夹角返回角度值 a, b, c np.array(a), np.array(b), np.array(c) ba a - b bc c - b # 点积公式求夹角加 1e-6 防止除零 cosine np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc) 1e-6) # 数值裁剪到 [-1,1]避免浮点误差导致 arccos 报 nan cosine np.clip(cosine, -1.0, 1.0) return np.degrees(np.arccos(cosine)) # 以左腿为例关键点索引 # 髋 23膝 25踝 27 knee_angle calc_angle(hip[23], knee[25], ankle[27]) hip_angle calc_angle(shoulder[11], hip[23], knee[25])calc_angle里三个参数是「点 A - 顶点 B - 点 C」的顺序顶点放中间。膝角用髋-膝-踝髋角用肩-髋-膝。np.clip那行是后悔药——浮点误差会让余弦值偶尔超出 [-1,1]arccos直接返回 nan整个判定就崩了加个裁剪能省掉大量排查时间。3.2 用状态机判断「一次完整的深蹲」单帧角度没有意义你得知道动作进行到哪一步。深蹲的完整周期是站立膝角接近 180°→ 下蹲膝角减小→ 最低点膝角最小→ 起身膝角增大→ 回到站立。用一个简单的状态机跟踪。class SquatFSM: def __init__(self): self.state STAND # 初始站立 self.min_knee_angle 180 # 记录本次下蹲的最小膝角 self.rep_count 0 # 完成次数 def update(self, knee_angle): if self.state STAND and knee_angle 150: # 从站立进入下蹲 self.state DOWN self.min_knee_angle knee_angle elif self.state DOWN: # 持续记录最低点 self.min_knee_angle min(self.min_knee_angle, knee_angle) if knee_angle 160: # 起身回到站立完成一次 self.state STAND self.rep_count 1 return self.judge(self.min_knee_angle) return None def judge(self, min_angle): # 最低点膝角大于 100 度判定下蹲深度不足 if min_angle 100: return 下蹲深度不足 return 动作达标状态机的两个阈值 150 和 160 是滞回区间防止角度在临界值附近抖动导致状态反复横跳。min_knee_angle记录整个下蹲过程的最小值用它来判断深度比用某一帧的角度可靠得多。判定逻辑集中在judge方法里想加「膝盖内扣」检测就在这个方法里补膝-踝连线与垂直方向的夹角判断。3.3 把骨架和判定结果画回视频光有数字不够直观得把关键点、骨架、角度值、判定文字叠加到画面上输出成可回看的视频。import mediapipe as mp import cv2 mp_pose mp.solutions.pose mp_draw mp.solutions.drawing_utils pose mp_pose.Pose( static_image_modeFalse, # 视频流用 False会做帧间跟踪 model_complexity1, # 0 最快 1 均衡 2 最准 min_detection_confidence0.5, min_tracking_confidence0.5 ) out cv2.VideoWriter(output.mp4, cv2.VideoWriter_fourcc(*mp4v), fps, (w, h)) while cap.isOpened(): ret, frame cap.read() if not ret: break rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) result pose.process(rgb) if result.pose_landmarks: mp_draw.draw_landmarks(frame, result.pose_landmarks, mp_pose.POSE_CONNECTIONS) # 取关键点像素坐标后续算角度、更新状态机 h_, w_ frame.shape[:2] lm result.pose_landmarks.landmark knee (lm[25].x * w_, lm[25].y * h_) # ... 省略其余点提取 out.write(frame) cap.release() out.release()static_image_modeFalse是关键视频流下 MediaPipe 会用上一帧的结果做跟踪速度和稳定性都更好设成 True 每帧独立检测又慢又抖。model_complexity从 0 到 2精度和速度的权衡CPU 上跑建议 1。min_detection_confidence调到 0.5 是平衡值调太高会漏检调太低会误检背景里的杂物。4. 避坑与排查跑不通、判不准、卡成 PPT 的常见原因4.1 关键点抖动导致判定结果乱跳现象同一段视频跑两遍一次说达标一次说深度不足角度曲线像锯齿。原因MediaPipe 逐帧检测有随机误差尤其脚踝和膝在快速下蹲时可见度低坐标会跳。单帧角度直接拿去判定结果自然不稳。解决对角度做滑动平均或指数平滑。常见做法是维护一个长度 5 的队列取均值再送进状态机。别用太长的窗口否则最低点会被抹平深度判定反而失准。from collections import deque angle_buffer deque(maxlen5) def smooth_angle(new_angle): angle_buffer.append(new_angle) return sum(angle_buffer) / len(angle_buffer)4.2 摄像头打不开或读到黑屏现象cap.isOpened()返回 False或者读出来的帧全黑。原因设备索引不对或者摄像头被其他程序占用视频会议软件最常见或者 Linux 下没有访问权限。解决先换索引试 0、1、2关掉占用摄像头的程序Linux 下把用户加进 video 组。本地视频黑屏则多半是编码格式问题换 H.264 编码的 mp4 重试。4.3 帧率太低判定跟不上动作现象视频播放卡顿一次深蹲只抓到三四帧状态机来不及记录最低点。原因1080p 以上分辨率在 CPU 上跑 MediaPipe 会掉到 10fps 以下或者model_complexity设成了 2。解决把输入分辨率降到 720p 甚至 480pmodel_complexity设 0 或 1。判定逻辑依赖帧数宁可画质差一点也要保证帧率。如果必须高分辨率先把视频抽帧存成图片离线批量处理不追求实时。4.4 角度算出来是 nan 或明显离谱现象打印角度出现 nan或者算出 300 多度这种不可能的值。原因关键点可见度太低时坐标是无效的或者calc_angle里没做除零保护和数值裁剪。解决算角度前先检查三个点的visibility低于 0.5 就跳过这一帧沿用上一帧的角度。calc_angle里的1e-6和np.clip必须保留这是防 nan 的底线。4.5 判定阈值照搬别人的自己视频全判错现象代码逻辑没问题但所有动作都被判成不达标。原因不同身高、拍摄角度、镜头焦距下同样的动作角度分布不一样。别人视频里膝角 90° 是标准你的机位拍出来可能是 110°。解决先跑一段自己的标准动作视频把每帧角度打印出来看最低点实际落在哪个区间再据此调judge里的阈值。别迷信默认值阈值是标定出来的不是抄来的。5. 进阶玩法把单动作判定扩成多动作与实时反馈跑通深蹲之后这套源码的骨架可以往两个方向扩。一是多动作支持二是实时反馈。多动作的核心是把「角度组合」抽象成动作模板深蹲看膝角和髋角俯卧撑看肘角和躯干倾角硬拉看髋角和脊柱曲度。每个动作写一个判定类共用同一套关键点提取和平滑逻辑主循环里根据用户选择切换判定器。class PushupJudge: 俯卧撑判定肘角 躯干是否成直线 def __init__(self): self.state UP self.min_elbow 180 def update(self, elbow_angle, body_angle): # body_angle 是肩-髋-踝夹角接近 180 表示躯干挺直 if self.state UP and elbow_angle 120: self.state DOWN self.min_elbow elbow_angle elif self.state DOWN: self.min_elbow min(self.min_elbow, elbow_angle) if elbow_angle 150: self.state UP if body_angle 160: return 塌腰躯干没挺直 if self.min_elbow 100: return 下放深度不足 return 动作达标 return Nonebody_angle用肩-髋-踝三点算接近 180° 说明身体成一条直线小于 160° 就是塌腰或撅臀。这个思路可以复制到任何「关节角度 身体姿态」组合的动作上。实时反馈则是把判定结果从「写进视频」改成「即时显示 语音提示」。用 OpenCV 的putText在画面上叠加文字配合pyttsx3做本地语音播报延迟能控制在可接受范围。注意语音播报别每帧都触发用状态机的返回值做触发条件一次动作只播一次。动作核心角度判定阈值参考常见错误深蹲膝角、髋角最低点膝角 70-100°深度不足、膝盖内扣俯卧撑肘角、躯干角最低点肘角 80-100°塌腰、下放不足硬拉髋角、脊柱角起始髋角 45-60°弓背、髋膝不同步弓步蹲前膝角、后膝角前膝 90° 左右前膝过脚尖、重心不稳阈值这一列是参考区间实际用之前一定按 4.5 节的方法在自己的视频上标定一遍。我现在的习惯是每换一个拍摄机位或换一批测试者先跑十次标准动作把角度分布打出来确认阈值落在合理区间再开判定。这套源码的价值不在于它内置的判定有多准而在于它把「关键点提取 → 角度计算 → 状态机判定 → 可视化输出」这条链路完整摊开给你每一环都能按自己的场景替换。希望帮到你。本文还有配套的精品资源点击获取