首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >纯浏览器实时动捕实践:用 MediaPipe、Kalidokit 和 Three.js 驱动蜘蛛侠 3D 模型

纯浏览器实时动捕实践:用 MediaPipe、Kalidokit 和 Three.js 驱动蜘蛛侠 3D 模型

原创
作者头像
用户5557817
发布2026-08-03 19:58:13
发布2026-08-03 19:58:13
2522
举报

项目背景

我最近做了一个名为 Everyone Is Spider-Man 的浏览器实时动作捕捉项目。

用户授权摄像头后,系统会识别人脸、上半身、手腕和手指动作,并使用识别结果驱动蜘蛛侠 3D 模型。用户转头、抬手、弯曲手肘或活动手指时,模型都会尽可能同步做出相应动作。

这个项目的核心目标是实现一个浏览器中的实时“蜘蛛侠镜子”:

  • 不安装客户端
  • 不依赖专业动作捕捉设备
  • 摄像头数据不上传服务器
  • 使用普通电脑摄像头即可体验
  • 支持脸部、上半身和双手追踪
  • 支持多语言界面

项目地址: https://github.com/MartinDelophy/everyone-is-spiderman

项目主要使用以下技术:

  • React
  • Vite
  • Three.js
  • Three-VRM
  • MediaPipe Tasks Vision
  • Kalidokit
  • GLB 3D 模型

一、整体技术架构

项目的实时处理链路如下:

代码语言:plaintext
复制
浏览器摄像头
    ↓
MediaPipe 识别人脸、身体和手部关键点
    ↓
坐标转换、镜像处理和置信度过滤
    ↓
Kalidokit 计算人体姿态
    ↓
转换为标准化人形骨骼旋转
    ↓
Three-VRM 驱动角色骨骼
    ↓
Three.js 实时渲染

摄像头画面不会上传到服务端,视觉推理、姿态计算和角色渲染全部在用户浏览器中完成。

这种架构具有两个明显优势:

  1. 摄像头隐私更容易得到保障。
  2. 不需要为每一帧视频承担服务端传输和计算成本。

同时,它也对用户设备性能提出了更高要求。浏览器需要同时完成视频解码、AI 推理、姿态计算和 3D 渲染。


二、使用 MediaPipe 获取人体关键点

MediaPipe Tasks Vision 可以在浏览器中直接运行视觉模型。

项目需要处理三类数据。

人脸关键点

人脸关键点主要用于计算:

  • 头部左右转动
  • 抬头和低头
  • 头部倾斜
  • 面部朝向

身体姿态关键点

身体关键点负责驱动:

  • 肩膀
  • 上臂
  • 手肘
  • 手腕
  • 躯干

手部关键点

手部关键点用于控制:

  • 手腕方向
  • 拇指
  • 食指
  • 中指
  • 无名指
  • 小拇指
  • 握拳、张手和捏合状态

手部追踪也是整个项目中最容易出现抖动和识别错误的部分。


三、关键点不能直接驱动模型

MediaPipe 返回的数据主要是关键点坐标,而 3D 角色需要的是骨骼旋转。

例如,当用户抬起右手时,模型并不是简单地把手移动到某个坐标,而是需要依次调整:

  • 右肩旋转
  • 右上臂方向
  • 右手肘弯曲
  • 右手腕朝向
  • 每根手指的弯曲程度

因此,需要先把关键点转换成人体姿态,再把姿态映射到角色骨骼。

项目使用 Kalidokit 处理这一转换。简化后的代码如下:

代码语言:js
复制
const poseResult = Kalidokit.Pose.solve(
  poseLandmarks,
  worldLandmarks,
  {
    runtime: "mediapipe",
    video
  }
);

得到姿态结果后,再将手臂、头部和身体的旋转应用到对应骨骼。


四、通过 Three-VRM 统一骨骼控制

不同 3D 模型的骨骼命名方式可能完全不同。

例如,左上臂可能叫:

代码语言:plaintext
复制
LeftArm
upper_arm.L
mixamorigLeftArm
J_Bip_L_UpperArm

如果业务逻辑直接依赖模型原始骨骼名称,更换模型后往往需要重写大量映射代码。

Three-VRM 提供了标准化的人形骨骼接口:

代码语言:js
复制
const leftUpperArm =
  vrm.humanoid.getNormalizedBoneNode("leftUpperArm");

const rightHand =
  vrm.humanoid.getNormalizedBoneNode("rightHand");

应用层只需要操作统一的骨骼名称,不需要关心模型内部原始命名。

项目使用固定的蜘蛛侠 GLB 模型作为可见角色资产,并将其映射到标准人形骨骼系统中。


五、通过插值降低模型抖动

视觉识别结果会随着光线、遮挡和运动速度不断变化。如果把每一帧的识别结果直接赋值给模型,角色会出现明显抖动。

对于骨骼旋转,可以使用四元数球面插值:

代码语言:js
复制
bone.quaternion.slerp(targetQuaternion, smoothingFactor);

其中,smoothingFactor 决定模型追随目标姿态的速度。

数值过小时:

  • 动作更加平滑
  • 快速动作延迟明显
  • 模型会产生“跟不上人”的感觉

数值过大时:

  • 动作响应更快
  • 识别噪声更明显
  • 手腕和手指容易抖动

更合适的方案是采用自适应平滑:

  • 缓慢移动时加强平滑。
  • 快速移动时提高响应速度。
  • 置信度较低时降低目标姿态权重。
  • 追踪丢失时逐渐回到自然姿势。

这样可以同时兼顾稳定性和响应速度。


六、镜像坐标处理

自拍摄像头通常会以镜像形式显示,而视觉模型输出的坐标不一定已经镜像。

常见问题包括:

  • 用户抬左手,模型却抬右手。
  • 手势光标的移动方向相反。
  • 头部旋转方向错误。
  • 身体动作正常,但手指朝向错误。

基础的横向镜像可以写成:

代码语言:js
复制
const mirroredX = 1 - landmark.x;

但实际项目中不能简单地对所有横坐标执行相同操作。

系统中至少存在三套坐标:

代码语言:plaintext
复制
摄像头原始坐标
屏幕显示坐标
Three.js 场景坐标

正确的处理方式是建立统一的中间坐标层:

  1. 将 MediaPipe 数据转换到标准坐标。
  2. 根据前置或后置摄像头确定镜像方式。
  3. UI 手势光标使用屏幕坐标。
  4. 角色骨骼使用模型坐标。
  5. 所有模块共享同一份左右手定义。

这样可以避免不同模块分别进行镜像,导致坐标被重复翻转。


七、手部追踪的主要难点

相比头部和身体,手部追踪更容易受到遮挡和运动速度影响。

双手身份互换

当两只手交叉时,MediaPipe 可能在连续帧中交换左右手结果。

可以根据以下信息维持手部身份:

  • 上一帧手腕位置
  • 当前帧手腕位置
  • 手部移动方向
  • 身体左右肩的位置
  • MediaPipe 返回的 handedness

不能只依赖单帧的左右手分类结果。

手指互相遮挡

握拳或做蜘蛛侠手势时,部分手指关键点可能被遮挡。

这时可以利用关键点置信度:

代码语言:js
复制
const weight = confidence > threshold ? 1 : 0.2;

置信度较低时,不要立即采用新的旋转结果,而是保留上一帧姿态或缓慢恢复到默认状态。

手势重复触发

如果只判断当前帧是否满足条件,一个手势可能在几十帧内被连续触发。

可以为手势建立状态机:

代码语言:plaintext
复制
IDLE
  ↓
CANDIDATE
  ↓
HOLDING
  ↓
TRIGGERED
  ↓
COOLDOWN
  ↓
IDLE

只有手势连续保持一定时间后才触发,触发后进入冷却状态,可以显著降低误操作。


八、蜘蛛侠手势识别思路

蜘蛛侠经典的蛛丝发射手势通常具有以下特征:

  • 食指伸直
  • 小拇指伸直
  • 中指弯曲
  • 无名指弯曲
  • 拇指位置满足一定角度条件

可以根据指尖到手掌的距离判断手指是否伸直:

代码语言:js
复制
function isFingerExtended(tip, pip, wrist) {
  const tipDistance = distance(tip, wrist);
  const jointDistance = distance(pip, wrist);

  return tipDistance > jointDistance * 1.15;
}

然后组合多个条件:

代码语言:js
复制
const isWebGesture =
  indexExtended &&
  pinkyExtended &&
  !middleExtended &&
  !ringExtended;

为了减少误识别,还可以增加:

  • 连续帧确认
  • 手掌朝向判断
  • 关键点置信度要求
  • 手势保持时长
  • 触发冷却时间

后续还可以加入双手蛛丝发射、瞄准、握拳和连续动作组合。


九、浏览器端性能优化

项目运行时需要同时进行 AI 推理和 3D 渲染,因此性能优化非常重要。

推理和渲染使用不同频率

Three.js 可以以 60 FPS 渲染,但 MediaPipe 不一定需要每帧执行。

例如:

代码语言:plaintext
复制
3D 渲染:60 FPS
视觉推理:20~30 FPS
骨骼更新:使用插值补齐

这样可以减少视觉模型占用,同时保持画面流畅。

避免高频更新 React State

人体关键点每秒可能更新几十次。如果全部写入 React State,会触发大量组件重新渲染。

实时追踪数据更适合存入 ref

代码语言:js
复制
const trackingRef = useRef(null);

Three.js 渲染循环直接读取 trackingRef.current。只有权限状态、语言选择和界面提示等低频数据需要存入 State。

动态调整画质

可以根据设备性能调整:

  • 摄像头分辨率
  • MediaPipe 推理频率
  • Three.js 像素比例
  • 抗锯齿
  • 阴影
  • 后处理效果

在移动设备上,稳定的 30 FPS 通常比不稳定的高画质更重要。


十、摄像头隐私与权限

项目中的摄像头画面和动作识别结果都在浏览器本地处理,不会上传到服务器。

摄像头应用需要清楚地向用户说明:

  • 为什么需要摄像头权限。
  • 视频数据是否会被保存。
  • 数据是否会上传。
  • 如何停止摄像头。
  • 权限被拒绝后如何重新授权。

此外,浏览器摄像头 API 通常只允许在以下环境中使用:

  • localhost
  • HTTPS 页面

因此,生产环境部署时必须启用 HTTPS。


十一、项目中的几个经验

开发这个项目后,我总结了几条比较重要的经验。

1. 能识别不代表体验好

AI 模型能够输出关键点,只代表功能可以运行。真正决定体验的是:

  • 动作是否稳定
  • 延迟是否足够低
  • 遮挡后能否恢复
  • 左右手会不会交换
  • 手势是否容易误触发

2. 不要直接使用单帧结果

摄像头追踪是一个连续过程,应当结合历史帧判断身份、速度和手势状态。

3. UI 和 3D 场景应共享坐标规范

如果 UI、手势识别和角色渲染分别处理镜像,很容易出现左右方向不一致的问题。

4. 低置信度不应该直接清空姿态

关键点短暂丢失时,让模型逐渐恢复到中立姿势,会比瞬间归零自然得多。

5. 浏览器端项目需要优先控制延迟

降低动作延迟通常比增加光影效果更能提升沉浸感。


十二、后续开发计划

接下来准备重点推进以下功能。

精细手势控制

  • 独立控制每根手指关节
  • 优化手腕旋转
  • 增加自适应平滑
  • 改善双手交叉时的身份稳定性
  • 优化快速运动和局部遮挡
  • 追踪丢失后自然恢复姿势

更多手势功能

  • 蜘蛛侠经典蛛丝发射手势
  • 握拳、张手和指向
  • 捏合、抓取和释放
  • 单手及双手瞄准
  • 双手同步发射蛛丝
  • 连续手势与组合手势
  • 左手、右手和双手映射配置

性能和兼容性

  • 自动选择设备画质
  • 降低摄像头到画面的整体延迟
  • 增加 FPS 和推理耗时诊断
  • 适配移动端屏幕旋转
  • 完善不同浏览器的摄像头兼容性
  • 为低性能设备提供降级方案

产品体验

  • 增加交互式新手引导
  • 增加手势说明与动画演示
  • 完善摄像头权限错误提示
  • 支持高对比度和减少动态效果
  • 完善多语言内容

总结

这个项目打通了一条完整的浏览器实时动作捕捉链路:

代码语言:plaintext
复制
摄像头画面
→ 人体关键点
→ 姿态计算
→ 骨骼重定向
→ 蜘蛛侠模型
→ 实时渲染

MediaPipe 负责感知用户动作,Kalidokit 负责计算人体姿态,Three-VRM 提供标准化人形骨骼接口,Three.js 则负责最终的 3D 渲染。

整个方案不需要专业动捕设备,也不需要把视频上传到服务器。用户只需一台带摄像头的电脑,就可以在浏览器中获得实时角色驱动体验。

后续最值得投入的方向,是手指控制、遮挡恢复、低延迟优化和蜘蛛侠特色手势。只有把这些细节处理好,角色才能从“跟着用户移动”进一步提升到“像用户一样自然地移动”。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 项目背景
  • 一、整体技术架构
  • 二、使用 MediaPipe 获取人体关键点
    • 人脸关键点
    • 身体姿态关键点
    • 手部关键点
  • 三、关键点不能直接驱动模型
  • 四、通过 Three-VRM 统一骨骼控制
  • 五、通过插值降低模型抖动
  • 六、镜像坐标处理
  • 七、手部追踪的主要难点
    • 双手身份互换
    • 手指互相遮挡
    • 手势重复触发
  • 八、蜘蛛侠手势识别思路
  • 九、浏览器端性能优化
    • 推理和渲染使用不同频率
    • 避免高频更新 React State
    • 动态调整画质
  • 十、摄像头隐私与权限
  • 十一、项目中的几个经验
    • 1. 能识别不代表体验好
    • 2. 不要直接使用单帧结果
    • 3. UI 和 3D 场景应共享坐标规范
    • 4. 低置信度不应该直接清空姿态
    • 5. 浏览器端项目需要优先控制延迟
  • 十二、后续开发计划
    • 精细手势控制
    • 更多手势功能
    • 性能和兼容性
    • 产品体验
  • 总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档