You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在React中提取BodyPix模型关键点并存储复用

BodyPix 关键点全局存储与JSON导出方案

全局复用关键点数据实现

React函数组件环境下用useRef存储高频更新的检测数据最合适,不会触发无意义重渲染,也不存在闭包取值问题,全组件范围内可随时访问。

  • 先修正现有代码的隐藏问题:原逻辑直接在组件根作用域调用runBodysegment(),会在每次组件重渲染时重复加载模型、新建定时器,造成内存泄漏。需要把初始化逻辑移入useEffect,组件卸载时自动清理定时器。
  • 新建两个ref分别存储不同维度的关键点数据:
    • latestKeypointsRef:存储当前最新检测帧的关键点,需要实时取值时直接调用
    • allKeypointsRecordRef:存储所有历史帧的带时间戳关键点,需要导出全量数据时使用
  • 在detect方法拿到模型返回结果后,直接把关键点数据写入ref即可。

修改后的核心代码如下:

import { useRef, useEffect } from 'react';

function App() {
  const canvasRef = useRef(null);
  const videoRef = useRef(null); // 原有video元素对应ref
  // 关键点存储ref
  const latestKeypointsRef = useRef(null);
  const allKeypointsRecordRef = useRef([]);

  const bodyPixProperties = {
    architecture: 'MobileNetV1',
    outputStride: 16,
    multiplier: 0.75,
    quantBytes: 4
  };

  // 模型初始化逻辑移入useEffect,仅在组件挂载时执行一次
  useEffect(() => {
    let detectTimer = null;

    const detect = async (net) => {
      // 保留原有video就绪判断逻辑
      // ...
      const video = videoRef.current;
      if (!video) return;

      const person = await net.segmentPersonParts(video, {
        flipHorizontal: false,
        internalResolution: 'medium',
        segmentationThreshold: 0.7
      });

      // 写入ref供全局调用
      latestKeypointsRef.current = person.pose.keypoints;
      // 不需要存历史数据就删掉下面这行,避免内存占用持续上涨
      allKeypointsRecordRef.current.push({
        ts: Date.now(),
        keypoints: person.pose.keypoints
      });

      // 保留原有遮罩绘制逻辑
      const coloredPartImage = bodyPix.toColoredPartMask(person);
      const opacity = 0.7;
      const flipHorizontal = false;
      const maskBlurAmount = 0;
      const canvas = canvasRef.current;
      bodyPix.drawMask(
        canvas,
        video,
        coloredPartImage,
        opacity,
        maskBlurAmount,
        flipHorizontal
      );
    };

    const runBodysegment = async () => {
      const net = await bodyPix.load(bodyPixProperties);
      console.log("BodyPix model loaded.");
      detectTimer = setInterval(() => {
        detect(net);
      }, 100);
    };

    runBodysegment();

    // 组件卸载时清理定时器
    return () => {
      if (detectTimer) clearInterval(detectTimer);
    }
  }, []);

  // 组件内任意位置需要取关键点,直接读ref即可
  // 例:const currentPoints = latestKeypointsRef.current

导出JSON文件实现

直接封装导出方法,绑定到按钮点击事件即可触发下载,支持选择导出当前帧或全量历史数据:

const exportKeypointsJson = (exportAllHistory = false) => {
  const targetData = exportAllHistory 
    ? allKeypointsRecordRef.current 
    : latestKeypointsRef.current;
  
  if (!targetData) {
    console.warn("暂无可用关键点数据,无法导出");
    return;
  }

  // 转格式化JSON字符串
  const jsonContent = JSON.stringify(targetData, null, 2);
  const blob = new Blob([jsonContent], { type: 'application/json' });
  const tempUrl = URL.createObjectURL(blob);

  // 创建临时DOM触发下载
  const downloadEl = document.createElement('a');
  downloadEl.href = tempUrl;
  downloadEl.download = `bodypix-keypoints-${Date.now()}.json`;
  document.body.appendChild(downloadEl);
  downloadEl.click();

  // 清理临时资源
  document.body.removeChild(downloadEl);
  URL.revokeObjectURL(tempUrl);
};

// 使用示例:绑定到操作按钮
return (
  <div>
    {/* 原有video、canvas渲染元素 */}
    <button onClick={() => exportKeypointsJson(false)}>导出当前帧关键点</button>
    <button onClick={() => exportKeypointsJson(true)}>导出全量历史关键点</button>
  </div>
)

提示:如果不需要长期运行保留历史数据,不要往allKeypointsRecordRef里追加数据,避免页面运行时间过长后数组占用过多内存。


内容的提问来源于stack exchange,提问作者Selim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 10:39:20