万字拆解 NeckFix:AI 脖子前倾检测的算法原理与工程实现

万字拆解 NeckFix:AI 脖子前倾检测的算法原理与工程实现

前言

上一篇文章 介绍了 NeckFix 的功能和使用方法,这篇深入聊聊背后的技术实现。


一、整体架构

核心设计原则:所有姿态数据在浏览器本地处理,摄像头画面永不离开用户设备。只有登录后的 AI 建议请求和训练记录同步才会访问远端服务。


二、姿态检测的技术选型

为什么用 8 个关键点而不是 33 个

MediaPipe Pose 输出全身 33 个 3D 关键点,我们实际只用了 8 个:

关键点 索引 用途
鼻子 0 Z 轴前倾深度 + Y 轴头部位置
左眼 / 右眼 2 / 5 头部侧倾角度(双眼连线 vs 水平)
左耳 / 右耳 7 / 8 辅助 Y 轴指标
左肩 / 右肩 11 / 12 躯干参考系 + 耸肩检测

其余 25 个点为什么不用的核心原因:Z 轴局限

Z 轴陷阱:为什么 CVA 金标准在单摄像头上行不通

颅椎角(Craniovertebral Angle,CVA)是临床评估脖子前倾的金标准。测量方式是从侧面拍照,计算耳屏(tragus)到 C7椎骨的连线与水平面的夹角。正常 ≥ 50°,低于 35° 为重度前倾。

但这个方案依赖侧面视角。当用户正对摄像头时,MediaPipe 的 Z 轴(景深)是从单张 2D图像估算的,噪声很大。我们的实测数据:

耳朵 Z 差值(耳-肩深度差):

头后缩时: 0.2446

头前伸时: 0.2439

变化幅度: 0.0007(几乎不变!)

耳朵在头部两侧,不随前伸显著移动,其 Z 值变化完全淹没在 MediaPipe

的估算噪声里。从正面单摄像头做 CVA 是不可靠的------这是反复试错后最重要的教训。

最终方案:鼻子 Z + 头部 Y 联合打分

鼻子的 Z 信号比耳朵强两个数量级:

鼻子 Z 差值(鼻-肩深度差):

头后缩时: 0.87

极限前伸: 0.96

变化幅度: 0.09(清晰可辨)

为什么鼻子 Z 更好?鼻子是面部最突出的点,前伸时它的 Z 坐标变化最显著,MediaPipe对鼻子的深度估算也最准确(面部特征密集)。

但 Z 轴还有一个问题:依赖摄像头距离。同一姿势,靠近屏幕和远离屏幕,Z差值会漂移。

我们的解决方法是 「距离提示 + 窄阈值」

  1. 用肩宽占画面比例估算距离(40cm 真实肩宽 / 摄像头 FoV → 画面占比 ≈ 25-65% = 50cm)
  2. UI 提示用户调整到标准距离
  3. 在标准距离下,直接将鼻子 Z 差值线性映射到 0-100 分(各阶段分数由本人实测得出,人力有限,同志们若想更加精确测定,可自行调试)
typescript 复制代码
// 阈值来自真人实测数据(50cm 标准距离)
// 后缩: noseZDiff ≈ 0.87, 极限前伸: noseZDiff ≈ 0.96
const fhpZScore = clamp(((noseZDiff - 0.84) / 0.14) * 100, 0, 100);

// 头部 Y 比:鼻子到肩膀的垂直距离 / 肩宽
// 后缩: ≈ 0.79, 前伸: ≈ 0.72
const headProtrusion = clamp(((0.82 - headYRatio) / 0.17) * 100, 0, 100);

// 综合:50% Z 深度 + 50% Y 垂直 = 兼顾前移 + 下沉
const forwardHeadAngle = clamp(fhpZScore * 0.50 + headProtrusion * 0.50, 0, 100);

三、四项检测指标

1. 脖子前倾(权重 45%)

基于鼻子 Z 深度(50%)+ 头部 Y

比(50%)联合打分。兼顾了头部的水平前移和垂直下沉两个维度。

2. 耸肩程度(权重 15%)

abs(leftShoulder.y - rightShoulder.y) --- 双肩高度差。办公族右手握鼠标常见右肩上抬。

3. 头部侧倾(权重 40%)

双眼连线与水平面的夹角。测试数据:

头后缩: -6.86°(左眼略低)

头前伸: -1.09°(基本水平)

|倾斜角| < 2° 为 0 分,> 10° 为 100 分。侧倾长期不纠正会导致颈椎侧弯。

综合分级

综合分 = 100 - (前倾 × 0.45 + 耸肩 × 0.15 + 侧倾 × 0.40)

≥ 75 分 → 良好

55-74 分 → 轻度

35-54 分 → 中度

< 35 分 → 重度

四、训练模块:呼吸同步与动作演示

为什么是 4 秒呼吸周期

吸气(4s) → 屏息(4s) → 呼气(4s) → 循环。4

秒来自物理治疗指南------颈椎康复要求慢速可控,足够完成一个动作相位并稳定呼吸。

动作时长设计

所有动作时长都是 12 的倍数(36s /

48s),确保每个动作包含完整呼吸周期,不会在中间截断;

动作去重

四个核心动作(下巴后缩、颈部前屈、侧屈、肩胛收缩)各三个等级,训练计划只展示每个动作的最高难度版本:

typescript 复制代码
const dedupedMap = new Map<string, Exercise>();
for (const ex of planExercises) {
  const base = ex.id.replace(/-l[123]$/, "");  // 去掉后缀
  dedupedMap.set(base, ex);                     // 后面的覆盖前面的
}
// 结果:4 个动作 × 各取最高等级

语音策略:少即是多

第一版每个动作阶段都播报,结果语音还没说完动作已切换到下一阶段。Web Speech API 的 speak()是异步的,无法精确控制时间轴。最终只保留三处语音:

  • 训练开始:播报动作名 + "开始"
  • 动作间切换:仅滴滴声(Web Audio API oscillator beep),不语音
  • 训练结束:播报"训练完成"

五、语音引导系统

5.1 Web Speech API 的限制

浏览器内置的 TTS

引擎,优点是零成本、零延迟、离线可用,缺点是语音质量取决于系统和浏览器。

5.2 语音优选策略

typescript 复制代码
const prefer = [
  "tingting",    // macOS --- Apple 原生中文女声,最好
  "huihui",      // Windows --- Microsoft 中文女声
  "yaoyao",      // Windows 11 --- 新版女声
  "xiaoxiao",    // Edge/Win11 --- 神经网络语音,接近真人
  "yunjian",     // Edge/Win11 --- 神经网络男声
  // ... 共 15 个候选项
];

遍历优先级列表,找到第一个可用的中文语音。Windows 11 + Edge 浏览器能选到神经网络语音Xiaoxiao,质量最高。Chrome 在 Windows 上只能用旧版语音,质量次之。

5.3 语音播报时机

经过多次迭代,最终方案非常克制:

  • 训练开始:播报动作名 + 第一条指令 + "开始"
  • 训练结束:播报"训练完成"
  • 动作间:只发滴滴声(Web Audio API beep),不语音
  • 预览:手动点「语音讲解」才逐条播报

这样避免了语音跟不上动作变化的问题,也让用户不会被频繁打断。


六、AI 建议:从数据到自然语言

6.1 Prompt 工程

const prompt = `你是一位资深的物理治疗师和姿势矫正专家。

根据以下用户的姿态检测数据,请给出3-5条简洁实用的个性化纠正建议。

使用中文回答,语气温暖而专业,每条建议不超过40个字。

数据(过去60秒内的平均值):

  • 脖子前倾程度: ${forwardHeadAngle}%
  • 头部前伸程度: ${headProtrusion}%
  • 耸肩程度: ${shoulderShrug}%
  • 身体左右倾斜: ${bodyTilt}%
  • 综合评分: ${overallScore}/100

请以康复治疗师的口吻给出建议,格式为编号列表。`;

关键设计:

  1. 角色设定:"资深物理治疗师"------给模型一个专业身份,输出质量明显高于直接问
  2. 字符限制:"每条不超过 40 字"------避免废话,也控制 token 消耗
  3. 数据注入:四项指标 + 综合分,让模型有据可依

6.2 API 配额保护

前端层:未登录 → 显示「登录后获取 AI 建议」按钮 → 跳转登录页

后端层:API route 校验 Supabase session → 无 session 返回 401

两层防护确保只有注册用户才能调用 DeepSeek,避免被刷量。


七、经验教训

8.1 Z 轴校准没有捷径

起初想用文献里的阈值,发现每个人离摄像头的距离、肩膀宽度都不同,固定阈值完全行不通。最后是靠真人反复测试,记录极端位姿的 Z 值,线性映射到 0-100 分。如果你的项目也用到了Z 轴数据,建议直接做真人标定。

8.2 语音播报越少越好

第一个版本每个动作阶段都播报一次,结果语音还没说完,动作已经切换到下一个阶段了。Web Speech API 的 speak() 是异步的,没法像音视频那样精确控制时间轴。最后砍掉所有中途语音,只保留开始和结束,体验反而更好。

相关推荐
jaychouchannel1 小时前
Python 常用排序算法详解
算法
数智工坊1 小时前
【Inner Monologue论文阅读】: 首次将大语言模型嵌入机器人控制闭环,实现自我反思和动态行为调整
论文阅读·人工智能·算法·语言模型·机器人·无人机
AI帮小忙1 小时前
Debian/Ubuntu 系linux操作系统Kali Linux 2026 里安装 Hermes Agent
人工智能
乌恩大侠1 小时前
基站正在成为 AI 计算节点:NVIDIA Aerial 推动 RAN 架构重构
人工智能·重构·架构
钓了猫的鱼儿1 小时前
基于深度学习+AI的水下目标目标检测与预警系统(Python源码+数据集+UI可视化
人工智能·深度学习·智能手机
Ting-yu1 小时前
Spring AI Alibaba零基础速成(6) ---- 向量化
数据库·人工智能
YUDAMENGNIUBI2 小时前
day29_NLP概念与文本预处理
人工智能·自然语言处理
南屹川2 小时前
【安全】代码安全审计与防护实践
人工智能
深开鸿2 小时前
福田区全栈式鸿蒙AI数智机关入选全市首批OR示范应用项目,深开鸿筑牢政务安全底座
人工智能·openharmony·政务