
简介基于mediapipe和KNN分类算法的健身计数器项目源码包支持引体向上、深蹲、俯卧撑三种动作识别与计数。与传统依赖骨骼角度阈值判断的方案不同它通过人体关键点归一化编码和k近邻分类完成动作判定切换运动类型时几乎无需改动代码适合python开发者用于运动AI入门、健身App原型落地等场景。压缩包共61个文件约18.45MB核心为10个py模块覆盖姿态编码、KNN分类、指数移动平均平滑、计数、结果可视化、训练集预处理与视频检测等完整流程另附csv特征库、可直接运行的视频示例、xml工程配置和jpg样例图片方便查看效果。目前已有270人学习。项目说明中详细阐述了各模块职责与训练样本组织要求配合示例视频和预置特征文件可快速复现计数效果也支持采集自定义样本、扩充自己的动作库。1. 健身计数器难点不在“识别”而在“状态”转换拿摄像头对着自己练引体向上最尴尬的时刻是模型把每个动作都识别对了计数却跳到莫名其妙。这个问题在这个标题所指的 MediaPipe KNN 方案里尤其常见MediaPipe 负责把身体变成 33 个关键点KNN 负责把这 33 个点变成“上拉 / 下放 / 悬停”的姿势标签而真正决定屏幕上“1、2、3”的是两者之外的计数逻辑。换句话说标题里最容易被忽略的“计数器”三个字才是这个项目的大部分工作量。我接下来会围绕这个标题拆开讲为什么选 MediaPipe KNN 而不是更“高级”的时序模型关键点和角度特征怎么构造KNN 怎么训练以及最终怎么用状态机把类别流变成可靠的计数值。这个方案适合手上只有一台笔记本摄像头、想快速搞定健身计数原型或毕业设计的人整套跑通只需要 Python 和几个 pip 包。2. 为什么是 MediaPipe 加 KNN轻量方案的选型逻辑先给结论用 YOLO 做姿态估计再接入分类器并不是不行而是把简单问题复杂化。YOLO 给的是目标检测框要做姿态估计还得额外接一个骨架分支模型而这个分支恰好是 MediaPipe Pose 已经做好的事。MediaPipe 是端到端的人体姿态估计方案输入一帧 RGB 图输出人体 33 个关键点的三维坐标和可见度不依赖 GPU 也能跑到十几帧以上KNN 则是这 33 个点与最终分类之间最直接的桥梁。两个库加起来不到两百行核心逻辑跑完数据采集、训练、计数全流程这个体量非常适合作为健身计数器的第一版实现。2.1 从 MediaPipe 拿到的 33 个关键点带什么信息MediaPipe Pose 的输出是一组landmarks每帧返回 33 个关键点每个点包含x、y、z、visibility四个值。x和y是归一化到 [0,1] 的图像坐标z是与髋部中心相对的深度估计visibility是该点被算法“看见”的置信度。注意z不是真实摄影测量度它只在单目图像里表达相对前后关系所以这个值在俯卧撑、深蹲这类身体姿态大幅变化的动作里抖动会很明显后期特征工程里建议降权或直接不用。import cv2 import mediapipe as mp mp_pose mp.solutions.pose mp_draw mp.solutions.drawing_utils pose mp_pose.Pose( static_image_modeFalse, # 视频流模式配合连续帧跟踪 model_complexity1, # 0轻量、1平衡、2高精度 smooth_landmarksTrue, # 平滑关键点减少抖动 min_detection_confidence0.5, # 首次检测阈值 min_tracking_confidence0.5, # 跟踪阈值低于则重新检测 ) cap cv2.VideoCapture(0) while cap.isOpened(): ret, frame cap.read() if not ret: break rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results pose.process(rgb) if results.pose_landmarks: # 这里会拿到 33 个关键点后文统称为 landmarks mp_draw.draw_landmarks(frame, results.pose_landmarks, mp_pose.POSE_CONNECTIONS) cv2.imshow(pose, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release()这段代码是做整个项目的起点。逻辑上先取一帧画面转成 RGBMediaPipe 内部用的就是 RGB 顺序直接喂 BGR 会得到明显偏色的姿态结果pose.process()返回的对象里pose_landmarks保存了全部关键点pose_landmarks.landmark[11]是左肩landmark[12]是右肩landmark[23]和landmark[24]是左右髋。参数上model_complexity1在普通笔记本上能跑到 25 帧左右如果机器比较老就降到 0代价是肩、髋这类大关节的定位精度会下降一点。这里还有个容易被新手忽略的点min_tracking_confidence设得越高人体快速移动时丢失跟踪的概率越大健身动作普遍速度不慢我一般保持在 0.5 而不是网上教程常见的 0.7。2.2 KNN 只负责“这一帧是什么姿势”不负责计数KNN 是惰性学习算法没有传统意义上的训练过程它只是把训练样本的特征向量和标签存起来。预测时计算新样本到所有训练样本的距离取距离最近的 K 个样本做投票票数最多的类别就是结果。之所以在这个项目里够用是因为健身计数的本质不是“识别动作”而是“识别动作的相位”。引体向上、深蹲、俯卧撑都是周期性运动每个周期里只有几个明显不同的静止姿态比如引体向上的“顶端悬挂”和“底部悬挂”深蹲的“站直”和“蹲到底”。KNN 对这种少量类别、特征明显的静态姿势分类非常擅长几十到几百条样本就能出可用效果。这也解释了为什么不需要 LSTM 或 Transformer 这类时序模型。时序模型解决的是“动作边界模糊、需要看前后文才知道在干什么”的问题而健身动作的相位差异足够大单帧姿态已经能给出很强的类别信号。引入时序模型意味着要标注动作起止、要处理变长序列、要调更多超参数对一个摄像头固定、动作规范的使用场景来说属于把简单问题复杂化。假如你之后想把方案扩展到“动作不标准提醒”那再考虑用序列模型或者 MediaPipe Model Maker 自定义分类器也不迟。2.3 装好 mediapipe 环境避免 Python 版本翻车这个标题下最容易劝退人的不是算法而是环境。MediaPipe 在 Windows Python 高版本上经常遇到装不上 wheel 的问题报错信息常常是一大段红色日志最后落在“No matching distribution found”上。常见做法是单独建一个 Python 3.9 的 conda 环境装这套依赖避坑概率最高Python 3.11 及以上版本搭配 MediaPipe 也不是完全不能装但不同操作系统、不同版本之间兼容性表现差异很大没必要把自己的时间搭进去。conda create -n fit-counter python3.9 -y conda activate fit-counter pip install mediapipe scikit-learn opencv-python numpy这里mediapipe负责姿态估计scikit-learn提供 KNN 分类器和标准化工具opencv-python负责读摄像头和画画面numpy用来做向量运算。装完先跑一行python -c import mediapipe; print(mediapipe.__version__)能正常输出版本号就说明环境没问题。Windows 上如果报缺少 DLL多半是缺 Visual C 运行库装上对应运行库再重试即可。3. 特征工程把身体的 33 个关键点变成 KNN 能吃的向量Feature engineering 是这个项目里决定上限的部分。同样一套 MediaPipe 和 KNN有人做出来非常准有人做出来在摄像头前稍微动一动就乱跳差别基本全在喂给 KNN 的特征上。直接把 33 个点的原始坐标拼成一个 99 维向量喂进去是最简单的做法但效果也是最差的。3.1 原始坐标直接喂 KNN 是不行的原因有三个平移、缩放、姿态参考系。人在画面里的位置会移动同一个下蹲姿态站在画面左侧和右侧x坐标差异很大KNN 的欧氏距离会被这种无意义的位移主导人与摄像头的距离不同同一个动作在画面里的大小编号差异也很大引体向上时身体在画面里是倒的还涉及翻转。所以第一步必须做归一化常见做法是以髋部中心为原点再按肩宽做尺度归一化。import numpy as np def pose_to_normalized_feature(landmarks): lm np.array([[l.x, l.y, l.z] for l in landmarks]) # 33 x 3 left_shoulder lm[11] right_shoulder lm[12] left_hip lm[23] right_hip lm[24] hip_center (left_hip right_hip) / 2 lm lm - hip_center # 平移到以髋部中点为原点 scale np.linalg.norm(left_shoulder - right_shoulder) # 肩宽 lm lm / (scale 1e-6) # 防止除零 return lm.flatten()逻辑说明先把所有关键点平移让髋部中心跑到坐标原点这样人站在画面左边还是右边就无所谓了再用肩宽做除法让身高和镜头距离不再影响特征尺度。之所以用肩宽而不是躯干长度是因为肩宽在任何直立和悬垂姿态下都相对稳定而且 MediaPipe 对肩膀的定位精度比脚踝高。加1e-6是为了避免肩宽为 0 时除零。这套特征构造完后向量里的数值就只表达“身体形态”不表达“人在画面里的绝对位置”。注意这里只建议用x和yz在俯卧撑这类贴近地面的动作里抖动非常大把z拼进去反而会引入噪声。3.2 加一组关节角度视角变化也不怕坐标归一化解决的是平移和缩放问题但解决不了“同一个动作在不同角度下拍摄坐标值完全不同”的问题。比如摄像头放在正前方和侧面 30 度肩、肘、腕三个点在画面里的相对位置差异很大。关节角度特征能缓解这一点因为角度是几何不变量不随拍摄视角旋转而改变。这里说的不是所有 33 个点而是几个对健身动作最关键的角度肘关节角、肩关节角、髋关节角、膝关节角。把这些角度和归一化坐标拼接起来KNN 的判别会更鲁棒。def calc_angle(a, b, c): 计算三个点构成的角度b 是顶点 a, b, c np.array(a), np.array(b), np.array(c) ba a - b bc c - b cos_theta np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc) 1e-6) return np.degrees(np.arccos(np.clip(cos_theta, -1.0, 1.0))) def pose_to_combined_feature(landmarks): lm np.array([[l.x, l.y, l.z] for l in landmarks]) # 先做平移缩放归一化 hip_center (lm[23] lm[24]) / 2 lm lm - hip_center scale np.linalg.norm(lm[11] - lm[12]) lm lm / (scale 1e-6) angles [ calc_angle(lm[11], lm[13], lm[15]), # 左肘角肩-肘-腕 calc_angle(lm[12], lm[14], lm[16]), # 右肘角 calc_angle(lm[23], lm[11], lm[13]), # 左肩角髋-肩-肘 calc_angle(lm[24], lm[12], lm[14]), # 右肩角 calc_angle(lm[11], lm[23], lm[25]), # 左髋角肩-髋-膝 calc_angle(lm[12], lm[24], lm[26]), # 右髋角 calc_angle(lm[23], lm[25], lm[27]), # 左膝角髋-膝-踝 calc_angle(lm[24], lm[26], lm[28]), # 右膝角 ] return np.concatenate([lm[:, :2].flatten(), angles])逻辑说明先用第 3.1 节的方法做平移缩放然后取x、y两个维度进特征拼上 8 个角度值。这样最终特征维度是 33×2 8 74 维比纯坐标少了 25 维信息量却更大。角度计算时重点看顶点膝关节角反映深蹲深度肘关节角区分引体向上的顶端和底端肩关节角用来区分俯卧撑推起和下压。参数上注意所有点的坐标都来自归一化后的lm因为角度只和三点相对位置有关和尺度无关所以这一步其实不依赖归一化但保持统一流程更顺手。这里有个经验深蹲时膝盖角度从 170 度到接近 90 度变化引体向上时肘角从 160 度到 30 度变化这些区间本身就足够区分相位不用再另外设计复杂特征。如果你发现单靠这几个角度在某个动作上不稳可以再加手腕到髋部的距离这种“部位间距离”特征但不建议一开始就堆特征维度。3.3 类别标签设计把动作拆成静态姿态KNN 输出的类别不应该叫“引体向上”或“深蹲”而应该是这些动作的相位标签。比如对引体向上设计neutral站在地上休息或悬垂放松、pullup_top下巴过杠、pullup_bottom手臂伸直悬垂三类。这样设计的原因是计数需要“先上后下”或“先下后上”的完整过程而不是只识别动作名称。对深蹲则是squat_up和squat_down俯卧撑则是pushup_up和pushup_down。每个动作单独训练一个 KNN 模型互不干扰比把所有类别混在一个模型里更容易调优。采集样本时我习惯让每个类别至少 100 条稳定一点可以到 200 条再多对 KNN 没有明显收益反而让预测变慢。这里还涉及一个类别平衡问题neutral类最容易采多因为多按几次按键就多了一堆样本。KNN 不支持class_weight所以类别不平衡得靠采样策略解决常见做法是把neutral类随机欠采样到和其他类相近的数量否则预测结果会偏向样本最多的类现象就是你站直后被疯狂识别成neutral。4. 训练与计数KNN 出类别状态机出次数整个项目的执行顺序是采集带标签的姿态特征 → 标准化 → 训练 KNN → 实时推理 → 状态机计数。前两步在离线完成后两步在摄像头循环里完成。接下来我会按这个流程给出一套可以直接照抄的实现。4.1 数据采集和打标签实时按键采集比事后抽帧省事数据采集有两种常见路子一种是录制视频再事后逐帧抽帧并标注另一种是实时摄像头前摆姿势按键采集。我强烈推荐后者因为前者意味着你要给几百帧图像一帧帧打标签枯燥还容易标错。按键采集时你只需要在镜头前摆好目标姿势按一个键就保存一条样本。import csv import cv2 import mediapipe as mp mp_pose mp.solutions.pose pose mp_pose.Pose(model_complexity1, min_detection_confidence0.5) # 按键映射nneutral, ttop, bbottom, q退出 label_map {n: neutral, t: top, b: bottom} rows [] cap cv2.VideoCapture(0) while cap.isOpened(): ret, frame cap.read() if not ret: break rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results pose.process(rgb) if results.pose_landmarks: feature pose_to_combined_feature(results.pose_landmarks) cv2.putText(frame, waiting for key..., (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(collect, frame) key cv2.waitKey(1) 0xFF if key in (ord(n), ord(t), ord(b)): label label_map[chr(key)] rows.append([label] feature.tolist()) print(fsample {len(rows)}: {label}) elif key ord(q): break cap.release() with open(collected.csv, w, newline) as f: writer csv.writer(f) writer.writerow([label] [ff{i} for i in range(len(rows[0]) - 1)]) writer.writerows(rows)代码逻辑是每帧实时计算特征按键时把当前特征和标签一起攒起来退出后写进 CSV。每个按键只采一帧所以不要按住不放而是每按一次换一下身体位置再按让样本覆盖不同站位和微小的姿态差异。这里最容易犯的错是固定站在原地连续采集几百条这样收集到的样本几乎一样KNN 学到的形态过于单一换一个站位就翻车。我一般每个类别采 150 到 200 条中间会有意左右平移半步、靠近和远离摄像头一点让平移和尺度归一化真正有东西可学。4.2 训练流程标准化加 KNN 的网格搜索KNN 的核心参数就两个K 值和投票权重。K 值太小容易受噪声点影响太大则会把边界样本拉进类别投票权重weightsdistance表示近邻按距离加权投票比均匀投票更能突出“近的样本更像”的直觉。这里有一个比调参更重要的前置步骤标准化。特征里归一化坐标和角度值的量纲不一样角度在 0 到 180 之间坐标在 -2 到 2 之间不标准化的话 KNN 的距离计算就被角度特征完全主导了。import pandas as pd from sklearn.model_selection import GridSearchCV from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.neighbors import KNeighborsClassifier df pd.read_csv(collected.csv) X df.drop(columns[label]).values y df[label].values pipe make_pipeline( StandardScaler(), KNeighborsClassifier(algorithmkd_tree) ) param_grid { kneighborsclassifier__n_neighbors: [3, 5, 7, 9], kneighborsclassifier__weights: [uniform, distance], } grid GridSearchCV(pipe, param_grid, cv5, scoringf1_macro) grid.fit(X, y) print(best params:, grid.best_params_) print(best f1:, grid.best_score_)逻辑说明make_pipeline把标准化和 KNN 串成一条流程标准化参数在每次交叉验证时只从训练折里学习避免信息泄露。GridSearchCV用 5 折交叉验证在 8 组参数组合里挑出最优解f1_macro对类别不平衡比准确率更敏感。这里要注意algorithmkd_tree虽然维度不高但样本量几百上千条时比默认暴力搜索快一些而ball_tree在这种维度下优势不明显。训练完把grid.best_estimator_用joblib.dump存盘推理时joblib.load回来直接用。有一点容易被忽略推理时传进去的特征必须走和训练时完全相同的流程平移、缩放、拼角度一样都不能少否则模型看到的分布跟训练时对不上结果一定乱。4.3 状态机计数从类别序列变成计数的完整实现训练好 KNN 之后摄像头循环里会得到一长串类别标签比如bottom → bottom → top → top → bottom。如果直接数“出现多少次 top”那身体在顶端抖动一下就会多计一次。所以计数要用状态机规则很简单只有当你先观察到top之后又观察到bottom才算完成一次动作。引体向上、深蹲都是这个方向俯卧撑把top和bottom对调即可。import joblib import collections model joblib.load(knn_model.joblib) scaler model.named_steps[standardscaler] knn model.named_steps[kneighborsclassifier] MIN_FRAMES 3 # 连续 N 帧确认状态 count 0 state neutral candidate neutral consistent 0 history collections.deque(maxlen10) def feed_prediction(pred): global count, state, candidate, consistent if pred state: candidate pred consistent 1 else: candidate pred consistent 1 if consistent MIN_FRAMES: state candidate # 状态转换先 top 后 bottom 视为一次 if pred bottom and state top: count 1 state bottom while cap.isOpened(): ret, frame cap.read() if not ret: break rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results pose.process(rgb) if results.pose_landmarks: feat pose_to_combined_feature(results.pose_landmarks).reshape(1, -1) feat_scaled scaler.transform(feat) proba knn.predict_proba(feat_scaled)[0] pred knn.classes_[np.argmax(proba)] if np.max(proba) 0.6: pred neutral # 低置信度按中性处理 history.append(pred) feed_prediction(pred) cv2.putText(frame, fcount: {count}, (20, 60), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)逻辑说明MIN_FRAMES3意味着连续 3 帧预测为同一个类别才切换状态这是一个简单有效的去抖手段能过滤单帧抖动造成的误翻转。predict_proba返回每个类别的投票占比取最高值作为置信度低于 0.6 就视为neutral避免模棱两可的姿势乱跳。计数条件是pred bottom and state top注意这里用的是当前帧的pred而不是state原因是state可能已经是bottom再用state判断会漏计。低置信度统一归为 neutral 意味着动作不标准或身体出画面时不会触发计数稳定性好很多。状态机的两个参数值得调MIN_FRAMES大一点能压抖动但会引入延迟动作快的用户做到每 0.5 秒一个完整动作时可容忍延迟不超过 5 帧置信度阈值设在 0.5 到 0.7 之间太低了噪声多太高了容易漏计真实动作。5. 避坑摄像头里的 4 个血泪排查这套方案看起来链路不长但真正跑起来后你会发现绝大多数问题不出在 KNN 分类上而是出在“摄像头看到的东西”和“模型以为看到的东西”不一致。下面这四条是我自己踩过的按现象、原因、解决的顺序写便于你直接对照排查。5.1 俯卧撑贴地时 MediaPipe 直接看不清现象做俯卧撑下压到最低点时预测结果忽然变成neutral或者完全检测不到姿态计数中断。原因身体贴近地面时从摄像头视角看躯干和四肢几乎是平行的部分关键点相互遮挡visibility置信度急剧下降。MediaPipe 在低可见度情况下输出的关键点位置漂移得厉害反而比检测不到更麻烦。解决在特征提取前加一道可见度过滤——比如肩、髋、膝、踝这几个关键点的visibility低于 0.5 时直接丢弃这一帧不让它进 KNN。丢失帧用状态机里“保持上一状态”的策略顶过去而不是强行补一个预测值。代码上只需要在feed_prediction之前判断一次可见度不可见就continue同时把计数逻辑里MIN_FRAMES稍微调大比如 4 到 5 帧俯卧撑这类动作的低置信度瞬间就不会击穿状态。5.2 引体向上的杠把关键点带偏了现象人在单杠下方做引体向上时肩部关键点在杠的位置上漂移上升和下降的预测类别来回横跳。原因单杠本身具有水平和垂直的边缘特征MediaPipe 的人体检测分支有时会把杠的一部分误认为是身体的一部分尤其当人正好在杠正下方时躯干和杠在图像上重叠严重。解决最直接的方案是换拍摄角度把摄像头放在侧面 45 度而非正对单杠让杠和人在画面上不要重叠。这个属于拍摄问题改代码解决不彻底。如果必须正面拍可以把model_complexity提到 2 来提高关键点定位精度帧率会从 25 掉到 15 左右但位置稳定性会好一些。另外在埋状态机时对top类别做一次条件校验要求手腕关键点的 y 坐标低于肩膀 y 坐标否则不认为是引体向上顶端。这个校验是从动作语义上排除误识别比单纯信任分类器更靠谱。5.3 预测全是 neutral类别不平衡和特征尺度问题现象模型训练完在摄像头前怎么摆姿势输出永远是neutral其他类别的预测概率很低。原因两个可能性叠在一起。第一个是采集时neutral类样本量远大于其他类KNN 距离相近时投票结果自然偏向大类别第二个是推理时的特征没有走和训练时一模一样的标准化流程比如直接用了原始坐标而没有经过StandardScaler转换。解决类别问题上用pd.value_counts看类别分布把neutral类随机欠采样到其他类的 1.2 倍以内再重训。特征问题上确认推理时scaler.transform(feat)用的是训练时那个已经fit过的对象而不是新fit一个joblib.dump(grid.best_estimator_)存整个 pipeline 能从根本上避免这个问题因为标准化和模型是绑在一起的。如果你发现自己的场景里 neutral 类确实天然比其他类多那就在计数逻辑里做条件压制只有当top或bottom的置信度超过 0.7 时才切换状态低于这个阈值一律保持当前状态。5.4 一次动作被计了两次抖动与迟滞现象引体向上做一次计数器跳了 2 或者直接跳 3。原因动作顶端或者底部有微小的上下晃动预测在top和bottom之间反复横跳每个来回都触发了一次计数条件。解决这就是MIN_FRAMES和迟滞机制要处理的问题。把连续确认帧数从 3 提到 5能挡住大部分单帧抖动但会让动作快的人感觉到延迟。另一个更结构化的办法是加迟滞区间当状态是bottom时必须连续看到 3 帧top才切到top反过来也一样而不是每帧无记忆地更新状态。这样状态切换需要“穿过一个缓冲带”抖动幅度小于缓冲带的噪声就永远触不发翻转。迟滞的具体参数按你的摄像头帧率来30 帧率下 3 到 5 帧比较合理帧率低就相应减少否则真实动作在慢速进行时也会被卡住。6. 进阶验证用回放把模型的每一帧决定都摊开看训练完 KNN、写完状态机你以为项目就结束了吗真正的调优才刚刚开始。我在完成第一版后做了一个回放工具把录好的视频重新喂给整套流水线每一帧都叠加上“预测类别、置信度、当前计数、状态”四个信息存成带标注的视频。然后逐帧回放找出计数错误帧发生的前后几秒对照画面里的实际姿态你会很清楚地看到问题是出在 KNN 分类还是状态机逻辑上。这个验证手段比任何评估指标都直观因为计数的错误往往集中在动作的过渡帧而这些过渡帧在整体准确率上根本看不出来。回放时的观察重点是“状态翻转的时机”。比如引体向上你要确认从bottom到top的翻转发生在手臂伸直到弯曲的什么位置反面案例是翻转发生得过早导致还没完全到顶端就开始计数。我习惯在每个动作视频上回放三遍第一遍看整体计数对不对第二遍慢放过渡帧找翻转时机第三遍遮住画面只看类别序列专门挑那种“连续 20 帧全是 top”的异常段。这种验证方式还有一个附加好处就是能顺便验证数据采集的覆盖度。如果你发现某个角度、某个站位下预测频繁出错说明该补的样本没采够回到第 4.1 节重新采十几条针对性样本就能解决。最后一个能让方案更通用的经验把状态机抽象成“举起 - 放下”通用模板。无论引体向上、深蹲还是俯卧撑本质都是两个相位交替唯一区别是哪个相位对应top、哪个对应bottom。把动作名、相位方向、计数方向做成配置项换一个动作只需要换配置和重新采集数据主流程代码一行都不用改。这样一来你手里的这套 MediaPipe 加 KNN 方案就不是一个只能跑三个动作的脚本而是一个能扩展的健身计数框架。我做完回放之后养成了一个习惯每调一个参数就录十秒视频看回放而不是对着摄像头一遍遍试动作这个习惯帮我少走了很多弯路。希望帮到你。本文还有配套的精品资源点击获取