OpenCV与MediaPipe实现人体姿态识别与深蹲计数实战
我最近在做一个基于OpenCV的人体姿态识别项目核心就是捕捉视频里的人体关键点实时判断深蹲动作、自动计数并且像私人教练一样给出姿态评估和康复训练矫正建议。这个项目用到的技术栈并不复杂OpenCV配合MediaPipe就能稳定跑起来关键是几个数学建模和状态机的思路。这篇文章就把整个项目的完整方案整理出来从技术选型、核心代码到部署实战再到我踩过的那些坑一次性说清楚给你一份可以直接照着抄的作业。如果家里有老人需要康复训练或者你自己健身时总担心蹲得不标准甚至你是做AI健身应用的开发者都适合往下看。这个项目能够解决的就一件事让计算机看懂你蹲得对不对、蹲了几个、该怎么调整。1. 项目概述与效果预览1.1 这个项目能做什么先说直观效果。打开摄像头人站在画面里屏幕上立刻会出现全身的骨骼点连线。当你开始下蹲系统会自动在画面里标出膝盖角度、髋部角度、躯干倾斜角度当动作达到有效深蹲标准时计数器加一。同时右上角会给出实时评分和矫正提示比如膝盖内扣注意外旋或蹲得太浅大腿需要更低。整个流程完全本地运行不需要云服务器摄像头数据不出本机。我测试过在老旧的笔记本电脑上也能跑到25帧左右基本实时。如果你是做产品原型的这个方案甚至可以直接嵌到小程序或者边缘设备上。这个项目本质上是一个标准的人体关键点识别应用底层靠的是MediaPipe的Pose模型把人体拆成33个关键点然后上层用OpenCV做画框、绘制骨架、叠加UI再用简单的几何计算完成姿态评估和深蹲计数。整个项目分四个模块视频采集与人体检测、关键点坐标提取与骨骼绘制、姿态角度计算与深蹲状态机、错误姿态识别与反馈提示。1.2 系统整体工作流程整个系统的工作流程可以这么概括从摄像头或视频文件读取一帧画面。把BGR图像转换成RGB送入MediaPipe Pose模型检测出人体关键点。从检测结果中提取需要的关键点坐标肩、髋、膝、踝等。通过坐标计算关节角度判断当前人体姿态是站立、下蹲还是蹲起。当姿态序列满足站立-下蹲-蹲起的完整循环时深蹲计数增加。在每一帧中检测常见错误姿态如膝盖内扣、重心前移、蹲幅过浅生成文字和语音矫正建议。用OpenCV把关键点、连线、角度数值、计数器和反馈提示绘制到画面上实时显示。这个流程看起来简单但其中每一步都有很多细节。比如角度计算要考虑屏幕坐标系与人体关节的关系深蹲计数要处理动作过冲和抖动姿态评估要考虑不同身高比例的人。接下来我逐个拆解。2. 技术选型为什么是OpenCV加MediaPipe2.1 人体关键点检测方案对比在做人体姿态识别时市面上可选的方案不少。OpenPose是学术界很经典的方法检测精度高但模型动辄几百MB在普通CPU上跑起来非常吃力更别说实时视频。PoseNet是TensorFlow推出的模型支持浏览器但姿态还原度一般关键点定位偶尔漂移。MediaPipe Pose是Google出的轻量级方案单帧推理在CPU上能做到几十毫秒还能同时检测33个关键点关键点顺序稳定正好够用。我最终选MediaPipe不是因为它的精度碾压其他方案而是因为它把实时性和易用性做到了很好的平衡。对于一个教练类应用用户需要的是即时反馈停下来等你算两秒是不能接受的。MediaPipe在普通笔记本CPU上就能跑到30帧如果在桌面GPU上甚至可以更快加上提供了Python接口和OpenCV搭配起来几乎没有摩擦。另外MediaPipe Pose输出的是标准化的人体关键点坐标范围是0到1相对于图像宽高归一化处理过了。这意味着不同分辨率、不同摄像头距离下坐标天然具备可比性做姿态判断时就不需要费劲去换算像素距离直接基于比例关系计算角度即可非常省心。2.2 OpenCV在其中的角色OpenCV在这套方案里的角色更像是骨架和画板负责整个项目的图像处理、视频信号输入输出和可视化反馈。具体来说有三个用途第一是视频采集。cv2.VideoCapture用来读取摄像头或视频文件OpenCV统一处理了不同摄像头驱动和视频编码格式的差异。你只需要告诉它设备号或文件路径就能逐帧拿到图像数据。第二是图像格式转换与预处理。OpenCV读取的图像默认是BGR格式而MediaPipe吃的是RGB格式所以要在喂给模型之前用cv2.cvtColor转换。另外如果需要做缩放、裁剪、翻转等操作OpenCV的图像缩放函数性能也非常好。第三是结果可视化。当你拿到MediaPipe返回的关键点坐标后需要用cv2.line绘制骨骼骨架用cv2.circle绘制关键点用cv2.putText把角度、计数、评分叠加到画面上。甚至可以用cv2.addWeighted来做背景遮罩提升UI层次感。有些人可能觉得既然MediaPipe能直接输出骨骼点那OpenCV是不是可有可无其实不然。没有OpenCV的话视频IO和渲染就会变成很大一块工作量而且MediaPipe不是万能的它对图像的预处理、后处理都依赖外部工具链。OpenCV是这一整套系统的地基两者配合才能形成一个完整的实时交互应用。3. 核心实现关键点提取与深蹲计数3.1 关键点检测与坐标提取先安装依赖这里假设Python环境已经准备好pip install opencv-python mediapipe numpy核心代码首先初始化MediaPipe Pose模型import cv2 import mediapipe as mp import numpy as np mp_pose mp.solutions.pose pose mp_pose.Pose( static_image_modeFalse, model_complexity1, smooth_landmarksTrue, enable_segmentationFalse, min_detection_confidence0.5, min_tracking_confidence0.5 ) cap cv2.VideoCapture(0) while cap.isOpened(): success, frame cap.read() if not success: break frame_rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results pose.process(frame_rgb) if results.pose_landmarks: h, w, _ frame.shape landmarks results.pose_landmarks.landmark # 提取关键点的像素坐标 def get_point(idx): lm landmarks[idx] return int(lm.x * w), int(lm.y * h) # 以左侧和右侧为例 left_shoulder get_point(mp_pose.PoseLandmark.LEFT_SHOULDER) left_hip get_point(mp_pose.PoseLandmark.LEFT_HIP) left_knee get_point(mp_pose.PoseLandmark.LEFT_KNEE) left_ankle get_point(mp_pose.PoseLandmark.LEFT_ANKLE)MediaPipe的Pose模型检测33个关键点我们只需要其中12个左右就能完成大部分姿态判断左右肩、左右肘、左右腕、左右髋、左右膝、左右踝。对于深蹲最核心的是髋部、膝盖、踝关节三个点的位置关系加上肩部作为躯干参考。需要注意一个实际细节很多人在家练习时是侧对摄像头的这个时候左右两侧的关键点都会出现在画面里但人体是侧着的一侧关键点的置信度会更高。我在实际测试时发现可以先检测左右髋和左右膝的可见性选择置信度更高的那一边做角度判断。MediaPipe的每个landmark带有visibility字段取值0到1大于0.5通常认为可见。3.2 深蹲姿态的数学建模深蹲动作的本质是下肢关节的弯曲与伸展。我们把人体简化成一个多段连杆模型髋关节连接躯干和腿部膝关节连接大腿和小腿踝关节连接小腿和脚。标准深蹲中膝盖弯曲角度大约在90度到110度之间髋部角度也有明显变化躯干会自然前倾。判断深蹲状态最常用的角度是膝关节角度就是大腿与小腿之间的夹角。这个角度通过三个点来计算髋部、膝盖、踝部。几何上以膝盖为顶点连接髋部和踝部的两条线段之间的夹角就是膝关节弯曲角。角度计算公式使用余弦定理def calculate_angle(a, b, c): a: 顶点之前的点如髋部 b: 顶点如膝盖 c: 顶点之后的点如踝部 返回a-b-c三点构成的夹角角度0-180度 a np.array(a) b np.array(b) c np.array(c) ba a - b bc c - b cosine_angle np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc)) cosine_angle np.clip(cosine_angle, -1.0, 1.0) angle np.arccos(cosine_angle) return np.degrees(angle)注意这个公式在屏幕坐标系下也适用因为平面中点与点之间的夹角不受坐标平移影响也不受图像缩放影响归一化坐标与像素坐标的趋势一致。但有一个前提人体不能完全垂直于摄像头平面否则关键点的连线会挤压变形。实际部署时建议摄像头放在人体侧面约45度角方向识别角度最准确。深蹲过程中的角度变化规律是站立时膝关节接近180度下蹲过程中膝关节角度逐渐减小最低点可能到70度到90度蹲起过程中角度逐渐恢复。判断「有效深蹲」通常要求膝关节角度达到某个阈值比如小于100度同时臀部位置明显降低。如果用纯角度做判断会发现一个问题不同身高的人膝盖弯曲相同角度时下蹲幅度完全不同。高个子往往膝盖弯曲到100度就已经是大腿平行地面矮个子可能需要到90度以下。因此实际项目中我一直使用角度加关键点相对位置的组合判断。比如同时判断髋部中心点的y坐标是否低于膝盖点以及臀部是否下移了足够距离这样对不同体型的人更稳定。3.3 计数逻辑与状态机设计深蹲计数不是简单的角度阈值碰触就加一。如果肢体抖动、位置变化、临时蹲下捡东西等场景都会导致误判所以必须引入状态机机制。我把人的下蹲动作拆成四个状态站立STAND、下蹲中LOWERING、蹲底BOTTOM、起身RISING。只有完整经历了这些状态且顺序正确才计为一次有效深蹲。状态迁移条件基于膝关节角度和躯干倾斜角度同时加入连续帧数的防抖确认class SquatStateMachine: def __init__(self): self.state STAND self.rep_count 0 self.frame_count 0 def update(self, knee_angle, hip_angle, torso_angle): # 状态机转换逻辑 if self.state STAND: # 站立时膝角大于160度髋角大于150度 if knee_angle 120 and hip_angle 130: self.state LOWERING self.frame_count 0 elif self.state LOWERING: self.frame_count 1 if knee_angle 95 and hip_angle 100: if self.frame_count 3: self.state BOTTOM self.frame_count 0 elif knee_angle 160: # 没蹲到位就回到站立 self.state STAND elif self.state BOTTOM: if knee_angle 110: self.state RISING self.frame_count 0 elif self.state RISING: self.frame_count 1 if knee_angle 160 and hip_angle 150: if self.frame_count 3: self.rep_count 1 self.state STAND self.frame_count 0 return self.rep_count这里的关键是状态机中的连续帧数确认。在每一帧中状态不会立即切换只有连续若干帧满足条件后才切换这样能滤掉因为摄像头噪声引起的单帧抖动。这个经验我是在实际测试中反复调出来的设得太小比如1帧会误判设得太大比如10帧会漏掉快速深蹲。我最终在30fps的视频流上用了3到5帧既能滤掉噪声又不会漏动作。计数逻辑中另一个容易忽略的是误拆问题。人在深蹲底部停留时间很长此时膝盖角度一直保持很小但状态机不会重复计数因为它必须等站起之后才能进入下一个周期。这跟硬件设计里的边沿触发是一个道理只认动作变化沿不认电平本身。3.4 深蹲质量评分只计数不能叫姿态评估还得给动作打分。我定义了一个0到100分的实时评分机制分四个维度扣分深度不足膝角最低值大于10度需要达到标准下限才给满分。膝盖内扣从正面视角看膝盖点的x坐标与踝关节x坐标的偏差超过一定阈值。重心偏移从侧面视角看肩部相对髋部的水平偏移量。躯干过度前倾躯干与竖直方向的夹角超过25度时扣分。这个评分实时更新并且把每个维度的扣分原因显示在屏幕上。比如某次深蹲扣了15分屏幕上会显示深度不足-5, 膝盖内扣-10用户一眼就能看到问题在哪。这个设计最大的好处是用户不用去理解复杂的角度数字只看到分数和关键词就知道自己问题在哪了。4. 姿态评估与康复矫正4.1 常见错误姿态识别深蹲是一项全身运动错误姿态不仅影响训练效果更有可能导致膝盖和腰部受伤。在康复训练、老年人群这个场景里错误姿态的识别尤其重要。我通过角度和关键点相对位置能识别下面这几类典型问题第一类是膝盖内扣也是人群里最常见的问题。正常情况下从正面看膝盖应向脚尖方向运动但很多人因为髋外展肌群力量不足下蹲时膝盖会向内塌陷。判断方法计算左右膝盖x坐标的中间值和左右踝关节x坐标的中间值作差如果膝盖中心点明显向内侧偏移比如偏移量大于踝间距的15%就判定为膝盖内扣。第二类是过度前倾或者说弯腰深蹲。有些人蹲的时候腰部弯曲过度导致髋关节代偿重心前移压力都跑到腰椎上。判断方法计算肩部与髋部连线与垂直线的夹角如果大于30度不同人群可能有差异就认为躯干前倾过大需要提醒收紧核心、挺直背部。第三类是蹲幅过浅。有些人蹲到一半就急着站起来膝关节角度还没降到110度以下。这通常是髋关节灵活度不足或对动作标准理解有偏差。这个判断相对简单直接用膝盖角度阈值加上大腿与地面是否平行的检测。第四类是重心偏前典型的动作是脚跟离地重心压在脚尖上。判断方法对比踝关节点和身体质心投影的水平位置。人体重心大致位于骨盆中心附近屏幕坐标里可以近似用髋部中点作为重心投影参考。如果髋部中点与踝关节的水平距离超过一定阈值说明重心偏移有摔倒风险。4.2 康复矫正建议的生成检测到错误姿态之后不能只报个数字还要给出可以执行的矫正动作。我把常犯错误和矫正建议做成一个规则映射表产生对应文字提示膝盖内扣 - 双腿打开膝盖与脚尖方向一致想象用臀部的力量向外撑开躯干前倾 - 收紧核心挺直背部胸口向前蹲幅过浅 - 向下蹲到大腿与地面平行可以扶住固定物慢速下蹲重心前移 - 重心落在全脚掌脚后跟用力踩实地面速度太快 - 放慢下蹲速度控制在2-3秒在代码里我维护了一个错误列表每一帧把所有检测到的错误推入列表并映射到建议feedback [] if knee_inward: feedback.append(膝盖内扣膝盖必须对准脚尖方向) if torso_lean 30: feedback.append(躯干前倾挺胸、收腹、保持背部中立) if knee_angle 110: feedback.append(蹲幅太浅继续下蹲直到大腿平行地面) if weight_shift 0.25: feedback.append(重心偏前脚跟踩实地面) if feedback: cv2.putText(frame, feedback[0], (30, 120), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)康复训练场景里我特别加入了语音提示。通过pyttsx3库把建议文本朗读出来这样用户就不需要一直盯着屏幕看。有些用户翘首练腿时眼睛看屏幕可能会不舒服语音提示能极大地提升体验。当然如果要集成到手机端可以用系统自带的TTS接口效果更好。这里必须强调一点这个系统只能作为动作辅助工具不能替代专业康复师或医生的诊断。如果你或者家人的膝盖、腰部有伤病请先咨询专业人士在指导下进行训练。系统可以帮你监控动作质量但判断是否适合训练是另一回事。4.3 可视化反馈与交互可视化是姿态评估里最直观的部分我参考了一些商用AI私教App的设计实现了三个层面的界面元素第一层是骨架覆盖层。用cv2.line把所有检测到的关键点按骨骼连接关系画出来连接关系是从MediaPipe的PoseLandmark里定义的比如左肩-左肘-左腕、左髋-左膝-左踝。我根据远近高低把线的颜色做了区分髋-膝-踝这条线用暖色肩-肘-腕这条线用冷色这样用户在第一眼就能关注到下肢动作。第二层是数据看板层。在画面左侧固定位置画一个半透明矩形区域里面显示实时膝关节角度、髋关节角度、当前状态、计数结果。为了让数字更容易读我把角度大于标准范围的用绿色标注小于标准的用红色标注。这样用户在动作过程中瞥一眼就知道当前动作质量。第三层是反馈提示层。当检测到错误姿态时在画面中央或者靠近对应关节的位置绘制一条醒目的文字提示持续3秒同时配合一声滴的提示音。我甚至在膝盖内扣时在膝盖位置画了一个红色圈标记用户低头一看就知道自己膝盖歪到哪了。交互逻辑方面按键盘上的R键可以清零计数P键暂停/继续Q键退出。这样在训练时不用动鼠标特别方便。5. 代码实现与部署实践5.1 环境准备与依赖安装这个项目在Windows、macOS、Linux上都能跑。我自己的主力环境是Windows 10Python 3.9OpenCV版本4.5以上MediaPipe版本0.10以上。建议新建一个虚拟环境避免依赖冲突python -m venv squat_env # Windows squat_env\Scripts\activate # macOS / Linux source squat_env/bin/activate pip install opencv-python mediapipe numpy pyttsx3安装pyttsx3前建议先装好系统TTSWindows上一般自带不用额外处理。如果你只需要可视化文字提示不装语音库也没关系。5.2 核心代码模块解析完整代码可以分成几个模块工具函数、状态机类、姿态评估类、主循环。这里我给出一个可以运行的缩略版本核心逻辑都在。import cv2 import mediapipe as mp import numpy as np mp_pose mp.solutions.pose mp_draw mp.solutions.drawing_utils pose mp_pose.Pose(model_complexity1, min_detection_confidence0.5) def cal_angle(a, b, c): a, b, c np.array(a), np.array(b), np.array(c) ba, bc a - b, c - b cos_angle np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc) 1e-6) cos_angle np.clip(cos_angle, -1.0, 1.0) return np.degrees(np.arccos(cos_angle)) def get_point(landmarks, idx, w, h): lm landmarks[idx] return int(lm.x * w), int(lm.y * h) class SquatCounter: def __init__(self): self.state STAND self.count 0 self.wait 0 def update(self, knee_angle, hip_angle): if self.state STAND: if knee_angle 120 and hip_angle 130: self.state LOWERING self.wait 0 elif self.state LOWERING: self.wait 1 if knee_angle 160 and hip_angle 150: self.state STAND elif knee_angle 95 and self.wait 3: self.state BOTTOM elif self.state BOTTOM: if knee_angle 110: self.state RISING elif self.state RISING: self.wait 1 if knee_angle 160 and hip_angle 150 and self.wait 3: self.count 1 self.state STAND return self.count counter SquatCounter() cap cv2.VideoCapture(0) while cap.isOpened(): ok, frame cap.read() if not ok: break frame cv2.flip(frame, 1) # 镜像显示更自然 rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results pose.process(rgb) if results.pose_landmarks: h, w, _ frame.shape lm results.pose_landmarks.landmark # 取置信度高的侧作为检测侧 hip_l lm[mp_pose.PoseLandmark.LEFT_HIP] hip_r lm[mp_pose.PoseLandmark.RIGHT_HIP] side L if hip_l.visibility hip_r.visibility else R if side L: hip get_point(lm, mp_pose.PoseLandmark.LEFT_HIP, w, h) knee get_point(lm, mp_pose.PoseLandmark.LEFT_KNEE, w, h) ankle get_point(lm, mp_pose.PoseLandmark.LEFT_ANKLE, w, h) shoulder get_point(lm, mp_pose.PoseLandmark.LEFT_SHOULDER, w, h) else: hip get_point(lm, mp_pose.PoseLandmark.RIGHT_HIP, w, h) knee get_point(lm, mp_pose.PoseLandmark.RIGHT_KNEE, w, h) ankle get_point(lm, mp_pose.PoseLandmark.RIGHT_ANKLE, w, h) shoulder get_point(lm, mp_pose.PoseLandmark.RIGHT_SHOULDER, w, h) knee_angle cal_angle(hip, knee, ankle) hip_angle cal_angle(shoulder, hip, knee) count counter.update(knee_angle, hip_angle) # 绘制骨架 mp_draw.draw_landmarks(frame, results.pose_landmarks, mp_pose.POSE_CONNECTIONS) cv2.putText(frame, fKnee: {knee_angle:.1f}, (30, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.putText(frame, fCount: {count}, (30, 70), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) cv2.imshow(AI Squat Trainer, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码已经可以完成基本的深蹲计数和角度显示。在实际项目里我还会在counter.update前后加入错误检测函数把结果绘制到界面上。代码的关键点是由于MediaPipe坐标是归一化的角度计算时不区分图像分辨率所以即使在不同摄像头或分辨率下逻辑都通用。5.3 实时运行与性能优化性能优化是我在实际部署时花时间比较多的地方。首先MediaPipe模型本身延续了轻量级特性在CPU上推理一帧大约30到50毫秒但如果你的摄像头分辨率是1080p全分辨率推理就会明显变慢。我的做法是先把图像缩放到640x480这个分辨率下既能保证位置精度又能让CPU推理稳定在30fps左右然后再把关键点坐标换算回原始分辨率用于绘制。第二主循环里不要每一帧都检测错误姿态可以做分频处理。比如计数器状态机必须要每帧更新但评分和错误提示可以每5帧刷新一次。因为错误提示不需要像素级精确用户也不会注意到4帧的延迟。这样做能够节省一部分CPU用来提高帧率稳定性。第三如果摄像头画面出现反光、暗光MediaPipe的关键点置信度会下降。我加了一个简单的预处理把灰度图做直方图均衡化再转回BGR喂给模型。实测在背光环境下的检测成功率提高了不少但对肤色和背景的要求仍然存在。如果你要把项目部署到树莓派这类更弱的设备上建议把输入分辨率降到320x240同时把模型复杂度设为0轻量版仍可获得约15帧的实时体验。部署方面这个方案完全没有GPU依赖所以可以很轻松地打包成可执行程序。如果你有多台设备需要部署可以考虑用Docker封装Python环境挂载USB摄像头作为视频输入然后通过网页或共享屏幕做远程输出。如果你想把模型集成到移动端MediaPipe本身有Android/iOS的SDKOpenCV在移动端也很成熟但UI层要另写代码逻辑完全可以直接复用。6. 常见问题与踩坑实录6.1 摄像头帧率不稳定或延迟高这是项目里最容易遇到的问题。我在初版代码里直接用摄像头默认分辨率有时是1280x720有时是1920x1080结果在普通笔记本上推理一帧就要80毫秒再加上UI绘制画面卡得厉害。排查方法把cv2.CAP_PROP_FRAME_WIDTH和cv2.CAP_PROP_FRAME_HEIGHT强制设置为640x480效果好很多。如果还是卡可以考虑用线程池来处理视频流。把读取线程和推理线程分开摄像头读帧是不阻塞推理的用队列传递图像帧。不过要注意队列长度否则内存会涨。我一般设置队列上限为2旧帧直接丢弃保证实时性。另一个技巧是cv2.waitKey(1)不要改成太大的值比如waitKey(30)会限制主循环在30fps以下但会增加延迟感。一般保持1毫秒即可。6.2 关键点抖动导致计数不准这是最影响体验的问题。人在下蹲过程中MediaPipe的膝盖点会随着图像噪声一会儿偏左一会儿偏右角度值也跟着抖状态机就可能误判。我第一次测试时把状态机切换条件设置为单帧结果做10次深蹲能数出14个。后来在状态机里加入连续帧确认问题基本解决。还想说说角度滤波。除了状态机防抖也可以对角度值做指数平滑smoothed_angle 0.8 * smoothed_angle 0.2 * current_angle平滑系数0.8表示更依赖历史值0.2表示更依赖当前值。这个系数我用下来比较舒服既不会过度滞后也不会被噪声带偏。平滑不要超过0.9否则动作已经完成但角度还没跟上标记点会显得很肉。6.3 视角问题导致错误姿态漏检摄像头位置对识别效果影响特别大。一开始我把摄像头放在正前方人正对镜头做深蹲结果发现从正面看膝关节弯曲角度根本算不准因为大腿和小腿在屏幕平面上的投影会压缩。正确做法是摄像头放在试验者的侧面45度角或者正侧面这样膝角、髋角在图片上的投影更接近真实值。如果必须正面识别也有一个折中方案正面视角主要检测膝盖内扣和重心偏移侧面视角主要检测下蹲深度和躯干前倾。我最后做的是一个双摄像头融合版本两个方向各放一个摄像头分别提取不同维度特征。但普通用户DIY时建议先侧方位放摄像头效果提升最明显。6.4 用户身高差异导致的误判高个子用户跟矮个子用户做同一个深蹲膝盖角度的运动范围其实差别很大。比如一个身高190厘米的人膝盖弯曲到110度时大腿可能就已经低于水平线了而一个身高160厘米的人膝盖弯曲到100度都未必达到平行。如果只用膝盖角度作为统一标准高个子会被误判为蹲得太深矮个子会被误判为蹲得太浅。我的解法是引入比例阈值。不再单纯用角度而是判断髋部点在竖直方向相对于膝盖的位置。比如有效深蹲的判定条件增加一条髋部y坐标大于膝盖y坐标即髋部确实下降到膝盖以下。这样角度只要作为辅助参考防止一些人变形下蹲但主要计数和评分以关键点相对位置变化为准。这个调整之后不同身高的人测试下来计数准确率大幅提升。常见问题速查表问题现象可能原因解决方案系统卡顿帧率低于15输入分辨率过高强制640x480降低模型复杂度深蹲计数多余或漏计状态机切换太快增加连续帧确认平滑角度膝盖角度抖动图像噪声、关键点抖动指数平滑滤波增加最小帧差深蹲幅度判断不准摄像头正对正面改为侧面45度角摆放检测不到人体光线过暗或背景杂乱增加直方图均衡化尽量保证纯背景语音不发声系统TTS未安装安装系统语音包或改用音频提示音不同身高用户误判固定角度阈值不适配引入关键点相对位置作为辅助判断画面镜像导致角度异常摄像头左右反转使用cv2.flip做水平翻转后再送入模型最后我再分享一个实际部署时的细节摄像头的曝光设置会影响MediaPipe对遮挡的判断。在光线强烈的环境下检测到的肩膀或膝盖点偶尔会大幅偏移。我试着关了摄像头的自动曝光手动设置亮度值情况会稳定很多。如果你使用笔记本自带摄像头无法手动调节自动曝光那就在算法层面加强滤波把检测到的异常大位移点剔除掉。另外康复训练场景最好加上最低动作时间约束。老年人群做深蹲时速度通常较慢如果状态机按年轻人的速度来判断可能在老年用户还没蹲到底的时候就被误判为蹲起导致无法计数。我在状态机里增加了一个底部保持时间检测当膝盖角度小于阈值时即使帧数超过阈值也保持在BOTTOM状态直到用户主动站起。这个改动虽然简单但让系统对康复人群友好了很多。这个项目做下来我的体会是人体姿态识别应用的门槛其实比以前低多了真正的难点不在模型本身而在姿态逻辑的合理性、系统的稳不稳定性以及对不同人群的适应能力。如果你也想做AI健身教练或者其他姿态评估工具建议先搭一个最小可用版本把状态机、角度计算和反馈跑通再逐步增加更复杂的错误识别逻辑。跑通之后你就发现剩下的只是不断根据实际测试数据去调阈值的事。