网页端, 5.5mb谷歌模型, 识别躯干, 视频都不卡

我的计划是识别唱🎤 跳🕺rap🗣️ 篮球🏀,今天识别跳🕺

视频素材是AI生成的, 仅用来测试

即便是纯cpu视频也不卡, 我是AMD5500, 入门CPU, 但是识别视频一秒也能60帧左右

上传图片画出骨架,传视频骨架跟着动,开摄像头实时识别。纯浏览器跑,零后端。模型只有 5.5MB,图片、视频、摄像头一个模型全搞定。

流程图

bash 复制代码
    打开页面
         │
         ▼
    加载 WASM 运行时
         │
         ▼
    后台加载 5.5MB 模型
         │
         ▼
    显示"已就绪 · GPU"
         │
         ▼
    选择 图片 / 视频 / 摄像头
         │
         ▼
    detectForVideo 推理
         │
         ▼
    33 个关键点 + 置信度
         │
         ▼
    Canvas 画骨架

10 行跑起来:

bash 复制代码
import * as $mp from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.21/+esm"
const { FilesetResolver, PoseLandmarker } = $mp
const vision = await FilesetResolver.forVisionTasks(
  "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.21/wasm"
)
const pose = await PoseLandmarker.createFromOptions(vision, {
  baseOptions: { modelAssetPath: "./models/pose_landmarker_lite.task", delegate: "GPU" },
  runningMode: "VIDEO",
})
const r = pose.detectForVideo(img, performance.now())
console.log(r.landmarks[0].length) // 33

@mediapipe/tasks-vision,Google 出品,GitHub 17k+ 星。传统方案要装 Python 或配 GPU 服务器。MediaPipe 把这些搬进了浏览器。

1. 模型预加载 + GPU/CPU 切换

bash 复制代码
async function loadModel(delegate) {
  const vision = await FilesetResolver.forVisionTasks(
    "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.21/wasm"
  )
  poseLandmarker = await PoseLandmarker.createFromOptions(vision, {
    baseOptions: { modelAssetPath: "./models/pose_landmarker_lite.task", delegate },
    runningMode: "VIDEO", numPoses: 1,
  })
}
loadModel("GPU") // 页面打开就加载

async function switchDelegate(delegate) {
  currentDelegate = delegate
  await loadModel(delegate) // 切换后重建模型
}

delegate"GPU" 走 WebGL,传 "CPU" 走原生。小模型有时 CPU 反而更快,点按钮切一下就行。

模型没加载好就拦截:

bash 复制代码
if (!poseLandmarker) { setStatus("模型还没加载好,请稍候"); return }

2. 一个模型跑图片 + 视频

VIDEO 模式通吃两种输入,图片当一帧传时间戳:

bash 复制代码
// 图片
const r = poseLandmarker.detectForVideo(img, performance.now())
// 视频帧
const r2 = poseLandmarker.detectForVideo(video, performance.now())

一次检测 25ms 左右:

bash 复制代码
图片: 800 × 600 | 人体数: 1 | 推理: 35ms

3. 视频/摄像头,20fps 节流

不能每帧推理,用 requestAnimationFrame + 50ms 节流:

bash 复制代码
function loopFrames(videoEl, cctx) {
  const now = performance.now()
  if (now - lastDetect > 50) {
    const results = poseLandmarker.detectForVideo(videoEl, now)
    cctx.drawImage(videoEl, 0, 0, W, H)
    drawSkeleton(cctx, W, H, results.landmarks[0])
    lastDetect = now
  }
  raf = requestAnimationFrame(() => loopFrames(videoEl, cctx))
}

摄像头复用同一套:

bash 复制代码
stream = await navigator.mediaDevices.getUserMedia({ video: { width: 640, height: 480 } })
camVideo.srcObject = stream
loopFrames(camVideo, camctx)

注意:要 localhost 或 https 才能用摄像头。videoplaysinline 兼容 iOS。

4. 画骨架 + 站坐躺判断

四色分组连线:

bash 复制代码
const CONNECTIONS = {
  spine: [[11,12],[11,23],[12,24],[23,24]],
  left:  [[11,13],[13,15],[15,17],[17,19],[19,21],[21,22],[21,25],[25,27],[27,29],[29,31],[31,32]],
  right: [[12,14],[14,16],[16,18],[18,20],[20,22],[22,26],[26,28],[28,30],[30,32]],
  head:  [[0,1],[1,2],[2,3],[3,7],[7,0],[0,4],[4,5],[5,6],[6,8],[8,0]]
}

for (const [i, j] of CONNECTIONS.spine) {
  ctx.moveTo(person[i].x * W, person[i].y * H)
  ctx.lineTo(person[j].x * W, person[j].y * H)
}
ctx.lineWidth = Math.max(2, W * 0.005) // 线宽自适应
ctx.strokeStyle = "#ffb84d"
ctx.stroke()

站坐躺判断,用肩膀和髋部的 y 坐标差:

bash 复制代码
const hipY = (person[23].y + person[24].y) / 2
const shoulderY = (person[11].y + person[12].y) / 2
const gap = hipY - shoulderY

if (gap > 0.12) return "站姿"
if (gap > 0.04) return "坐姿"
return "躺姿"

注意 y 向下增大。写成 shoulderY - hipY 就是负数,永远判"躺姿"。

6. 输出 33 点坐标

bash 复制代码
[
  { x: 0.52, y: 0.18, z: 0.01, score: 0.98 }, // 0 nose
  { x: 0.48, y: 0.22, z: 0.01, score: 0.95 }, // 1 left_eye_inner
  ...
]

x、y 归一化 0 到 1。z 是深度。score 低于 0.5 的点列表里灰掉。

6. 起服务

WASM 需要 COOP/COEP 头:

bash 复制代码
import { join } from "node:path"

Bun.serve({ port: 3005, async fetch(req) {
  const file = Bun.file(join(import.meta.dir, "static",
    new URL(req.url).pathname === "/" ? "/index.html" : new URL(req.url).pathname))
  return new Response(file, { headers: {
    "Cross-Origin-Opener-Policy": "same-origin",
    "Cross-Origin-Embedder-Policy": "require-corp",
  }})
}})

总结

  • MediaPipe Tasks Vision:纯 JS,17k+ 星

  • lite 模型:5.5MB,95% 精度

  • 页面预加载:打开就加载,带状态徽章

  • GPU/CPU 切换:实测哪个快用哪个

  • VIDEO 模式:一个模型通吃图片视频

  • 20fps 节流:视频识别不卡顿

  • 四色骨架:头/臂/脊柱/腿一眼分清

往期文章

网页端, 6.5mb人脸识别模型, 谷歌框架, 又快又准

网页端OCR, 加载6mb大小模型, 又快又准, 百度这次真香

相关推荐
WongKyunban39 分钟前
Go语言的简洁并发编程
开发语言·后端·golang
雪芽蓝域zzs1 小时前
Prettier (代码格式化)配置详解
前端·vue.js
晴天161 小时前
Gradio 与 Streamlit:Python 快速构建 Web 应用的双子星-Day28
开发语言·前端·python
码哥DFS1 小时前
LRU缓存
前端·javascript·缓存
mldong1 小时前
一条命令,十分钟:jeeflow 工作流应用的六语言一键部署
前端·后端·架构
Shawn_Shawn1 小时前
本体论的基本核心概念
后端·llm·领域驱动设计
zzzzzz3102 小时前
别急着把页面做成舞台:从 react-bits 看动画组件该怎么选
javascript·react.js·开源
ly768910 小时前
HTML5 从入门到工程实践:语义化、表单、多媒体、性能与项目规范详解
前端·html·html5
凤山老林11 小时前
Spring Boot 3.x AOT 编译实战:从原理、踩坑到生产落地
java·spring boot·后端