
简介这份源码资源面向希望将计算机视觉落地到健身场景的Python开发者与健身科技爱好者针对无教练指导下动作不规范、影响训练效果甚至造成损伤的痛点提供一套可运行的健身动作识别与指导实现方案。压缩包共20个文件约83KB以8个py源码文件为核心配合7个xml界面与配置描述、2个txt说明及md文档、license和iml工程文件覆盖主程序入口、src模块、data数据与utils工具等目录结构清晰便于二次开发。项目涉及人体姿态估计、动作识别算法、数据采集处理与实时反馈机制借助OpenCV及TensorFlow、PyTorch等框架完成关键动作点比对与规范性判断。目前已有88人学习适合作为课程设计、毕业设计或视觉识别入门练手项目帮助读者快速理解从摄像头捕捉到动作指导的完整链路并在此基础上扩展训练计划与个性化反馈功能。1. 从一段手机拍摄的深蹲视频说起视觉识别怎么给健身动作当教练健身房里最常见的场景不是没人练而是练错了没人说。深蹲膝盖内扣、硬拉弓背、卧推肘部外翻这些错误动作短期看不出问题积累三五个月就是腰和膝盖的账单。请私教能解决但一节几百块的成本让大多数人只能靠镜子自我纠正而镜子只能看正面侧面和背面的问题基本是盲区。基于 Python 的视觉识别健身动作指导方案要解决的就是这件事用普通摄像头或手机录一段动作视频通过人体姿态估计提取关节点坐标再对关节角度、躯干倾斜、左右对称性做量化判断最后给出「膝盖内扣约 12 度」「下蹲深度不足」这类具体反馈。它适合两类人一类是想自己搭一套家庭动作纠正工具的开发者另一类是想把动作识别能力集成进健身 App 或智能镜子的工程师。核心链路是「视频输入 → 姿态估计 → 角度计算 → 规则判定 → 反馈输出」本文按这条链路把每一步的参数和坑讲清楚。2. 姿态估计选型为什么 MediaPipe 是健身动作识别的默认起点2.1 三条技术路线的取舍健身动作识别底层依赖人体姿态估计把图像里的人还原成一组关键点坐标。目前主流有三条路线选错了后面全是返工。第一条是 OpenPose 系的自底向上方法先检测所有关节点再聚类成人体。优点是多人场景不用预先框人缺点是模型大、推理慢在普通笔记本上跑 1080p 视频经常掉到个位数帧率做实时反馈体验很差。第二条是 YOLO-Pose 这类单阶段检测器把人体框和关键点一起回归速度快、工程化成熟适合需要自己训练特定动作场景的情况但预训练模型对遮挡和非常规姿态的鲁棒性需要额外调。第三条是 MediaPipe Pose / BlazePoseGoogle 针对移动端和桌面端优化的轻量方案单帧推理在 CPU 上就能到 30fps 以上输出 33 个关键点开箱即用。对健身动作识别这个具体场景我一般直接上 MediaPipe。原因是健身动作大多是单人、全身入镜、动作幅度大MediaPipe 的精度足够而且它给的 33 点里包含了脚踝、脚跟、脚尖这些对深蹲和硬拉判定很关键的点OpenPose 的 25 点模型反而没有脚尖。如果你要做多人团课识别或者需要自己标注数据训练特定动作再考虑 YOLO-Pose。2.2 环境搭建与最小可运行代码先确认 Python 版本MediaPipe 对 3.8 到 3.11 支持最稳3.12 早期版本有过 wheel 缺失的问题。用 conda 或 venv 建独立环境别在系统 Python 里装。# 建虚拟环境Python 3.10 是当前兼容性最好的版本 python -m venv fitness_env # Windows 激活 fitness_env\Scripts\activate # macOS / Linux 激活 source fitness_env/bin/activate # 安装核心依赖 pip install mediapipe0.10.9 opencv-python4.9.0.80 numpy1.26.4版本号不是随便写的。mediapipe 0.10.x 之后 API 稳定opencv 4.9 和 numpy 1.26 搭配不会出现np.float被移除的报错。装完跑一段最小代码验证环境import cv2 import mediapipe as mp mp_pose mp.solutions.pose pose mp_pose.Pose( static_image_modeFalse, # 视频流用 False会做帧间跟踪 model_complexity1, # 0 最快最糙1 平衡2 最准最慢 smooth_landmarksTrue, # 关键点平滑减少抖动 min_detection_confidence0.5, # 首帧检测阈值 min_tracking_confidence0.5 # 后续帧跟踪阈值 ) cap cv2.VideoCapture(squat.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break # MediaPipe 要 RGBOpenCV 默认 BGR这行漏了关键点会全乱 rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) result pose.process(rgb) if result.pose_landmarks: # 33 个关键点每个点有 x, y, z, visibility lm result.pose_landmarks.landmark left_knee lm[mp_pose.PoseLandmark.LEFT_KNEE.value] print(f左膝坐标: x{left_knee.x:.3f} y{left_knee.y:.3f} vis{left_knee.visibility:.2f}) cap.release()这段代码的逻辑是逐帧读视频、转色彩空间、送进模型、取关键点。参数里最容易被忽略的是model_complexity做深蹲这种大动作 0 就够做手腕翻转这类精细动作才需要 2。smooth_landmarksTrue在视频模式下会做时序平滑代价是快速动作会有轻微延迟做爆发力动作分析时可以关掉。visibility是每个点的可见置信度低于 0.5 的点在后续角度计算里要丢弃否则会算出离谱的角度。2.3 关键点坐标系与归一化处理MediaPipe 输出的 x、y 是归一化到 0 到 1 的x 乘图像宽度、y 乘高度才是像素坐标。z 是相对深度以髋部中心为原点单位大致和 x 同尺度但精度有限做三维角度计算时不要过度依赖 z。做动作判定时我一般只用 x、y 做二维角度对深蹲、卧推、硬拉这些在矢状面或冠状面完成的动作已经够用。如果要做三维动作捕捉级别的分析需要多摄像头或者换用带三维输出的方案那是另一个量级的工程。3. 从关键点到动作判定关节角度计算与规则引擎3.1 用三点夹角算关节角度拿到关键点后核心计算是关节角度。以膝关节为例取髋、膝、踝三点算膝这个顶点处的夹角。用向量点积公式不依赖任何几何库。import numpy as np def calc_angle(a, b, c): 计算 b 点处的夹角a、b、c 为 (x, y) 坐标 返回角度值范围 0-180 a np.array(a) b np.array(b) c np.array(c) # 向量 ba 和 bc ba a - b bc c - b # 点积和模长 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) angle np.degrees(np.arccos(cosine)) return angle # 从 MediaPipe 结果取点注意转成像素坐标或保持归一化都行角度不受尺度影响 def get_point(lm, idx, w, h): p lm[idx] return (p.x * w, p.y * h) # 左膝角度 hip get_point(lm, mp_pose.PoseLandmark.LEFT_HIP.value, w, h) knee get_point(lm, mp_pose.PoseLandmark.LEFT_KNEE.value, w, h) ankle get_point(lm, mp_pose.PoseLandmark.LEFT_ANKLE.value, w, h) knee_angle calc_angle(hip, knee, ankle)1e-6是防止向量模长为零导致除零。np.clip那行是血泪经验浮点误差会让 cosine 算出 1.0000001arccos 直接返回 nan整个角度序列就废了。角度计算本身不受归一化坐标影响因为缩放是线性的但如果你要算左右对称性涉及距离就必须统一到像素坐标。3.2 深蹲动作的判定规则设计有了角度接下来是规则。以深蹲为例我一般监控四个指标膝角最小值判断下蹲深度、左右膝角差值判断对称性、躯干与竖直方向夹角判断是否弓背、膝盖与脚尖的水平偏移判断膝内扣。def analyze_squat(knee_angle, hip_angle, left_knee_x, right_knee_x, left_ankle_x, right_ankle_x, torso_angle): feedback [] # 深度深蹲底部膝角一般小于 90 度 if knee_angle 100: feedback.append(f下蹲深度不足膝角 {knee_angle:.0f} 度建议蹲到 90 度以下) # 对称性左右膝角差超过 10 度提示 # 膝内扣膝盖 x 坐标比脚踝更靠内 if left_knee_x left_ankle_x 0.02: feedback.append(左膝内扣注意膝盖对准脚尖方向) if right_knee_x right_ankle_x - 0.02: feedback.append(右膝内扣注意膝盖对准脚尖方向) # 躯干前倾超过 45 度提示弓背风险 if torso_angle 45: feedback.append(f躯干前倾 {torso_angle:.0f} 度核心收紧保持背部中立) return feedback阈值不是拍脑袋定的。膝角 90 度是深蹲深度的通用参考线但个体柔韧性差异大实际产品里会先让用户做几次标准动作校准基线。膝内扣的 0.02 是归一化坐标下的经验值对应 1080p 大约 20 像素太小会误报、太大会漏报。躯干角度用肩和髋连线与竖直方向夹角算超过 45 度基本可以判定弓背。3.3 时序平滑与动作分段单帧判定会抖因为关键点本身有噪声。两个处理必须做一是滑动窗口平滑角度二是用状态机把连续帧切成一次次的重复动作。from collections import deque class AngleSmoother: def __init__(self, window5): self.buf deque(maxlenwindow) def update(self, angle): self.buf.append(angle) return sum(self.buf) / len(self.buf) class RepCounter: 用膝角状态机数深蹲次数 def __init__(self, down_thresh100, up_thresh160): self.state up self.count 0 self.down_thresh down_thresh self.up_thresh up_thresh def update(self, knee_angle): if self.state up and knee_angle self.down_thresh: self.state down elif self.state down and knee_angle self.up_thresh: self.state up self.count 1 return self.count平滑窗口 5 帧在 30fps 下约 0.17 秒延迟对健身动作反馈可以接受。状态机用双阈值而不是单阈值是为了防止在临界值附近反复跳变导致重复计数这是计数类功能最常见的翻车点。down_thresh和up_thresh之间留了 60 度迟滞区间动作快慢都能稳定计数。4. 避坑与排查姿态识别在真实健身视频里的五个翻车现场4.1 关键点左右互换导致角度全错现象明明做的是标准深蹲反馈却说左右严重不对称。原因MediaPipe 的左右是以人体自身为参照但摄像头镜像或用户背对镜头时模型的左右判定会和你以为的相反。解决在代码里加一个镜像检测或者干脆不区分左右只报「一侧膝盖内扣」并高亮对应侧。如果产品必须区分左右让用户正面入镜并关闭摄像头镜像。4.2 遮挡导致关键点漂移现象手放胸前做深蹲时手腕关键点突然跳到肩膀位置角度计算全乱。原因关键点被身体遮挡模型靠猜visibility 下降但没到零。解决在角度计算前加 visibility 过滤低于 0.5 的点直接跳过该帧的判定不要用上一帧的值硬填。同时给用户提示「请确保全身入镜、避免遮挡」。4.3 帧率不稳导致时序判定失效现象同一段视频在不同机器上数出的深蹲次数不一样。原因状态机依赖帧序列帧率波动大时角度变化速率不同固定窗口平滑的效果也不同。解决不要用帧数做窗口改用时间戳做窗口。记录每帧的时间戳平滑窗口按毫秒算比如 200ms 内的角度取平均这样跨设备一致。4.4 归一化坐标算距离的尺度陷阱现象膝内扣判定在 720p 视频上正常换 4K 视频后全部误报。原因归一化坐标下 0.02 的偏移在 720p 和 4K 对应的像素数差好几倍但归一化值一样阈值没跟着变。解决涉及距离的判定统一转像素坐标或者用身体尺度归一化比如用肩宽作为基准偏移量表示为肩宽的百分比。4.5 模型复杂度选太高拖垮实时性现象本地测试流畅集成到 App 后卡顿。原因开发时用了model_complexity2在 PC 上没问题移动端算力不够。解决动作识别类应用model_complexity0或 1 足够把省下的算力留给后处理。上线前在目标设备上实测帧率低于 15fps 就要降复杂度或降输入分辨率。5. 进阶技巧用动作模板匹配替代硬阈值规则硬阈值规则写多了会发现问题每个人动作幅度不同统一阈值要么误报要么漏报。更稳的做法是模板匹配。让用户先做 3 次标准动作把每次的关节角度序列存成模板之后实时计算当前序列和模板的 DTW 距离距离超过阈值就提示动作变形。from dtaidistance import dtw import numpy as np def build_template(angle_sequences): angle_sequences: 多次标准动作的角度序列列表 # 取平均长度重采样到统一长度 target_len int(np.mean([len(s) for s in angle_sequences])) resampled [] for s in angle_sequences: idx np.linspace(0, len(s) - 1, target_len) resampled.append(np.interp(idx, np.arange(len(s)), s)) return np.mean(resampled, axis0) def check_form(current_seq, template, threshold15.0): 返回当前动作与模板的偏差 distance dtw.distance( np.array(current_seq, dtypenp.double), np.array(template, dtypenp.double) ) # 按序列长度归一化避免长动作距离天然更大 normalized distance / len(template) return normalized, normalized thresholdDTW 的好处是允许动作快慢不同但形态一致比逐帧对比鲁棒得多。threshold需要按动作类型调深蹲这种大动作 15 左右卧推这种小幅度动作要降到 8 左右。模板匹配的代价是需要用户先做校准适合有引导流程的产品如果只是快速验证硬阈值规则先跑起来也够用。验证方法上我习惯用同一段视频跑两遍第一遍开平滑和状态机第二遍关掉对比角度曲线和计数结果。如果两遍差异大说明平滑参数或阈值需要调。另外准备一段故意做错的视频作为负样本看规则能不能稳定报出问题这比只看正确动作更能暴露阈值边界。我自己踩过最深的坑是早期用单帧判定直接出反馈用户做深蹲时反馈文字疯狂闪烁体验极差。后来加了 5 帧平滑和 300ms 的反馈节流同一个问题 300ms 内只提示一次才像个能用的产品。做这类工具算法精度只是一半另一半是让反馈稳定、可读、不打扰。希望帮到你。本文还有配套的精品资源点击获取