如何在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
相关产品推荐
相关产品推荐

