我正在使用 P5 和 ML5,并在 React 中使用它们,这让我在让它们工作时不得不克服一些障碍。
我正在尝试获取标签名称,并将其绘制在网络摄像头上。我可以在 gotResults 函数中使用 console.log 记录标签名称,但我不知道如何将它用于 gotResults 函数之外的地方…
我刚开始学习编程,请原谅我 🙂
import React, { useRef, useEffect, useState } from 'react';import p5, { VIDEO } from 'p5'import ml5 from 'ml5'const Webcam = props => { const myRef = useRef(); const [label, setLabel] = useState([ { label: 'Starting Label', confidence: 0 }, ]); useEffect(() => { let myp5 = new p5(sketch, myRef.current) }); const sketch = (p) => { let mobilenet; let cam; let classifier; let thumbUpRight; let thumbDownRight; let thumbUpLeft; let thumbDownLeft; let trainButton; // let label = ''; const modelReady = () => { console.log('Model is ready!'); } const videoReady = () => { console.log('Video is ready!'); } const whileTraining = (loss) => { if (loss == null) { console.log('Training Complete') classifier.classify(gotResults) } else { console.log(loss) } } const gotResults = (error, result) => { if (error) { console.error(error); } else { // console.log(result) setLabel(result); classifier.classify(gotResults); } } p.setup = () => { p.createCanvas(640, 550); cam = p.createCapture(VIDEO); cam.hide(); p.background(0) mobilenet = ml5.featureExtractor('MobileNet', modelReady) classifier = mobilenet.classification(cam, videoReady) thumbUpRight = p.createButton('Up Right'); thumbUpRight.mousePressed(function () { classifier.addImage('thumbUpRight'); }) thumbDownRight = p.createButton('Down Right'); thumbDownRight.mousePressed(function () { classifier.addImage('thumbDownRight'); }) thumbUpLeft = p.createButton('Up Left'); thumbUpLeft.mousePressed(function () { classifier.addImage('thumbUpLeft'); }) thumbDownLeft = p.createButton('Down Left'); thumbDownLeft.mousePressed(function () { classifier.addImage('thumbDownLeft'); }) trainButton = p.createButton('Train'); trainButton.mousePressed(function () { classifier.train(whileTraining); }) }; p.draw = () => { p.background(0); p.image(cam, 0, 0) p.fill(255); p.textSize(32); p.text(label[0].label, 10, 530); } }; return ( <div ref={myRef}> </div> )}export default Webcam;
更新:所以错误与状态重置有关,每次调用 ML5 时都会重新渲染网络摄像头和所有内容?我猜想,每次它接触到 ML5 API 时都会重新绘制…?
回答:
看起来这些重新渲染是由于 classifier.classify(gotResults)
函数会执行多次,而你在这里使用 setLabel(result)
更新状态,这会导致每次调用时重新渲染。你可以注释掉它,看看是否是罪魁祸首。如果是的话,那么你应该浅比较 results
和 label
,只有当这些对象不同时才更新状态 setLabel(result)
:
在更新状态之前浅比较 result
和 label
:
const gotResults = (error, result) => { if (error) { console.error(error); } else { if (!areEqualShallow(label, result) { setLabel(result); } classifier.classify(gotResults); }}function areEqualShallow(a, b) { for (var key in a) { if (a[key] !== b[key]) { return false; } } return true;}
如果这不是导致这些重新渲染的罪魁祸首,那么你应该检查你的 useEffect
钩子。我不知道你是否有意这样做,但如果没有参数的 useEffect
钩子,它会在每次渲染时执行这个钩子。
useEffect 在每次渲染后都运行吗? 是的!默认情况下,它会在第一次渲染后和每次更新后运行。(我们稍后会讨论如何自定义这一点。)不要以“挂载”和“更新”的方式思考,你可能会发现以“渲染后”发生的效果来思考更容易。React 保证在运行效果时 DOM 已经更新了。
这个钩子会在每次渲染时运行:
useEffect(() => { let myp5 = new p5(sketch, myRef.current)});
在 useEffect
钩子的末尾添加一个空数组,只运行一次:
useEffect(() => { let myp5 = new p5(sketch, myRef.current)}, []);
更新
要更新现有 P5 对象上的标签,你必须使用 redraw
。你需要在 label
状态变量上创建一个效果钩子,当 label
变化时它会被调用:
// 设置一个 P5 引用,以便你可以再次使用它const p5Ref = useRef();// 更改初始钩子以设置 p5RefuseEffect(() => { p5Ref.current = new p5(sketch, myRef.current);}, []);// 当整个 label 对象更新时,这个效果将运行,// 而不是仅仅当 label.<prop> 更新时useEffect(() => { // 我不能确定这是否会起作用, // 你需要测试并查看如何使用返回的 P5 对象正确地强制重绘 if (p5Ref && p5Ref.current) { p5Ref.current.redraw(); }}, [label]);
当然,由于对象引用,如果你只是更改 label
的值,这个钩子不会触发,所以在使用 setLabel
更新 label
时,你需要创建一个新对象,像这样:
const gotResults = (error, result) => { if (error) { console.error(error); } else { // 创建一个新对象,以便我们能在 label 更改时触发更新效果钩子 setLabel({ ...result }); classifier.classify(gotResults); }}