☰
基于MediaPipe Pose的俯卧撑计数与姿势矫正系统实现
2026/10/2 20:07:58 网站建设 项目流程

家里囤了张瑜伽垫,刚开始练俯卧撑时,我用手机记次数,朋友在旁边用人眼帮我盯动作。后来发现两个问题:数着数着就断了,或者明明塌腰了,自己还觉得挺标准。于是想着干脆自己写个工具,用摄像头实时识别俯卧撑,顺便帮我盯姿势有没有走样。调研了一圈,最后选了Google的MediaPipe Pose,配合OpenCV和Python,做出了一个既能计数又能做基础动作质量评估的小系统。

这个项目适合谁?想用代码辅助力量训练的人,带学生的健身教练,还有想做一个有趣AI应用练手的开发者。MediaPipe的优势在于它足够轻,CPU就能跑,关键点输出稳定,不用自己从头训模型。整套系统从思路到落地,核心四块:姿态关键点获取、俯卧撑动作建模与计数逻辑、姿势矫正规则、实时反馈与调试优化。下面把这四块拆开讲,代码和阈值都给到,你可以直接抄作业,再按自己的身体参数微调。

1. 项目背景与方案选型

1.1 为什么选择MediaPipe做俯卧撑识别

做俯卧撑识别,本质上要解决两个问题:人体姿态估计和动作阶段判断。人体姿态估计方向有几个选项:OpenPose准确率高,但重,CPU上跑起来费劲,一般人手里的笔记本风扇会起飞;PoseNet在浏览器里好用,但拿到Python里玩没MediaPipe方便;直接用OpenCV做背景差分和轮廓分析,干扰太大,只能用在做“有没有人动”这种粗粒度判断,没法精确拿到关节角度。

MediaPipe Pose是Google开源的人体姿态估计方案,输出33个关键点坐标(landmark),包含鼻子、肩膀、肘部、手腕、髋部、膝盖、脚踝等关键部位,每帧还能给出每个点的可见度和置信度。对它做过对比测试:在同一位居CPU的笔记本上,分辨率设置成640x480时,MediaPipe能跑到30FPS以上,占用CPU在30%-50%之间,OpenPose基本只有个位数帧率。对实时交互来说,30FPS意味着你做完一个俯卧撑的下降、上升过程,系统能捕捉到十几帧关键状态,这足够支撑可靠的计数逻辑了。

另一个原因是MediaPipe的跨平台属性,Python、Android、iOS、Web全都有。我今天演示的是Python版本,但同样的逻辑稍微改改就能搬到手机上,做成App或者小程序,扩展性很强。加上它自带的人体检测加姿态跟踪管线,在单人场景下非常稳定,不用额外处理多目标匹配这些麻烦事。

1.2 系统能力边界

明确一下系统能做什么:输入是摄像头实时画面,输出是三件事:俯卧撑次数累计、当前动作阶段(准备/下降/上升/完成)、姿势异常提示(塌腰、撅屁股、半程、手臂外展等)。另外我会把每帧的关键点数据记录下来,方便之后回看和分析。

需要说明的是,这套方案依赖单目摄像头,最适合侧面视角。正面或斜向视角不是不能用,但关节角度的计算误差会明显变大。系统默认假设人体基本侧对镜头,这样“肩-肘-腕”构成的平面和成像平面平行,角度测量最准。如果你想对着镜子练,镜子里的画面会有镜像问题,记得在代码里做水平翻转,并且保持镜面干净,光线均匀。

还有一个边界:MediaPipe是通用姿态估计模型,不是专门为俯卧撑训练的。它的优势是泛化能力强,但遇到极限动作或者大幅遮挡时,关键点置信度会下降。所以系统里必须做置信度检查和状态机容错,否则偶尔的某一帧误检会导致计数乱跳。后面第5节我会专门讲怎么处理。

2. 姿态关键点映射与俯卧撑动作建模

2.1 MediaPipe Pose的关键点输出

跑起来之后,每帧会得到results.pose_landmarks,它是个NormalizedLandmarkList,里面33个点,每个点有x, y, z, visibility四个值。x和y是相对图像宽度和高度的归一化坐标,范围0到1。z以臀部中心为原点的大致深度值,单位不严格,日常动作识别里基本用不上,主要看x和y。visibility是一个0到1的置信度,表示模型对这个点的可见程度,取值在0.6以下基本不可信,我们要拿它来过滤坏帧。

33个点对应的索引在MediaPipe官方文档里有,俯卧撑用得到的主要是这几个:

关键点名称索引作用
左肩 / 右肩11 / 12判断躯干倾斜、手臂位置
左肘 / 右肘13 / 14计算手臂弯曲角度
左腕 / 右腕15 / 16支撑点位置
左髋 / 右髋23 / 24判断躯干是否塌陷或者撅起
左膝 / 右膝25 / 26判断腿部位置
左踝 / 右踝27 / 28判断脚部支撑位置

因为是侧面对着摄像头,系统里取左右两侧中visibility更高的那侧来计算角度。这样无论你做左右手交替还是标准俯卧撑,都能正确跟踪。

2.2 俯卧撑关键角度定义

有了三个关键点坐标,就能计算关节角度。以肘部角度为例:取肩-肘-腕三个点,肘部是顶点,问题就转化为求两个向量v1 = shoulder - elbow和v2 = wrist - elbow之间的夹角。余弦定理,二维坐标直接用向量点积:

import numpy as np def calc_angle(a, b, c): """计算三点夹角,b是顶点,返回角度值 0~180""" a = np.array(a[:2]) # 只取x,y b = np.array(b[:2]) c = np.array(c[:2]) v1 = a - b v2 = c - b norm_v1 = np.linalg.norm(v1) norm_v2 = np.linalg.norm(v2) if norm_v1 == 0 or norm_v2 == 0: return 0.0 cos_theta = np.dot(v1, v2) / (norm_v1 * norm_v2) cos_theta = np.clip(cos_theta, -1.0, 1.0) angle = np.degrees(np.arccos(cos_theta)) return angle

俯卧撑过程里,三个角度最有用:

  • 肘关节角度,由肩、肘、腕三个点算出。用来判断下降深度和上升完成度。标准俯卧撑,下降到最低点时肘角大约在70度到90度之间,撑起来时接近180度。
  • 髋关节角度,由肩、髋、膝三个点算出。这个角度衡量躯干和腿部是否保持在一条直线上。正常平板姿态下,这个角度接近180度,处于160度到180度之间都算可接受。如果角度明显小于150度,说明髋部向下塌,就是俗称的“塌腰”;如果明显大于180度,髋部向上顶,就是我们常说的“撅屁股”或者“臀部过高”。
  • 肩关节角度,由肘、肩、髋三个点算出。用来检查手臂是否过度外展。做俯卧撑时,上臂和躯干的夹角建议在20度到45度之间,如果这个角度超过60度,说明手肘往外张得太厉害,对肩关节压力很大。

这三个角度覆盖了俯卧撑质量评估最主要的信息:手臂弯曲程度、躯干平板程度、肩胛稳定性。不用去看膝盖和脚踝,也能应付大部分场景。

2.3 动作状态机的设计

计数不能只看“当前角度小于某个值”就加一次,因为俯卧撑是一个动态过程,如果只检测瞬时状态,身体抖动一下或者关键点跳变就会引发误判。更稳的做法是用状态机。

我设计了三个状态:UP(撑起)、DOWN(下降到底)、WRONG(错误姿态)。状态转移逻辑:

  • 初始状态是UP,此时肘角大于等于设定阈值(默认150度)。
  • 当肘角持续降低,小于等于下降阈值(默认90度),并且髋角正常,进入DOWN状态。
  • 在DOWN状态下,如果肘角开始回升,持续大于等于上升阈值(默认160度),并且髋角正常,判定完成一次,计数加一,状态回到UP。
  • 无论哪个状态,如果连续若干帧检测到髋角异常,切换到WRONG状态,并发出姿势提醒。错误纠正后,如果髋角恢复正常,再回到正常状态继续计数。

这种状态机的核心是“持续多少帧”的判定,不是单帧生效,不然噪声太大。我代码里用的frame_threshold = 5,也就是连续5帧满足角度条件才切换状态。用人的动作速度换算一下,30FPS下5帧也就是约0.17秒,不会感觉到延迟,又能过滤掉短时间抖动。

为什么要分下降阈值和上升阈值?因为现实动作存在“滞后”和“弹性”,你降到90度之后,身体不会立刻回到160度,中间会有个缓冲过程。如果下降上升用同一个阈值,比如都用130度,那肌肉在临界点轻微抖动就会来回触发。两个阈值形成滞回区间,相当于给状态机加了一层物理“死区”,稳定性立刻提升。

3. 系统实现与核心代码

3.1 环境搭建

先交代环境。我用的版本是:

  • Python 3.9
  • opencv-python 4.8+
  • mediapipe 0.10+
  • numpy 1.24+

安装就是一个命令的事情:

pip install opencv-python mediapipe numpy

如果你还需要语音提示,加一个:

pip install pyttsx3

这里提醒一句:MediaPipe版本更新比较快,API在0.10.x系列中稳定,后面如果出新大版本,注意看一下Pose接口是否变化。遇到奇怪报错,先排查依赖版本。

3.2 核心流程代码

先放一个完整的基础版本,功能是:实时读取摄像头,绘制骨架,计算肘角和髋角,进行状态机计数,输出文字提示。语音部分下一节加。

import cv2 import mediapipe as mp import numpy as np mp_pose = mp.solutions.pose pose = mp_pose.Pose( static_image_mode=False, model_complexity=1, smooth_landmarks=True, enable_segmentation=False, min_detection_confidence=0.6, min_tracking_confidence=0.6, ) mp_draw = mp.solutions.drawing_utils def calc_angle(a, b, c): a = np.array(a[:2]) b = np.array(b[:2]) c = np.array(c[:2]) v1 = a - b v2 = c - b norm_v1 = np.linalg.norm(v1) norm_v2 = np.linalg.norm(v2) if norm_v1 == 0 or norm_v2 == 0: return 0.0 cos_theta = np.dot(v1, v2) / (norm_v1 * norm_v2) cos_theta = np.clip(cos_theta, -1.0, 1.0) return np.degrees(np.arccos(cos_theta)) def get_side_landmarks(landmarks): """根据可见度挑选更可靠的左右侧关键点""" left_conf = landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value].visibility right_conf = landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value].visibility if left_conf >= right_conf: return landmarks[11:24] # 这是左半边的索引范围 else: # 右半边的索引需要单独取 ids = [12, 14, 16, 24, 26, 28] return [(landmarks[i].x, landmarks[i].y, landmarks[i].z, landmarks[i].visibility) for i in ids] # 实际使用时,更简单的方式是直接取可见度高的单个点 def pick_visible(landmarks, left_idx, right_idx): left = landmarks[left_idx] right = landmarks[right_idx] return left if left.visibility >= right.visibility else right # 状态机参数 UP_THRESHOLD = 150 DOWN_THRESHOLD = 90 HIP_LOW = 150 HIP_HIGH = 180 FRAME_THRESHOLD = 5 state = "UP" frame_count = 0 pushup_count = 0 warning_msg = "" cap = cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) cap.set(cv2.CAP_PROP_FPS, 30) while cap.isOpened(): ret, frame = cap.read() if not ret: break # 如果摄像头画面是镜像的,可以这样翻转 # frame = cv2.flip(frame, 1) rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = pose.process(rgb) if results.pose_landmarks: lm = results.pose_landmarks.landmark # 选择可见度更高的左右点 shoulder = pick_visible(lm, mp_pose.PoseLandmark.LEFT_SHOULDER.value, mp_pose.PoseLandmark.RIGHT_SHOULDER.value) elbow = pick_visible(lm, mp_pose.PoseLandmark.LEFT_ELBOW.value, mp_pose.PoseLandmark.RIGHT_ELBOW.value) wrist = pick_visible(lm, mp_pose.PoseLandmark.LEFT_WRIST.value, mp_pose.PoseLandmark.RIGHT_WRIST.value) hip = pick_visible(lm, mp_pose.PoseLandmark.LEFT_HIP.value, mp_pose.PoseLandmark.RIGHT_HIP.value) knee = pick_visible(lm, mp_pose.PoseLandmark.LEFT_KNEE.value, mp_pose.PoseLandmark.RIGHT_KNEE.value) elbow_angle = calc_angle(shoulder, elbow, wrist) hip_angle = calc_angle(shoulder, hip, knee) # 状态机 hip_ok = HIP_LOW < hip_angle < HIP_HIGH if not hip_ok: frame_count += 1 if frame_count >= FRAME_THRESHOLD: state = "WRONG" if hip_angle <= HIP_LOW: warning_msg = "塌腰了,收紧核心!" elif hip_angle >= HIP_HIGH: warning_msg = "臀部抬太高了,保持躯干一条直线!" else: if state == "UP": if elbow_angle <= DOWN_THRESHOLD: frame_count += 1 if frame_count >= FRAME_THRESHOLD: state = "DOWN" frame_count = 0 else: frame_count = 0 elif state == "DOWN": if elbow_angle >= UP_THRESHOLD: frame_count += 1 if frame_count >= FRAME_THRESHOLD: pushup_count += 1 state = "UP" frame_count = 0 else: frame_count = 0 elif state == "WRONG": # 姿态恢复后重置状态 frame_count += 1 if frame_count >= FRAME_THRESHOLD: state = "UP" warning_msg = "" frame_count = 0 # 绘制关键点和连线 mp_draw.draw_landmarks( frame, results.pose_landmarks, mp_pose.POSE_CONNECTIONS, mp_draw.DrawingSpec(color=(0, 255, 0), thickness=2, circle_radius=2), mp_draw.DrawingSpec(color=(255, 0, 0), thickness=2)) # 在画面左上角显示数据 cv2.putText(frame, f"Count: {pushup_count}", (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.putText(frame, f"Elbow: {int(elbow_angle)}", (20, 80), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2) cv2.putText(frame, f"Hip: {int(hip_angle)}", (20, 110), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2) cv2.putText(frame, f"State: {state}", (20, 140), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 0), 2) if warning_msg: cv2.putText(frame, warning_msg, (20, 180), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) cv2.imshow("Pushup Counter", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()

这段代码可以直接跑起来。里面有个细节:我在状态机切换时,用了frame_count分别对错误姿态计数和正常状态计数,避免一边计数一边报错互相干扰。另外,我故意把pose对象放在循环外初始化,这样不会每帧重新加载模型,否则性能会惨不忍睹。

3.3 关键参数调优

跑通之后,最影响使用体验的是几个阈值。

肘角下降阈值DOWN_THRESHOLD,默认90度,适合标准俯卧撑。但如果你力量不够,下不到那么深,或者膝盖着地做了简化版,可以放宽到100度。反之追求深度刺激,可以收紧到80度。需要注意的是,阈值越严,系统判定越保守,越不容易误计,但可能会漏计。

上升阈值UP_THRESHOLD,默认150度,实际撑起来的动作,肘角一般在160-175度之间。我建议150到155之间,因为这个区间正好避开了肌肉的半程位置,能有效防止“偷懒俯卧撑”被算作完整一次。如果你就是做半程训练,可以把这个阈值调到130度。

髋角上下限HIP_LOW和HIP_HIGH,采用的是平板支撑的标准范围。但每个人关节活动度不一样,腰椎前凸程度不同,建议先用一段自己的标准动作录像跑一遍,记录正常状态下髋角是多少,再设成正常范围 ± 15度。这点很重要,因为有人天生髋角就偏大,强行用180度判断反而会误报。

帧数阈值FRAME_THRESHOLD,5是个均衡值。太快容易受噪声影响,太慢会有大约0.3秒以上的延迟,动作做完要愣一下才计数,体验不好。

另外,摄像头分辨率不要贪高。640x480在30FPS下CPU占用还能接受,如果硬上1280x720,MediaPipe的推理时间会翻倍,画质提升对关键点准确度帮助有限,实际测试下来,640x480的关键点位置已经足够稳。所以我建议优先保证帧率而不是分辨率。

4. 姿势矫正的规则引擎与提醒策略

4.1 常见错误姿势检测

计数只是第一步,姿势矫正才是这套系统的价值点。我整理了四个常见错误,每个都能用角度规则直接判断:

错误类型关键几何特征判定规则建议阈值的含义
塌腰髋关节角度明显小于正常平板直线髋角 < HIP_LOW躯干核心没有收紧,腰部下坠
撅屁股髋关节角度明显大于正常平板直线髋角 > HIP_HIGH臀部抬太高,躯干和腿不成直线
半程俯卧撑下降深度不够就开始上升肘角没有降到DOWN_THRESHOLD就回升手臂没有弯曲到位
手臂外展过大肩关节角度过大,手肘远离躯干肩角 > 60度上臂和躯干夹角太大,伤肩隐患

前两种用髋角判断,第三种本质上已经被状态机覆盖了,因为不降到阈值就不会从UP切到DOWN,也就不会计数。第四种需要额外计算肩关节角度,逻辑类似,只是要监控的点变成了肩、肘、髋。我建议在状态机之外独立跑一个检测线程或者每帧检测一次,因为手臂外展可能会在上升、下降阶段都存在,如果只在特定状态才判断会漏报。

除了角度判断,还可以结合visibility做合理性检查。比如手腕的位置置信度很低,说明手部被遮挡,这时候关于手臂角度的判断都不可靠,应当暂时屏蔽错误提示,否则容易误报。

4.2 矫正提示与语音反馈

视觉文字提示在训练时其实很难注意到,因为你眼睛得盯着地面或者前方,不可能一直看屏幕。所以更实用的方式是语音提醒。用一个轻量级库pyttsx3就行,它调用系统TTS,不需要联网。

import pyttsx3 engine = pyttsx3.init() engine.setProperty('rate', 160) def speak(text): engine.say(text) engine.runAndWait()

调用时机要控制好,不能每帧都触发,否则语音会连成一片。我建议同一个错误提示只播报一次,直到状态恢复正常后再重置。比如检测到塌腰,立刻语音“塌腰了,收紧核心”,然后设置一个标记,在接下来3秒内不再播报同样的内容,否则你整个训练过程会不停被打断。

更进阶一点,可以用edge-tts生成更自然的MP3声音,然后播放。这需要异步处理,对实时性要求不高的场景也可以。我自己的体验是,pyttsx3已经足够,因为提示词就那几句,机械音反而更容易让大脑条件反射。

4.3 实时展示与数据记录

长期训练需要数据积累。我习惯把每次训练的计数、错误次数、平均髋角、平均肘角记录到CSV文件里。实现很简单,每次状态机完成一次完整动作时,把这段时间内的关键点数据汇总,追加写入文件。

import csv from datetime import datetime row = { 'timestamp': datetime.now().isoformat(), 'count': pushup_count, 'avg_elbow_angle': round(np.mean(elbow_history), 2), 'avg_hip_angle': round(np.mean(hip_history), 2), 'wrong_posture_count': wrong_count, } with open('pushup_log.csv', 'a', newline='') as f: writer = csv.DictWriter(f, fieldnames=row.keys()) writer.writerow(row)

elbow_history和hip_history在每帧计算时append,动作完成时用np.mean取平均值,然后清空列表。这些数据后面可以用Excel或pandas做趋势分析,看看每周训练的质量是不是在提升。

另外,画面显示上我会把状态颜色做区分:正常状态显示绿色,错误状态显示红色。姿态错误时,在对应的关节点画一个明显的圆圈标记提示。比如塌腰时,在髋关节画一个红圈,这样即使不看文字也能通过颜色快速感知问题。

5. 常见问题与调试心得

5.1 Landmark抖动问题

实际跑起来第一个遇到的问题就是关键点坐标会抖动。原因有很多:摄像头噪声、图像压缩、模型推理本身的不确定性。抖动直接导致角度波动,比如静止时肘角可能在5度范围内来回跳,如果阈值卡得太死,状态机就会在临界点反复横跳。

解决方法有两个方向。第一个是降低角度敏感度,也就是拉大滞回区间,这我在状态机设计里已经提到了。第二个是对角度做平滑。我推荐用指数移动平均(EMA),代码极其简单:

def ema(new_value, old_value, alpha=0.3): return alpha * new_value + (1 - alpha) * old_value

开始时,old_value取第一帧的角度,之后每帧更新。alpha越小,平滑程度越高,但反应越迟钝。实测下来alpha=0.3比较合适,既不会让动作看起来延迟,又能滤掉大部分高频抖动。注意平滑的对象最好是角度值而不是原始坐标,因为对坐标平滑之后重新计算角度,会在运动的拐点处出现角度过冲,反而更不可靠。

5.2 摄像头角度与光线影响

这是最容易踩坑的地方。我一开始把摄像头放在正面,结果显示髋角一直在170到185之间波动,怎么调阈值都容易误报。换到侧面,且摄像头高度基本与胸部平齐之后,角度计算稳定多了。原因是正面角度下,肩、肘、腕在成像平面上的投影关系,和实际三维空间中的角度差异很大,余弦定理的前提是三者共面,而在正面视角下这个条件被破坏得很严重。

光线也很关键。焦距和帧率不变的情况下,光线暗会导致图像噪声增加,MediaPipe的landmark置信度明显下降。我的建议是训练区域保证足够的顺光,尽量避开顶光造成的面部阴影。如果你晚上练,至少得开一盏灯,亮度能保证画面里人物轮廓清晰区分开。

另外,测试时我发现极小概率会出现关键点闪烁,就是某一个点突然跑到画面另一侧去。这时候visibility会骤降,所以我在计算角度前会检查所有参与计算的点的visibility是否大于0.5,如果小于0.5,就直接跳过该帧的状态更新,宁可少计数也不乱计数。

5.3 距离适配与人体检测不到

MediaPipe对目标大小有偏好。你离摄像头太远,整个人很小,关键点间距只有几个像素,角度计算误差会被放大数倍;离太近,手脚容易出画,检测也会失败。经验值:人体在画面中的高度占整幅画面的50%到70%效果最好。也就是说640x480的画面里,你的头部到脚底大概占据320到340像素。

如果出现检测不到,先别急着改模型参数。调整摄像头位置,确保全身从头到脚都在画面内。然后看results.pose_landmarks是不是空值。如果一直空,再调min_detection_confidence,从0.6降到0.4,牺牲一些精度换取检测率。min_tracking_confidence也可以降低到0.5,帮助短时遮挡后的重新跟踪。

还有一点容易被忽略:摄像头自动白平衡和自动曝光会在环境变化时突变,导致画面忽明忽暗,影响检测稳定性。用OpenCV的CAP_PROP_AUTO_WB和CAP_PROP_AUTO_EXPOSURE关闭自动调节,固定ISO和快门速度。具体参数取决于摄像头型号,可以先用默认值跑通,等遇到画面闪烁再处理。

5.4 性能优化

如果你的电脑性能一般,跑起来掉帧,优先做三件事。第一,把model_complexity从1降到0,模型精度会下降一点,但推理速度能提升30%以上,对角度计算的影响可以通过平滑来部分弥补。第二,把摄像头采集分辨率降为480x360,分析图像缩小,计算量直线下降。第三,不要在一帧里重复调用process方法,确保主循环里只推理一次。

如果你有更高追求,可以用cv2.VideoCapture配合threading做异步读取,把图像采集和姿态推理放到不同线程,避免I/O等待拖慢帧率。更极端的做法是使用MediaPipe的GPU后端,但配置起来略繁琐,对多数人来说没有必要。

性能之外还有一个逻辑层面的坑:你在状态机判断时使用了全局变量state和frame_count,如果后期加入多视角或多用户,就必须为每个目标单独保存一份状态变量,否则不同人的动作会串扰。单用户场景下没问题,但代码要预留扩展位置。

写在最后的小建议

这套系统我从能跑通到稳定使用,中间调了大概两周。最大的感受是:技术上的难点不是调用MediaPipe,而是如何把动作语义转成可靠的状态判断。角度阈值没有绝对正确的值,它跟你的训练目标、力量水平、身体结构都相关。我建议你在正式使用前,先录一段自己认为最标准的动作视频,回放时打印出每个关键角度的变化范围,再根据这个范围设定阈值。所谓“标准”也是相对的,你只需要让系统符合你自己的训练节奏。

如果你想让系统更聪明,下一步可以尝试用MediaPipe的输出序列训练一个简单的LSTM分类器,对“标准、塌腰、撅屁股、半程”做端到端分类,替代手工规则。这又是一个新项目了,但底层的数据采集和角度计算,今天这套代码已经给你打好了地基。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询