做智能健身镜这个项目之前,我一直在想一个问题:传统健身镜本质上就是一面镜子加一块屏幕,用户跟着视频练,动作错了没人管,练久了还容易形成错误的肌肉记忆。我们想做的,是让镜子里住进一个AI Agent,它能看到你的动作、判断对错、实时纠正,甚至直接给你打分。这个项目从立项到跑通demo,前后花了三个多月,踩了不少坑,也攒下来很多可以复用的经验。今天这篇就围绕AI Agent的实时动作纠正与评分,把产品定位、技术选型、算法设计和实操过程完整拆开,希望能给正在做同类项目的朋友一些参考。
1. 项目定位与整体架构设计
1.1 智能健身镜到底在补什么空缺
先说需求背景。健身行业的数据很直观:绝大多数家庭健身用户坚持不过三个月,中途放弃的头号原因不是懒,而是“练得对不对完全不知道”。跟着录播课做动作,视频里的人做了二十个,你也做了二十个,但你的膝盖可能早就内扣了,腰部也可能在代偿发力。这种无反馈状态下,动作越练越废,最后不是受伤就是放弃。
健身房和私教能解决这个问题,但成本摆在那里。一节私教课少则一两百,多则四五百,再怎么包月也是一笔不小的开销。智能健身镜的切入点就在这里:用一个足够便宜的硬件载体,装下一个“不休息的私教”——也就是AI Agent,让我们在家也能获得接近真人指导的体验。
这并不是把摄像头画面简单叠加到镜面上就完事了。真正有价值的能力有三个:
- 第一是实时评估:用户动作做到哪一步,系统能立即识别出来。
- 第二是主动纠正:发现错误时给出具体、可执行的口令,比如“膝盖再向外打开一点”“腰背挺直,不要塌腰”,而不是一句冰冷的“动作不标准”。
- 第三是量化评分:把训练质量变成一个分数,让用户看到自己的进步,也让坚持变得有反馈。
我们做了一个小范围的用户测试,把一次深蹲训练从“无反馈跟练”换成“AI纠正+评分”之后,用户单次训练的动作合格率提升了大约三成,连续打卡意愿也明显增强。所以说,这套闭环不是锦上添花,而是决定产品能不能留住用户的核心。
1.2 技术选型的核心取舍
确定了产品目标,接下来就是技术路线。整个系统最关键的模块是姿态估计,也就是从摄像头画面里提取人体骨架关键点。这个环节选型直接决定了后续所有逻辑的可行性。
主流方案我对比过几个,简单列个表:
| 方案 | 关键点数量 | 模型体积 | 单帧推理耗时(实测参考) | 主要特点 |
|---|---|---|---|---|
| MediaPipe Pose | 33 | 约5MB | 10-20ms | 轻量、跨平台、自带平滑 |
| MoveNet | 17 | 约6MB | 15-25ms | 面向运动场景优化 |
| OpenPose | 18/25 | 200MB以上 | 100ms以上 | 精度高,但重,不适合嵌入式 |
我们最终选择了MediaPipe Pose,理由有三点。第一,它输出33个关键点,覆盖脚趾、拇指、面部轮廓等细节位置,对判断膝内扣、骨盆前倾这类细粒度问题很有用;第二,模型足够轻,可以在本地边缘设备上跑实时推理,不用依赖云服务器;第三,它在视频模式下默认带时间平滑滤波,关键点抖动控制得比较好,这对后续角度计算很友好。
有了姿态估计方案,下一个问题是推理跑在哪。当时有两个选择:云端推理和本地边缘推理。云端方案听起来美好——可以上大模型、精度高,但实际一测就发现问题:一次推理往返网络要增加几十到几百毫秒延迟,动作纠正是实时的,晚半秒说出来的话用户动作都做完了,没有意义。再加上视频画面涉及用户隐私,上传云端会让不少家庭用户产生顾虑。所以我们把姿态估计和动作判定全部放在本地设备上完成,用的是一块具备NPU的ARM平台,整机成本可控,实时性也有保障。
关于实时性,我们给自己定了一个硬指标:从摄像头采集到画面反馈,端到端延迟控制在80毫秒以内。这个数字不是随便定的。根据用户体验研究,在动作指导场景下,反馈延迟超过100毫秒,用户就会明显感到“卡顿”和“不同步”。80毫秒看起来不难,但覆盖到整个处理管线——采集、缩放、推理、逻辑判断、渲染叠加、屏幕输出——每一环都要精打细算,后面实操部分我会展开讲。
2. 核心算法与AI Agent设计
2.1 从关键点到角度特征
姿态估计模型输出的是人体关键点的坐标,MediaPipe Pose会给出33个点的归一化坐标(x, y, z)和可见度。但直接拿坐标去比较动作,会踩一个大坑:屏幕里的人在画面中的大小、站位、身高比例都不一样,同样的动作,坐标数值完全不同。
所以我们的第一步,是把坐标转换成角度特征。以深蹲为例,我们关注大腿与小腿的夹角(膝关节角度)、躯干与垂直方向的夹角(髋关节屈曲角度)、膝盖与脚尖的方位关系等。这些角度特征天然不受拍摄距离和人体体型影响,可比性远好于原始坐标。
角度计算本身不复杂,就是向量点积和反余弦:
import numpy as np def calculate_angle(p1, p2, p3): """ p1, p2, p3分别是三个关键点的坐标数组,例如[0.5, 0.4] p2是夹角的顶点,常见取法:肩-肘-腕算肘角,髋-膝-踝算膝角 """ v1 = p1 - p2 v2 = p3 - p2 cos_angle = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2) + 1e-6) cos_angle = np.clip(cos_angle, -1.0, 1.0) angle = np.degrees(np.arccos(cos_angle)) return angle这里面的操作细节值得说两句。分母加一个1e-6是防止向量长度为零导致除零错误;np.clip则是把余弦值限制在[-1, 1]区间内,避免浮点误差让反余弦函数返回NaN。这些在单帧测试时不容易暴露,跑到连续视频流里就会频繁出现,属于必加的防护。
有了角度计算还不够,因为每个动作不是孤立的几个角度,而是一组角度随时间变化的序列。我们把这个序列叫做“动作特征轨迹”。比如标准深蹲,膝关节角度会经历“初始直立约170度—>下蹲过程逐渐减小到约90度—>站起过程再恢复到170度”的完整过程。这个轨迹才是动作识别的核心依据。
2.2 动作模板与匹配机制
AI Agent要判断“你的动作对不对”,首先得有一个“对”的参照物。这个参照物就是动作模板。我们最开始的版本是请一位专业教练录制标准动作,然后把视频逐帧跑姿态估计,提取出关键角度轨迹,存成模板库。
但这里有个很现实的问题:用户做动作的节奏、幅度和教练不一样,哪怕都已经到了深蹲的最低点,前端耗费的时间也不同。如果用逐帧对齐去比较,速度快慢稍微不同,就会导致极高的误报率。我们需要把两条轨迹在时间轴上对齐。
这里用的是动态时间规整(DTW)。它的原理很简单:允许两个序列中的点在时间上做非线性伸缩对齐,找到一条累积距离最小的路径。有点像是两个人说同一句话,一个语速快一个语速慢,DTW能自动把对应的字词匹配起来,而不是简单按时间戳硬对。
DTW的思路直观,但工程上要注意计算量。完整的DTW计算复杂度是O(N×M),N和M是两条序列的长度,如果动作要跑一分钟,序列长度上千,直接算会吃掉不少算力。我们用了一个折中方案:先把动作序列降采样到固定长度,比如每个动作采集120帧特征,再做分段粗粒度匹配,对比效率高很多,并没有明显损失准确率。
匹配完成之后,就进入阈值判断环节。单纯依赖一个全局阈值不可靠,因为不同动作、不同部位的容忍度差异很大。比如膝关节角度的判断,很多错误的动作其实角度差只有十度左右,阈值太宽会漏报,太窄又会把正确动作误判成错误。我们的做法是给每个动作配置一套分部位的阈值矩阵,例如深蹲的膝角误差允许正负8度,但脊柱前倾角一旦偏离超过5度就标记为高风险。阈值不是一次性拍脑袋定的,而是反复采集多人测试数据后迭代出来的初始版本,后续还会按用户体型做归一化微调。
2.3 AI Agent的纠正策略设计
纠正策略是整个AI Agent最考验产品功力的一环。技术上识别偏差容易,难的是决定“什么时候开口、说什么、说多快”。
我们一开始犯过一个大错误:把所有检测到的偏差一次性喊出来。结果用户反馈说“像被教练训斥,完全跟不上”。后来调整了策略,采用分级反馈原则:
- 严重风险错误优先:比如深蹲时膝盖内扣、腰椎弯曲,这是可能导致受伤的错误,必须立即提示,语音加大音量并打断当前播报。
- 次要偏差延后提示:比如膝盖超过脚尖两厘米这类问题,不打断用户当前动作,等动作完成之后再给出修正建议。
- 正确动作即时鼓励:动作做得好时,用简短的语音鼓励“很好,保持节奏”,增强训练的正向反馈。
这套分级逻辑背后,是设计思维的转变:AI Agent不是监考老师,而是一个在旁陪伴的教练。它的首要任务是帮助用户安全完成训练,而不是逐一揪错。
纠正信息的表述也不能太长。我们实测发现,语音播报超过六个字,用户就容易漏听后一半。所以每个纠正条目都经过压缩:“膝盖外展”“收紧核心”“腰背挺直”这类短语,比完整句子有效得多。
评分机制是另一个让用户“上瘾”的设计。我们的评分模型分三块:完成度、规范度和节奏一致性。基础分100分,动作做满深度且速度适中不扣分,出现偏差按严重程度扣分,最后再根据连续正确动作数给予额外加分。这个模型不追求精细到肌肉发力级别的分析,但胜在可解释——用户可以明确知道“我为什么被扣分”,而不会对分数产生不信任感。
3. 实操过程与核心环节实现
3.1 动作模板采集与标注流程
模板采集是整个项目里最容易被低估的工作。我给你一个具体流程参考。先约好教练,在不同的角度、距离下录制标准动作视频,每类动作至少录制5组,包含慢速版、常速版和快速版三种节奏。为什么需要不同节奏?因为用户训练时速度不会完全一致,模板库越多样,DTW匹配的抗形变能力越强。
录制完成后,处理流程是这样的:
- 对视频逐帧跑姿态估计,提取所有关键点的坐标序列。
- 手动标注动作阶段:开始帧、最低点、结束帧。这个标注看起来很笨,但后续训练和评估都依赖它,不能省。
- 对每个阶段内的关键点序列做角度化处理,生成角度轨迹。
- 对轨迹做平滑滤波,去除个别帧的毛刺,然后降采样到固定长度。
- 把处理结果存成模板库的JSON文件。
模板文件结构类似这样:
{ "action_name": "squat", "version": "1.0", "stage": ["standing", "descending", "bottom", "ascending"], "key_angles": [ {"name": "left_knee", "landmarks": ["LEFT_HIP", "LEFT_KNEE", "LEFT_ANKLE"]}, {"name": "right_knee", "landmarks": ["RIGHT_HIP", "RIGHT_KNEE", "RIGHT_ANKLE"]}, {"name": "hip_flexion", "landmarks": ["RIGHT_SHOULDER", "RIGHT_HIP", "RIGHT_KNEE"]} ], "thresholds": { "knee_varus_max": 8, "back_tilt_max": 5, "squat_depth_min": 90 }, "template_curve": [[170, 168, 165], [90, 95, 92], [95, 92, 90]] }这里的template_curve存的就是角度轨迹的关键节点,实际运行时还需要做插值和NTW对齐。这个JSON设计的好处是,新增一个动作或者调整阈值,不需要改代码,只改配置文件就行。整个项目动作库从最初的两个动作扩展到八个动作,全程没动过主程序逻辑。
3.2 实时推理管线与延迟优化
实时推理管线是技术的核心,我强烈建议把整条链路画成清晰的数据流图再动手写代码。我们的管线是这样的:
摄像头采集帧 —> 图像缩放与色彩空间转换 —> 姿态估计模型推理 —> 关键点提取 —> 角度特征计算 —> 动作匹配与规则判定 —> 反馈输出(画面叠加、语音播报、评分更新)
每一步都可能成为性能瓶颈。下面几个优化点我们实际做下来收益最明显。
第一,输入分辨率不用太高。姿态估计模型需要的有效信息远小于摄像头原始分辨率,把画面缩放到640x480或720p再喂给模型,推理速度能提升40%到60%,而关键点精度几乎不受影响。
第二,直接复用推理帧做渲染叠加,不要额外抓一帧去做画面显示,省一次图像转换的耗时。第三,姿态估计和动作逻辑判断拆到两个线程里:推理线程负责连续推理,逻辑线程处理角度计算、DTW匹配和评分更新,两者通过队列传递结果。这样即使某次推理慢了几毫秒,逻辑线程也不会阻塞,整体体验更顺滑。
抓一段核心代码流程,大家感受一下:
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) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) while cap.isOpened(): success, frame = cap.read() if not success: break # 转RGB并缩小,提升推理速度 frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) frame_small = cv2.resize(frame_rgb, (640, 480)) results = pose.process(frame_small) if results.pose_landmarks: lm = results.pose_landmarks.landmark # 提取关键角度 left_knee = calculate_angle( np.array([lm[mp_pose.PoseLandmark.LEFT_HIP.value].x, lm[mp_pose.PoseLandmark.LEFT_HIP.value].y]), np.array([lm[mp_pose.PoseLandmark.LEFT_KNEE.value].x, lm[mp_pose.PoseLandmark.LEFT_KNEE.value].y]), np.array([lm[mp_pose.PoseLandmark.LEFT_ANKLE.value].x, lm[mp_pose.PoseLandmark.LEFT_ANKLE.value].y]) ) # 此处接入动作判定、DTW匹配、评分更新等逻辑 # ... # 画面叠加与显示 cv2.imshow("Smart Fitness Mirror", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()这段代码本身很简单,但工程落地时有很多容易忽略的细节。比如model_complexity这个参数,0是轻量版,1是标准版,2是高精度版。我们在边缘设备上实测,model_complexity=1时精度和速度平衡最好,0会漏检一些侧面角度,2又会把延迟拉高到不可接受。
再比如光照问题。摄像头画面过暗或过曝都会让姿态估计质量断崖式下跌。我们在产品外壳上加了一圈补光灯,但补光策略也有讲究——不是越亮越好,而是保证人体区域亮度均匀,避免正脸区域过曝导致面部关键点丢失,在代码里加了一段自动曝光补偿,通过读取画面亮度均值动态调整增益,效果比固定亮度好不少。
3.3 评分与反馈闭环的完整实现
评分和反馈是用户直接感知的上层功能,我的实现方案是把它们做成分层模块,方便独立调试。
评分模块接收动作判定结果,维护一条本次训练的“事件流水”。每帧检测到的偏差事件会写入流水,评分器从流水里汇总:总动作数、正确动作数、各部位偏差次数。最后通过一个加权公式得到综合分。这里的关键是权重的设计,我给了风险性偏差(比如膝内扣)更高的扣分权重,而轻微幅度不足只做单项扣分,不重伤总体分数。
反馈模块则分三层输出:画面层、语音层、数据层。
画面层是在实时视频上做骨骼叠加,并把当前需要关注的关节用高亮圆圈标出来,同时显示该关节的实时角度。这样用户低头看一眼屏幕就知道自己哪里不对。语音层用TTS播报纠正口令,优先级队列前面说过,这里不再重复。数据层则是把每次训练的分项成绩记录下来,形成趋势图表,让用户看到自己的进步曲线。
有一个小细节很有用:当检测到用户连续三次做同一个动作都犯同样的错误,系统会在这一组动作结束时给出专项提示,比如“你的深蹲连续三次出现膝盖内扣问题,下一组请刻意把膝盖朝脚尖方向打开”。这个“连续犯错追踪”机制虽然没有复杂的模型,却显著提升了用户对AI专业度的感知。
4. 常见问题与排查技巧实录
4.1 高频问题排查速查表
整个开发过程中我们记录了一堆问题,我按出现频率整理了下面这个排查表,应该对做类似项目的人有帮助。
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 关键点抖动导致角度不稳定 | 单帧误检、光线波动、快速运动模糊 | 开启模型自带的时间平滑;对角度序列做滑动窗口均值;降低曝光增益波动 |
| 动作明明标准却被扣分 | 单一模板不匹配用户的体型/节奏 | 增加多节奏模板;按身高比例归一化关键点;适当放宽阈值 |
| 真正出错时却不提示 | 阈值过严、模型置信度抖动 | 从较宽松阈值开始,用回放标注结果迭代收紧;采用连续3帧判定方案防抖 |
| 端到端延迟超标 | 推理耗时高、渲染线程阻塞 | 降低输入分辨率;推理线程与逻辑线程分离;关闭不必要的显示特效 |
| 光照变化导致检测率骤降 | 补光不均匀、背景复杂 | 增加均匀补光;固定拍摄距离区间;提示用户清空身后杂物 |
| 两人同时入镜 | 模型在目标间跳变 | 增加单人目标锁定框;在UI中提示单人使用场景 |
| 设备过热降频后变卡 | 边缘设备持续高负载 | 控制帧率上限;加入温度监测和动态降分辨率策略 |
4.2 实战避坑心得与扩展方向
做完这个项目,我最大的一个体会是:不要一上来就追求覆盖几十个动作、搞复杂的端到端神经网络。走通“一个动作—一整套反馈闭环”的最小路径,才是最高效的做法。我们的第一个可运行demo只支持深蹲一个动作,但用户已经能体感到“被纠正”和“被评分”的完整流程。产品验证成立之后,再横向扩展动作库,只是时间和数据积累的问题。
另一个心得是关于“场景约束”的。很多做视觉产品的团队会把算法通用性摆在第一位,恨不得任何角度、任何光照、任何距离下都准确。但对智能健身镜这种固定场景的产品,约束反而是朋友。我们把用户站位的活动范围限定在镜前1米到2.5米之间,正面朝向设备,背景相对干净,这个限定让姿态估计的准确率提升非常明显,而且大幅简化了判定逻辑。
还有一点容易被忽略:语音播报的节奏。即使我们做了优先级队列,TTS引擎本身的合成延迟也会影响体验。后来我们把常用的十几个纠正口令全部预合成并缓存成音频文件,播放时直接读音频文件而不是实时合成,延迟直接降到了可以忽略的程度。如果有条件,建议在方案里直接内置这种缓存机制。
再往后扩展,可以做的东西很多:多动作组合成训练计划、基于动作质量给用户推荐下一组训练强度和动作类型、把评分数据接入更长时间尺度的体能评估模型。我们目前正在尝试的是把识别结果与训练计划联动——当系统发现用户某个动作连续犯同一类错误时,自动在下一轮训练中降低该动作的组数和负重,避免受伤风险。这个方向其实比单纯追求算法精度更有产品价值,毕竟用户真正关心的不是模型多厉害,而是持续、安全、有效地练下去。
以我个人这些操作经验来说,与其把精力花在调一个高精度模型上,不如多花时间把“动作模板—判定逻辑—反馈体验”这个闭环打磨顺,因为后者才是用户每天都能感受到的东西。这个项目让我最满意的一点,也正是当一位完全没接触过健身镜的用户第一次跟着AI完成训练后,主动说了一句“它真的在纠正我”的时候——那一刻,所有踩过的坑都值了。