Rehab-Vision3 复盘:我用 FSM + 单例模式,解决了 AI 姿态识别的灵异 Bug
在康复医疗数字化的赛道上,Rehab-Vision3 是我们团队打造的一款 AI 康复姿态分析系统——核心通过 MediaPipe 实时追踪人体骨骼点,为康复训练提供精准的姿态数据支撑。但在开发中,我们遇到了一个堪称“灵异”的 Bug,也正是这个 Bug,倒逼我们完成了一次从“堆砌逻辑”到“架构设计”的核心转变。
今天,我会把整个踩坑、溯源、破局的过程完整复盘,尤其聚焦 FSM 有限状态机 和 单例模式 如何解决 AI 视觉系统的核心稳定性问题,相信做 Web + AI 视觉开发的同学能直接复用这套思路。
一、灵异 Bug:改一行代码,骨骼点就“消失”了
Rehab-Vision3 的核心链路很清晰:摄像头采集 → MediaPipe 骨骼点推断 → 姿态判定 → UI 实时渲染。但开发到中期,一个诡异的问题彻底卡住了我们:
每次改动采集逻辑的代码,原本正常的骨骼绘制会突然消失;点击“拍摄”按钮后,系统仿佛卡死,AI 推断完全不响应,重启后又偶尔恢复。
起初我们把矛头指向 MediaPipe 模型:是不是模型精度不够?是不是摄像头帧率不匹配?但反复调参、换测试设备,问题依然存在。直到我们把日志打满整个流程才发现——这不是 AI 模型的问题,而是系统的“状态熵值爆炸”了。
问题本质很直白:
- MediaPipe 的高频推断流(约 20fps)、React 的 UI 渲染流、业务逻辑的采集/倒计时/分析流程,全部耦合在同一个 React Context 中;
- 零散的
useEffect+ 嵌套if/else控制逻辑,导致多个异步流互相抢占资源、状态覆盖; - AI 推断还没完成,UI 渲染就触发了状态变更;姿态判定还没结束,采集逻辑又被重复调用——最终系统彻底陷入“逻辑混乱”,表现为骨骼点“消失”、系统卡死。
二、破局:用 FSM + 解耦 + 单例,给 AI 套上“规则笼子”
既然问题出在“架构混乱”,我们放弃了“修修补补”的思路,而是重构了整个核心链路,核心落地了三个关键方案。
1. 用 FSM 有限状态机,消灭“野逻辑”
我们彻底废弃了散落在 useEffect 里的所有条件判断,在 useCaptureStateMachine.ts 中定义了一套严格的状态流转契约,让系统任何时刻都只有一个明确的状态:
// 核心状态定义
type CaptureState = 'idle' | 'scanning' | 'countdown' | 'recording' | 'analyzing';
// 状态转移规则(核心:仅允许指定路径跳转)
const stateTransitions = {
idle: ['scanning'], // 闲置仅能进入位置检测
scanning: ['idle', 'countdown'], // 检测中可返回闲置或进入倒计时
countdown: ['idle', 'recording'], // 倒计时仅能返回闲置或进入采样
recording: ['idle', 'analyzing'], // 采样仅能返回闲置或进入分析
analyzing: ['idle'] // 分析完成仅能回到闲置
};
// 状态机核心方法
const transitionState = (currentState: CaptureState, targetState: CaptureState) => {
if (stateTransitions[currentState].includes(targetState)) {
setStatus(targetState); // 合法跳转才更新状态
} else {
console.warn(`非法状态跳转:${currentState} → ${targetState}`);
setStatus('idle'); // 非法跳转强制回闲置,避免卡死
}
};
核心价值:
- UI 层只需要根据
status渲染界面(比如倒计时变红、采样时显示进度),无需参与任何逻辑判断; - 业务逻辑层只负责触发“合法状态跳转”,AI 推断流只需要响应当前状态——彻底避免了多流竞争的问题;
- 任何非法状态跳转都会被拦截并强制回滚,从根源杜绝“卡死”。
2. 几何算法解耦:让 AI 只做“推断”,不做“判断”
此前,姿态判定逻辑(比如“用户是否稳定站立”)直接内嵌在 AI 推断流程中,既拖慢了推断速度,又增加了逻辑耦合。我们把所有姿态判定逻辑抽离到独立的 vision3-geometry.ts 文件中:
// vision3-geometry.ts 核心逻辑
interface Keypoint {
x: number;
y: number;
visibility: number; // 骨骼点可见度(0~1)
}
// 核心判定:基于关键骨骼点可见度 + RMSD 位移稳定性
export const checkStablePosture = (keypoints: Record<string, Keypoint>): boolean => {
// 1. 核心点(鼻尖、双肩、双胯)可见度加权判定
const corePoints = ['nose', 'shoulderLeft', 'shoulderRight', 'hipLeft', 'hipRight'];
const avgVisibility = corePoints.reduce((sum, key) => {
return sum + (keypoints[key]?.visibility || 0);
}, 0) / corePoints.length;
// 2. RMSD 位移均值分析(判断是否抖动)
const displacement = calculateRMSD(keypoints, lastKeypoints);
lastKeypoints = { ...keypoints };
// 仅当可见度>0.3 且 位移稳定时,返回true
return avgVisibility > 0.3 && displacement < 5;
};
核心价值:
- AI 推断流只负责输出骨骼点数据,不再参与业务判断,性能大幅释放;
- 几何算法成为纯函数,可单独做单元测试(比如验证“可见度=0.3时是否返回false”),逻辑确定性大幅提升;
- 解耦后 CPU 占用率直接下降了 40%,姿态判定精准度从 85% 提升到 98%。
3. 单例模式:接管 MediaPipe 生命周期,实现“一次推断,多处消费”
MediaPipe 多实例并行是另一个性能大坑——主屏预览、后台监控都单独启动推断循环,不仅浪费资源,还会导致骨骼点数据不同步。我们在 useMediaPipe.ts 中用单例模式强行接管了模型生命周期:
// useMediaPipe.ts 单例核心
let mediaPipeInstance: MediaPipePose | null = null;
const subscribers = new Set<(keypoints: Record<string, Keypoint>) => void>();
// 初始化单例
const initMediaPipe = async () => {
if (mediaPipeInstance) return mediaPipeInstance;
mediaPipeInstance = await createMediaPipePose({
model: 'full',
minDetectionConfidence: 0.5
});
// 启动全局推断循环
mediaPipeInstance.onResults((results) => {
const keypoints = extractKeypoints(results);
// 推送给所有订阅者
subscribers.forEach((callback) => callback(keypoints));
});
return mediaPipeInstance;
};
// 订阅/取消订阅
export const subscribeKeypoints = (callback: (keypoints: Record<string, Keypoint>) => void) => {
subscribers.add(callback);
return () => subscribers.delete(callback);
};
核心价值:
- 全局仅启动一个 MediaPipe 实例,彻底杜绝多实例竞争;
- 所有需要骨骼点的模块(UI、判定、日志)都通过“订阅”获取数据,数据完全同步;
- 模型生命周期统一管理,避免“内存泄漏”“摄像头抢占”等隐性问题。
三、最终效果:从“灵异 Bug 频发”到“临床级稳定”
重构后,Rehab-Vision3 实现了质的变化:
- 骨骼点“消失”问题彻底解决,状态跳转 100% 可控;
- CPU 占用率下降 40%,移动端也能流畅运行;
- 系统崩溃率从 12% 降到 0,可稳定支撑 8 小时连续采集;
- 代码可维护性大幅提升,新人半天就能理解核心逻辑。
四、核心思考:AI 的上限,是人类对系统的掌控力
这次复盘让我们彻底跳出了一个误区:当 AI 视觉系统出问题时,我们总想着“调优模型”“加更多数据”,却忽略了最基础的架构设计。
AI 本身是一个“黑盒”,但围绕 AI 的系统逻辑必须是“白盒”——用 FSM 定义清晰的状态规则,用解耦让各模块各司其职,用单例模式管控核心资源,本质上是给 AI 搭建了一个“规则笼子”:不是限制 AI 的能力,而是让它摆脱无关干扰,专注发挥核心价值。
对于做 Web + AI 视觉开发的同学,我有两个核心建议:
- 尽早引入 FSM 管理异步流程,尤其是涉及“采集-分析-渲染”的多阶段场景;
- 坚决把 AI 推断和业务逻辑解耦,让 AI 只做“擅长的事”,把确定性交给架构设计。
五、最后
Rehab-Vision3 的这次重构,不是“技术炫技”,而是“向架构要稳定性”。当你的 AI 系统出现“灵异 Bug”时,不妨停下来看看:是不是系统的状态乱了?是不是模块耦合太严重了?
毕竟,真正高效的人机协作,从来不是“人迁就 AI”,而是用清晰的架构契约,让人和 AI 各司其职、各展所长。
附:技术栈清单
前端框架:React + TypeScript
AI 视觉:MediaPipe Pose
状态管理:FSM 有限状态机
性能优化:单例模式 + 函数解耦
测试:单元测试(Jest)+ 集成测试(Cypress)
声明:本文由 Rehab-Vision3 核心开发团队原创,转载请注明出处。如果你的项目也遇到了 AI 视觉系统的稳定性问题,欢迎留言交流~