网页端, 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 才能用摄像头。video 加 playsinline 兼容 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大小模型, 又快又准, 百度这次真香

相关推荐
可乐鸡翅yeah_11 小时前
新手梳理:HLS 线上问题,哪些该提给 CDN,哪些该找后端切片服务
开发语言·前端·javascript·vue.js·网络协议·http·m3u8在线
zwd200511 小时前
Manim arrange 和 arrange_in_grid 用法详解:buff、aligned_edge、rows/cols(0.21.0 实测)
前端·python·edge
小小龙学IT11 小时前
Go 语言 reflect 反射包深度解析:从 Type/Value 到三大定律与工业实践
前端·golang
小溪学编程11 小时前
C语言篇:枚举类型
java·c语言·前端
xingpanvip11 小时前
星盘接口开发文档:推算星座接口指南
前端·php
Da Da 泓11 小时前
HTML浅浅入门
前端·html
এ慕ོ冬℘゜11 小时前
es6基础
前端·javascript·es6
熊猫钓鱼>_>12 小时前
从2D平铺到3D沉浸:我用HarmonyOS 7端侧AI做了一个全程数据不出设备的空间化私密相册
前端·人工智能·3d·华为·华为云·harmonyos·鸿蒙
大圣编蚕12 小时前
Spring AOP 失效排查:从原理到实战的完整指南
java·后端·spring
AINative软件工程12 小时前
LLM 多租户 Prompt Isolation 工程实践:为 100 个租户定制 Prompt,但别让它们互相污染
后端·llm