简介:本资源是一套面向健身教练、运动科学学习者及Python计算机视觉初学者的深蹲姿势分析实践代码包,聚焦于利用开源技术实现人体姿态评估与动作质量判别。压缩包共含3个Python源文件,总大小2.43MB,核心涵盖视频流实时捕获、关键帧姿态解析及演示可视化三大功能模块,分别对应运动数据采集、OpenCV+姿态估计模型调用、关节角度计算与结果呈现等关键环节。代码结构清晰,模块职责分明,已封装基础依赖调用与典型参数配置,便于快速部署调试。目前已有222人下载学习,适合希望掌握OpenCV图像处理、人体关键点检测(如OpenPose轻量集成)、运动生物力学指标提取(如髋膝踝角度动态分析)及Matplotlib可视化联动的实践者,可直接复用于深蹲教学反馈系统开发或拓展至其他力量训练动作分析场景。 深蹲姿势分析-python源码.zip,这是我最近整理完的一个小项目。名字起得比较直白,内容也确实不绕弯子:用 Python 加 MediaPipe 加 OpenCV 做了一套深蹲动作识别与计数工具,打开摄像头就能实时检测,也能丢一段视频进去离线分析。核心功能就三块,实时捕捉人体骨骼关键点、计算膝盖和髋关节角度、用动作状态机判断每次深蹲是否标准并自动计数。这个包适合三类人拿去看:一是正在学姿态识别、但不想自己从头训练模型的人,二是想做个健身辅助工具、需要现成思路的开发者,三是课程设计或毕业设计选了动作识别方向、想找一个能落地的参考项目。源码已经整理成 zip 压缩包,解压后按 README 里的步骤把依赖装好,直接就能跑起来。
1. 项目总体设计与思路拆解
1.1 为什么选 MediaPipe,而不是自己训练姿态模型
先说选型。做人体姿态估计,业内绕不开的无非三条路:OpenPose、MediaPipe,或者自己从头训一个关键点检测网络。OpenPose 精度不错但依赖重、模型大,CPU 上跑实时基本别想;自己训模型的成本更夸张,要标几万张人体图片,要有 GPU 环境,还要处理数据增强、损失函数、后处理一堆东西。对于“深蹲姿势分析”这种单场景轻量级需求,属于典型的杀鸡用牛刀。
MediaPipe 的 Pose 模块则是把预训练好的 BlazePose 模型封装成了开箱即用的 Python 接口,pip 一条命令装完,CPU 上就能实时跑 30 帧以上,直接输出人体 33 个关键点的坐标和置信度。打个比方,MediaPipe 就像一个已经认识所有骨骼位置的老师傅,你只需要把画面交给他,再从他嘴里读出每个关节的位置坐标,剩下的判断和计数逻辑完全由自己掌控。
所以在项目里,我把所有“检测人体”的脏活累活都交给 MediaPipe,自己只做三件事:从 landmark 里抽取需要的关节坐标,用三点夹角公式计算膝盖角度,再写一个状态机把角度序列翻译成“下蹲”“站起”“完成一次”。这种分工最大的优势是把复杂度隔离在模块边界之外,核心代码加起来不过 200 行,别人拿到源码包想改也好改。
1.2 项目要解决的三个核心问题
这个项目不是单纯做一个深蹲计数器,它围绕的是健身场景里最常见的三个问题。
第一个问题是动作是否标准。很多人健身时其实完全不知道自己蹲得够不够深、膝关节角度有没有达到 90 度附近。这里我选择用膝盖弯曲角度作为核心判据:站直时膝盖角在 170 度到 180 度,下蹲到标准位置时大约在 90 度左右,整个过程角度从大变小再变大,天然就是一个干净的波形信号。
第二个问题是每一次完整动作怎么界定。深蹲不是“蹲下去”就算一次,而是从站姿到下蹲、再回到站姿才算完成。如果只靠一个固定角度阈值判断,会在临界点来回抖动造成重复计数。所以我设计了一个简单的状态机:用 UP 和 DOWN 两个状态描述当前身体处于“站起”还是“下蹲”,状态切换时才更新计数。
第三个问题是使用场景。健身房里用平板或手机摄像头看,回家想回看自己的动作视频,这两种需求差异很大。源码里同时实现了摄像头实时模式和视频文件离线模式,通过命令行参数切换,不必为两种模式维护两套逻辑。
1.3 技术栈与源码包结构规划
技术栈比较克制,核心依赖只有三个:opencv-python 负责读取视频帧和画面绘制,mediapipe 负责姿态关键点检测,numpy 负责少数数值计算。代码里所有的关节角度计算其实只用了 math 库,安装环境时会少踩很多依赖冲突的坑。
源码压缩包内部结构我特意整理成了适合二次开发的形态,而不是把所有代码塞进一个文件里:
| 文件 | 作用 |
|---|---|
| main.py | 入口,负责视频读取、循环调度、画面绘制 |
| pose_utils.py | 角度计算、角度平滑、landmark 索引映射 |
| config.py | 阈值参数、摄像头索引、显示选项等所有可调配置 |
| requirements.txt | 依赖清单 |
| README.md | 安装步骤、运行命令、参数说明 |
| demo_squat.mp4 | 一段用于测试的深蹲演示视频 |
之所以最后打包成 zip,而不是直接贴代码,是因为这个项目涉及的模块文件、模型配置和测试视频比较多,zip 能保持完整的目录结构,下载时也更方便校验完整性。而且 zip 是跨平台通用的压缩格式,Windows、macOS、Linux 都能直接解压,拿到包的人不需要额外装 7-Zip 或 WinRAR。
2. 核心细节解析与实操要点
2.1 骨骼关键点索引与膝盖角度计算
MediaPipe Pose 输出的 landmark 是 33 个点的数组,每个点包含 x、y、z 和 visibility。对深蹲来说,关心的主要是髋关节、膝关节和踝关节:左髋索引 23、左膝 25、左踝 27;右髋 24、右膝 26、右踝 28。需要提醒的是,索引如果不查文档硬记,非常容易搞混,我前期就因为在代码里把 24 和 23 用反了,导致一侧膝盖角度算出来始终是钝角,排查了半天。
角度计算我用的是经典的三点夹角公式。以膝盖为顶点,把髋关节和踝关节看作另外两个点,用反正切函数分别求出两条向量的方向角,再取差值绝对值:
import math def calc_angle(a, b, c): # a 和 c 分别是关节两端的点,b 是顶点 ba = math.atan2(a.y - b.y, a.x - b.x) bc = math.atan2(c.y - b.y, c.x - b.x) angle = abs(math.degrees(ba - bc)) if angle > 180: angle = 360 - angle return angle有同学可能会问,为什么不直接算大腿和竖直方向的夹角?因为三点夹角是纯几何定义,跟摄像头摆放角度无关,不受人体出现在画面左右位置的影响,鲁棒性更好。不过也要承认,这种方法依赖单目摄像头,当人体深度方向变化过大、脚和髋不在同一平面时误差会变大,所以在实测时我要求人尽量侧对摄像头,这样膝盖弯曲的弧度在画面里最明显。
2.2 角度平滑与置信度过滤
MediaPipe 返回的 landmark 坐标并非每帧都稳定,尤其画面有轻微抖动或肢体遮挡时,角度值会出现毛刺。如果不做平滑,画面上显示的角度会在真实值附近反复横跳,状态机也容易误判。
我做了两层防抖。第一层是 visibility 过滤,MediaPipe 会对每个关键点给出一个 0 到 1 之间的置信度,低于 0.5 的直接认定当前帧无效,保留上一帧的角度值。第二层是滑动平均,保留最近 5 帧的角度做平均,相当于把高频抖动滤掉一层。实测下来,这个组合对普通手机拍摄的视频效果非常明显,角度曲线从锯齿状变成了平滑的弧线。
调参的时候注意一个权衡:平滑窗口越大曲线越干净,但动作的真实转折点也会被延迟,导致计数时机偏晚。5 帧窗口在 30 帧每秒的视频里大约是 0.17 秒的延迟,体感基本无感,我会建议新手先按这个配置来。
2.3 状态机计数与滞回区间
角度平滑解决的是“看着不乱跳”的问题,但计数逻辑里还有一个更隐蔽的坑:如果在角度阈值边缘抖动,会出现蹲到一半上下反复横跳、计数疯狂增加的情况。比如把“下蹲”阈值设在 100 度,深蹲到 99 度时触发了 DOWN,但下一秒身体微抬到 101 度又回到 UP,然后再蹲又触发一次,实际只做了一次动作却可能被计两三次。
解决方法是给阈值加一个滞回区间,让进入和离开某个状态使用不同的阈值。这个思路在很多控制系统里都有,比如空调压缩机启动温度和停止温度不一样,就是为了避免频繁启停。具体到深蹲计数:
UP = 0 DOWN = 1 def update_state_machine(angle, state, count, down_threshold=100, up_threshold=135): if state == UP and angle < down_threshold: state = DOWN elif state == DOWN and angle > up_threshold: state = UP count += 1 return state, count这里下蹲到 100 度以下才进入 DOWN 状态,站起到 135 度以上才回到 UP 同时计数加一。100 到 135 之间的区间就是滞回带,角度在这个范围内抖动不会触发状态翻转。这个设计是整套计数逻辑里最值得注意的部分,改代码时千万别把两个阈值改成同一个值,否则立刻回到重复计数的老问题。
3. 实操过程与核心环节实现
3.1 环境准备:Python 版本与依赖安装
项目基于 Python 3.9 开发,用 3.8 到 3.11 实测都能顺利跑通。Python 3.12 存在一部分老版本 numpy 和 opencv wheel 不兼容的问题,建议先建虚拟环境再装依赖,避免污染系统环境。
python -m venv squat_env source squat_env/bin/activate pip install -r requirements.txtrequirements.txt 的内容大概是这样的:
opencv-python>=4.8.0 mediapipe>=0.10.0 numpy>=1.24.0国内网络环境下,mediapipe 这个包体积不小,直接 pip 安装容易超时。可以把 pip 源切换到清华镜像,速度会快很多。如果你是第一次用 Python,装完后在命令行敲python -c "import mediapipe, cv2",没有报错就说明环境正常。IDE 我推荐 VSCode 加 Python 插件,调试时可以看到每一帧的 landmark 坐标,对理解代码逻辑很有帮助。
3.2 核心代码拆解:主循环、角度抽取与画面绘制
主程序 main.py 是一个标准的生产者-消费者循环:读一帧画面、交给 MediaPipe 检测、抽取坐标、计算角度、更新状态机、把结果画到画面上。中间任何一步返回异常都会退出循环并释放摄像头资源。
import cv2 import mediapipe as mp mp_pose = mp.solutions.pose pose = mp_pose.Pose( static_image_mode=False, model_complexity=1, min_detection_confidence=0.5, min_tracking_confidence=0.5, ) cap = cv2.VideoCapture(0) # 0 是默认摄像头,换视频时改成视频路径 state = UP count = 0 angle_history = [] while cap.isOpened(): ret, frame = cap.read() if not ret: break rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = pose.process(rgb_frame) if results.pose_landmarks: landmarks = results.pose_landmarks.landmark left_angle = get_knee_angle(landmarks, side='left') right_angle = get_knee_angle(landmarks, side='right') angle = (left_angle + right_angle) / 2 angle = smooth_angle(angle, angle_history) state, count = update_state_machine(angle, state, count) cv2.putText(frame, f"Knee Angle: {angle:.1f}", (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.putText(frame, f"Count: {count}", (20, 80), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.putText(frame, f"State: {'DOWN' if state == DOWN else 'UP'}", (20, 120), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow("Squat Analysis", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()这段代码是精简后的主循环,实际源码包里还多了取两侧膝盖角度均值的逻辑。之所以取均值而不是只看一侧,是因为人下蹲时身体可能轻微左右不对称,取均值对判断总体动作深度更公平。当然代价是如果某一侧关键点被遮挡,均值就会偏,所以代码里保留了 visibility 过滤,一旦某侧置信度不够就自动只依赖另一侧。
3.3 源码包的导入、解压与运行命令
拿到 zip 压缩包后,先别急着双击,养成先确认文件完整性的习惯。Linux 和 macOS 上可以用file deep_squat_analysis.zip查看真实文件类型,正常会输出Zip archive data;Windows 用户直接看文件后缀和大小,如果下载后只有几 KB,基本是下载中断了。
解压命令方面,Linux 和 macOS:
unzip 深蹲姿势分析-python源码.zip -d squat_analysisWindows 可以用资源管理器解压,也可以在 PowerShell 里用Expand-Archive,这个命令比第三方工具稳定,且遇到中文文件名乱码的概率更低。解压完成后,进入目录确认有 main.py 和 requirements.txt 两个文件,再安装依赖。
运行方式分成两种:
# 摄像头实时模式 python main.py --camera 0 # 视频离线模式 python main.py --video demo_squat.mp4--camera 0里的 0 是摄像头索引,笔记本自带摄像头一般是 0,外接 USB 摄像头可能需要改成 1,这块我见过太多人卡住,后面常见问题里会细说。离线分析时视频尽量选侧面视角拍的,人物完整出现在画面里,效果最好。
3.4 判定标准如何进一步细化
膝盖角度只是判断深蹲深度的一个维度。很多人下蹲时膝盖角度到了 90 度,但上身过度前倾,或者膝盖明显超过脚尖,这些动作仍然有受伤风险。所以在源码包里,我在 config.py 里预留了髋关节角度的计算位置,思路跟膝盖角度一样,用肩、髋、膝三点计算躯干与大腿的夹角,再判断躯干是否低于水平线。
更进一步可以统计膝盖在水平方向相对脚尖的位置:取膝关节和踝关节的屏幕坐标差值,如果膝盖 x 坐标超出脚尖 x 坐标一定像素,就认为膝盖前探过度。这些规则不建议一次性全加,先跑通主流程,再逐个扩展,否则变量太多反而排查困难。
4. 常见问题与排查技巧实录
4.1 拿到 zip 压缩包后解压失败或报错
这个项目是 zip 分发,自然要聊几句 zip 相关的问题。最常见的是下载完提示file is not a zip file,原因几乎都是文件没下载完整,或者网站把它存成了 .html 后缀。处理办法很简单:Linux 用file命令看真实格式,Windows 用 7-Zip 打开确认,如果发现是网页重定向产生的 html 文件,重新找下载链接,别反复尝试重命名。
另一个高频报错是invalid zip archive: could not find eocd。EOCD 是 zip 格式最末尾的结束标记,找不到它说明文件被截断。这种情况可以用内置修复命令恢复:
zip -FF damaged.zip --out repaired.zip这个命令会扫描 zip 中残留的本地文件头,尽可能重建目录结构。如果核心源码文件都被修复出来了,就能正常解压。需要说明的是,如果 zip 包本身带密码且密码丢失,修复和恢复都比较麻烦。这类密码恢复工具只建议用来找回自己加密的备份文件,个人项目源码通常没必要加密码,加了反而给使用者制造障碍,我打包时就没有设置密码。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| file is not a zip file | 下载不完整或文件后缀错误 | 重新下载,用 file/7-Zip 检查真实格式 |
| could not find eocd | 压缩包被截断 | 用 zip -FF 尝试修复 |
| 解压后中文文件名乱码 | zip 编码不一致 | 用 7-Zip 或 Expand-Archive 解压 |
| 有密码但忘记密码 | 加密时使用的是自定义密码 | 密码恢复工具只用于找回自己的备份 |
4.2 Python 环境与依赖装不上的问题
pip install mediapipe慢到想砸电脑是绝大多数人遇到的第一个坎。解决办法就是把 pip 指向清华镜像:
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple装完之后如果 import mediapipe 直接报 DLL load failed,大多数情况是缺少 Visual C++ 运行库。Windows 用户装一下 Microsoft Visual C++ Redistributable 2015-2022 x64,这个问题基本就消失了。macOS 不太会遇到这个,但个别版本需要xcode-select --install装命令行工具。
另一个容易忽略的是 numpy 版本冲突。因为 mediapipe 对 numpy 1.x 的老版本有依赖,如果你之前装过 numpy 2.0,可能会出现类型转换报错。稳妥的做法是顺序执行:先建干净虚拟环境,再按 requirements.txt 安装,让 pip 自动解析依赖树,而不是手动逐个 pip install。
4.3 识别效果差、计数不准确怎么调
检测效果不理想,优先排查的是拍摄角度和遮挡。MediaPipe 对侧脸、背面、身体被遮挡的情况非常敏感,而深蹲又是前后方向动作,所以最好的拍摄角度是正侧面,让髋、膝、踝三个点始终可见。如果画面中检测不到人,先确认人物和摄像头距离是否太远,建议占比超过画面高度的三分之一。
角度值持续乱跳时,优先上调滑动平均窗口,把config.py里的SMOOTH_WINDOW从 5 调到 8 或 10。代价是动作响应会稍微迟钝,但计数稳定性提高明显。计数偏多时优先检查滞回区间,把DOWN_THRESHOLD调低、UP_THRESHOLD调高,让两个状态切换吃得更深。还有一个小细节是 MediaPipe 默认只追踪画面中的一个人,如果画面里出现了别人或者镜子里的自己,检测会不稳定,尽量保持画面干净。
4.4 摄像头打开失败或画面卡顿
cv2.VideoCapture(0)打不开摄像头时,最常见原因是摄像头索引不对。笔记本自带摄像头是 0,外接 USB 摄像头经常会变成 1 或 2。改索引比想象中更玄学,有时候插拔一次 USB 口,索引就变了,所以我把摄像头索引也放进了 config.py,方便大家不用改代码直接调。
画面卡顿一般是分辨率太高。MediaPipe 在 1080p 下 CPU 占用会拉到接近满载,帧率自然上不去。我实测把分辨率固定到 640x480 后,从十几帧直接跳到稳定 30 帧,完全够用。可以在打开摄像头后加这两行:
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)5. 后续可以怎么玩:从计数到动作打分
5.1 把判定规则升级成标准分制
目前的版本在画面上只显示膝盖角度、动作状态和计数,这满足计数需求,但对“姿势是否标准”的判断比较粗糙。我设想过一个更完整的方案:把膝盖角、髋关节角、膝盖前探距离三个维度分别量化打分。
比如膝盖角度在 80 到 100 度之间给满分,低于 70 或高于 120 扣分;髋关节角度反映躯干前倾程度,前倾小于 30 度算合格;膝盖水平位移不超过脚尖算合格。三个维度的分数汇总成 0 到 100 的标准分,低于 60 就在画面上用红色显示“注意姿势”。这个升级不需要换模型,只是在现有 landmark 基础上多算几个角度,代码量也不大,适合作为二次开发的第一个练手方向。
5.2 加一个语音提示或音效
如果真要在健身时用,眼睛盯着屏幕是不现实的。一套完整的深蹲辅助工具应该能“听见”反馈:蹲到位时响一声,站起时响一声,或者直接播放“下蹲”“起立”的语音指令。实现上可以用 pygame 播放音效文件,也可以调用系统自带的 tts 引擎合成语音。这个功能我在分支版本里做过一版,接入点就在更新状态机之后,状态切换时触发播放即可,对主流程侵入很小。
5.3 记录运动日志,做长期数据追踪
另一个顺手就能扩展的点是输出运动日志。目前可以每隔一帧把时间戳、膝盖角度、动作状态写入 CSV 文件,练完一次就能看到每一次深蹲的深度曲线。如果你有兴趣,还可以把单次深蹲的曲线长度、最低点角度、动作持续时间存成一条记录,一段时间后就能直观看到自己的稳定性和耐力变化。这是从“计数工具”走向“运动分析工具”比较自然的一步。
我在实际使用中发现,把角度序列画成曲线比看实时数字直观得多,深蹲效率高不高一眼就能看出来。源码包里我没有把曲线绘制加进去,但已经在 README 里留了扩展思路,有兴趣的话可以自己加上。
真要总结这个项目给我的感受,那就是姿态估计的入门门槛已经低到不可思议,关键不在模型而在于你如何把模型的输出转化成有用的业务逻辑。代码里最花时间的地方不是调 API,而是想明白怎么用状态机处理动作时序、怎么用滞回区间压制抖动。这些经验放在任何一个传感器数据处理场景里都通用。整个项目我在录好的视频上反复测试了一个晚上,确认计数稳定后才放到摄像头实时环境里跑,实际测试下来稳得很。
本文还有配套的精品资源,点击获取