AI 驱动的 Brunnstrom 分期康复 · 技术文档
浏览器调用 getUserMedia 打开摄像头。每 100ms 抽一帧 JPEG,Base64 编码后 POST 到 /api/upload_frame。服务器跑 MediaPipe Pose + Hands 两个模型,同时检测身体 33 个关键点和双手各 21 个关键点。
进去之后先做 ROM 测试,分三步:肩外展(手臂从侧面抬起来)、肩前屈(手臂往前抬)、肘屈伸(手肘弯曲伸直)。每步系统会记录你的最大活动角度,三步做完算总分,自动定 Brunnstrom 分期。
Brunnstrom 是康复医学里经典的六期分期法。系统根据 ROM 三项平均角度来推算:
| 分期 | 特点 | 平均角度范围 |
|---|---|---|
| I 期(软瘫期) | 完全没力,动不了 | < 30° |
| II 期(痉挛初现) | 开始有点痉挛,勉强能动 | 30°–60° |
| III 期(痉挛高峰) | 痉挛最严重,能做基本协同动作 | 60°–100° |
| IV 期(开始恢复) | 痉挛减轻,能做分离动作 | 100°–140° |
| V 期(分离运动) | 各关节能独立活动 | 140°–170° |
| VI 期(接近正常) | 协调性基本恢复 | > 170° |
定完分期后,系统的训练难度会自动适配。比如 ROM 测出来只恢复了 40%,那所有动作的目标角度就打四折——不会一上来就让你抬到 160°,而是从 64° 开始慢慢加。一句话:你能做多少就练多少,不硬来。
每帧进来,系统做三件事:
① 算关节角度:肩外展、肩前屈、肩内收、肘屈伸、前臂旋前旋后、躯干倾斜——6 类角度全算出来。
② 跟目标对比:每个关节跟预设目标比对,Δ ≤ 容差 → 绿灯,容差 1~2 倍 → 黄灯,超过 2 倍容差 → 红灯。
③ DTW 动作质量评分:不是只看单帧角度,而是把整段动作的时序序列跟参考模板做动态时间规整(DTW),算一个 0-100 的相似度分。你做得快一点慢一点没关系,DTW 会自动对齐时间轴。
④ 语音播报:动作到位了说"很好",角度偏了说"注意手肘角度",自带 3 秒冷却避免话痨。
每次训练自动记到 SQLite:哪一天、练了哪个动作、做了几次、每次多少分、花了多少秒。点"周报"按钮就能看到过去 7 天的汇总——训练天数、总次数、平均分、各动作明细,还有最近的 ROM 数据。
| 项目 | 内容 |
|---|---|
| 功能 | AI 驱动的脑卒中居家手指/上肢康复训练系统 |
| 适用场景 | Brunnstrom I–VI 期脑卒中患者在家庭环境下的上肢康复训练 |
| 开发语言 | Python 3.10+ |
| Web 框架 | Flask(单线程 + 线程锁保证帧同步) |
| 人体检测 | MediaPipe Pose 0.10(33 关键点)+ MediaPipe Hands 0.10(21 关键点) |
| 核心依赖 | OpenCV、NumPy、Pillow(中文渲染)、pyttsx3(语音) |
| 数据库 | SQLite(本地文件,3 张表:ROM 评估 / 训练记录 / 逐次角度) |
| 动作对比算法 | DTW 动态时间规整 + 指数衰减归一化 |
| 评分策略 | 红黄绿三色交通灯 + 0-100 连续打分 |
| 部署方式 | systemd 服务化 + nginx 反代 + HTTPS(Let's Encrypt) |
不走传统的 OpenCV 本地摄像头循环,而是设计成 set_external_frame() → process_frame() → get_display_data() 三步调用。浏览器端 setInterval 定时 POST JPEG,服务器解码后塞给引擎处理。这样浏览器和服务器完全解耦,浏览器只管采帧和显示,服务器只管算。
Flask 默认多线程处理请求,但引擎只有一个实例。用 threading.Lock 保护 _latest_jpeg 和 _latest_data,API 读取的时候不会读到半帧数据。MJPEG 流用 threading.Event 做帧就绪信号,避免空转烧 CPU。
身体用 Pose(model_complexity=1,平衡精度和速度),手部用 Hands(model_complexity=0,轻量模型)。双手数据单独跑,算出前臂旋前/旋后角度(拇指 vs 小指的垂直偏移 → arctan2 映射到 0°–180°)。这个设计是因为常规 Pose 模型检测不到手掌朝向,必须加 Hands 才能判断"手心朝上还是朝下"。
不是简单的"对了/错了"二分。每个关节角度跟目标的偏差分三档:
| 偏差 | 颜色 | 计分 |
|---|---|---|
| ≤ 容差 | 绿灯 | 100 分 |
| 容差 ~ 2×容差 | 黄灯 | 50–99 分(线性衰减) |
| > 2×容差 | 红灯 | 0–50 分(线性衰减) |
总分有两种模式:mean(所有关节取平均,适合标准动作)和 max(取最高分的关节,适合交替动作——比如一只手弯一只手直,只看做到位的那只)。
有些动作需要"保持 X 秒不动"(比如前臂旋前旋后要保持 2 秒)。用 hold_seconds 字段控制:评分 ≥ 85 分才开始计时,中间掉下来就清零重来。屏幕上显示倒计时,到 0 算完成一次。
ROM 测完后,总角度 ÷ 理论最大值(300° = 三关节各 100° 的理想值),算出恢复百分比。所有训练动作的目标角度乘以这个百分比。比如你肩外展只能到 45°,那训练目标不是 90°,而是 45° × 0.5 = 22.5°,不让你硬拉。
OpenCV 的 putText 不支持中文。用 Pillow 画中文再贴回 OpenCV 帧上。字体路径做了 Windows / Linux / macOS 三级 fallback:微软雅黑 → 文泉驿 → Noto Sans CJK → PingFang → 兜底遍历。字号缓存避免每次都重新加载字体文件。
不是只看当前帧,而是录整段动作序列(最多 300 帧),跟理想的正弦波形参考模板做 DTW 对齐。公式:score = 100 × exp(-DTW距离 / (序列长度 × 特征维度) × 10)。指数衰减让分数对小幅偏差不敏感,大幅偏差快速惩罚。
用 pyttsx3 离线 TTS(不依赖网络),语速降到 160(老年人友好)。每条消息 3 秒内不重复播,避免"注意角度注意角度注意角度"洗脑循环。
| 参数 | 默认值 | 说明 |
|---|---|---|
| MediaPipe 检测置信度 | 0.7 | Pose 模型,低于此值认为没检测到人 |
| MediaPipe 追踪置信度 | 0.7 | 帧间追踪稳定性 |
| Hands 检测置信度 | 0.6 | 手部模型(比 Pose 低一点,因为有双手同时检测) |
| Pose 模型复杂度 | 1 | 0=轻量(快但糙),1=标准,2=重(准但慢) |
| Hands 模型复杂度 | 0 | 轻量模型,手部关键点少但够用 |
| 关节角度容差 | 20°–30° | 每个动作单独设置,越精细的动作容差越小 |
| 评分阈值(绿灯) | ≥ 85 分 | 动作合格线 |
| 评分阈值(黄灯) | 60–84 分 | 需要改进 |
| 保持计时触发线 | ≥ 85 分 | 静态动作开始计时的分数线 |
| 语音冷却 | 3 秒 | 相同提示不重复播 |
| TTS 语速 | 160 | pyttsx3 rate 参数,正常语速 200 |
| MJPEG 帧间隔 | 33ms(~30fps) | video_feed 流输出间隔 |
| 周报统计范围 | 7 天 | SQL 查询的 WHERE 条件 |
| 动作 | Brunnstrom | 监测关节 | 评分模式 | 特别说明 |
|---|---|---|---|---|
| 双侧上肢交替上举 | IV 期 | 肩前屈 160°、肘关节 160° | mean | 基础动作,两手交替往上抬 |
| 双侧上肢触摸对肩上举 | V 期 | 肩前屈 160°(上举)、肩内收 50°(够对肩) | max | 先摸对侧肩膀,再往上举直。两个阶段取最高分 |
| 双侧肘关节交替屈伸 | III 期 | 肘关节 30° | max + min聚合 | 一只手弯到 30° 同时另一只保持伸直。取弯的那只计分 |
| 前臂旋前旋后 | V 期 | 肘关节 90°(保持)、旋前角度 160° | max | 曲肘 90° 固定,手心翻上翻下。需保持 2 秒 |
| 双手交替翻掌拍臂 | V 期 | 肩内收 45°、旋前角度 160° | mean | 一手心朝上一手心朝下,交替翻掌。练协调性 |
-- ROM 关节活动度评估记录 CREATE TABLE rom_assessments ( id INTEGER PRIMARY KEY, timestamp TEXT, -- ISO 时间戳 shoulder_abduction REAL, -- 肩外展角度 shoulder_flexion REAL, -- 肩前屈角度 elbow_flexion REAL, -- 肘屈曲角度 total_score REAL, -- 三项总分 brunnstrom_stage INTEGER -- 分期 1-6 ); -- 训练记录 CREATE TABLE training_sessions ( id INTEGER PRIMARY KEY, timestamp TEXT, exercise_name TEXT, -- 英文 key exercise_name_cn TEXT, -- 中文名 rep_count INTEGER, -- 完成次数 avg_score REAL, -- 平均分 duration_seconds INTEGER -- 训练时长 ); -- 每次动作的关节角度快照(JSON) CREATE TABLE session_angles ( id INTEGER PRIMARY KEY, session_id INTEGER, -- 关联 training_sessions rep_index INTEGER, joint_angles TEXT, -- JSON: {"shoulder_flexion": 145, ...} score REAL );
| 路由 | 方法 | 用途 |
|---|---|---|
/api/upload_frame | POST | 浏览器上传一帧 JPEG → 返回处理后的帧 (Base64) + 状态数据 |
/api/status | GET | 拿当前训练状态(模式、分数、关节角度、反馈文字) |
/api/exercises | GET | 返回 5 个训练动作的 key 和中文名列表 |
/api/start_rom | POST | 开始 ROM 关节活动度评估 |
/api/next_rom | POST | ROM 评估进入下一阶段(三步依次推进) |
/api/start_exercise | POST | 开始指定训练动作(body 传 key) |
/api/toggle_record | POST | 开始/停止单次动作录制(用于 DTW 对比) |
/api/return_menu | POST | 返回主菜单 |
/api/report | GET | 获取周报文本 |
/api/ref_video/<key> | GET | 获取训练动作对应的参考视频路径 |
/video_feed | GET | MJPEG 实时流(处理后画面) |
| 项目 | 详情 |
|---|---|
| 服务器 | Ubuntu 22.04, 4 核 4GB |
| 域名 | cdqtech.online |
| 访问地址 | https://cdqtech.online/stroke-rehab/ |
| 后端端口 | 127.0.0.1:5003 |
| 进程管理 | systemd(限制 127.0.0.1 监听,外网不可直连) |
| HTTPS | nginx 443 反代 + Let's Encrypt 自动续期 |
| 代码路径 | /data/stroke-rehab/ |
cdqtech.online · 中风患者居家康复系统技术文档