使用Puppeteer能否将Javascript对象传递给Node.js?

9

背景

我正在使用Posenet(在此处查看浏览器演示here)进行关键点检测。我已经设置它在WebRTC MediaStream上运行,如下:

客户端:在机器A的Chrome标签中运行。初始化WebRTC连接并将MediaStream发送到服务器。通过WebRTC的DataChannel实时从服务器接收关键点数据。

服务器:在机器B的Chrome标签中运行,接收WebRTC流并将相应的MediaStream传递给Posenet。Posenet完成其任务并计算关键点。然后,通过WebRTC的DataChannel将此关键点数据发送回客户端(如果您有更好的想法,我会倾听)。

问题: 我希望服务器能够从各个客户端接收多个流,并在每个流上运行Posenet,向所有客户端发送实时关键点数据。虽然我不太喜欢服务器使用Chrome,但目前我可以使用puppeteer和Chrome的无头模式来抽象WebRTC的复杂性。

方法

我已经尝试了两种方法,其中我非常偏爱#2

方法 #1

puppeteer 上下文中运行 @tensorflow/tfjs(即在无头 Chrome 标签页中)。但是,由于一些 WebGL 错误,我似乎无法让 PoseNet 浏览器演示 在无头模式下工作(尽管在非无头模式下可以工作)。我尝试了以下方法(将 args 传递给 puppeteer.launch() 以启用 WebGL,但我没有任何运气 - 参见 此处此处):

const puppeteer = require('puppeteer');

async function main() {
  const browser = await puppeteer.launch({
    headless: true,
    args: ['--enable-webgl-draft-extensions', '--enable-webgl-image-chromium', '--enable-webgl-swap-chain', '--enable-webgl2-compute-context']
  });
  const page = await browser.newPage();
  await page.goto('https://storage.googleapis.com/tfjs-models/demos/posenet/camera.html', {
    waitUntil: 'networkidle2'
  });
  // Make chromium console calls available to nodejs console
  page.on('console', msg => {
    for (let i = 0; i < msg.args().length; ++i)
      console.log(`${i}: ${msg.args()[i]}`);
  });
}

main();


在无头模式下,我收到了这个错误信息。
0: JSHandle:Initialization of backend webgl failed
0: JSHandle:Error: WebGL is not supported on this device

这让我面临一个问题:如何在puppeteer中启用WebGL?

方法二

最好的情况是,我想使用@tensorflow/tfjs-node后端来运行posenet,以加速计算。因此,我需要将puppeteer@tensorflow/tfjs-node链接起来,使得:
  • puppeteer-chrome-tab客户端使用WebRTC通信,它使得一个Mediastream对象可用于node
  • node接收这个MediaStream并将其传递给posenet(因此传递给了@tensorflow/tfjs-node),机器学习魔法就发生了。然后,node将检测到的关键点传回puppeteer-chrome-tab,后者使用其RTCDataChannel将它们传回客户端

问题

问题在于我似乎无法在node中访问puppeteer的MediaStream对象,以将此对象传递给posenet。我只能访问JSHandlesElementHandles。是否有可能将与句柄相关联的javascript对象传递给node
具体而言,抛出以下错误:
UnhandledPromiseRejectionWarning: Error: When running in node, pixels must be an HTMLCanvasElement like the one returned by the `canvas` npm package
    at NodeJSKernelBackend.fromPixels (/home/work/code/node_modules/@tensorflow/tfjs-node/dist/nodejs_kernel_backend.js:1464:19)
    at Engine.fromPixels (/home/work/code/node_modules/@tensorflow/tfjs-core/dist/engine.js:749:29)
    at fromPixels_ (/home/work/code/node_modules/@tensorflow/tfjs-core/dist/ops/browser.js:85:28)
    at Object.fromPixels (/home/work/code/node_modules/@tensorflow/tfjs-core/dist/ops/operation.js:46:29)
    at toInputTensor (/home/work/code/node_modules/@tensorflow-models/posenet/dist/util.js:164:60)
    at /home/work/code/node_modules/@tensorflow-models/posenet/dist/util.js:198:27
    at /home/work/code/node_modules/@tensorflow/tfjs-core/dist/engine.js:349:22
    at Engine.scopedRun (/home/work/code/node_modules/@tensorflow/tfjs-core/dist/engine.js:359:23)
    at Engine.tidy (/home/work/code/node_modules/@tensorflow/tfjs-core/dist/engine.js:348:21)
    at Object.tidy (/home/work/code/node_modules/@tensorflow/tfjs-core/dist/globals.js:164:28)

记录传递给NodeJSKernelBackend.prototype.fromPixels = function (pixels, numChannels) {..}pixels参数,它被评估为ElementHandle。我知道可以使用puppeteerpage.evaluate访问Javascript对象的可序列化属性。但是,如果我想通过调用puppeteer.evaluate(..)CanvasRenderingContext2DimageData(使用getImageData()方法)传递给node,这意味着需要对整个原始图像进行字符串化,然后在node的上下文中重新构建它。

这让我面临着第二个问题:有没有办法直接在node中访问(只读)来自puppeteer上下文的对象,而不必通过例如puppeteer.evaluate(..)的方式?

1
由于执行环境的差异,您在节点和浏览器上下文之间传递的任何内容都将被序列化。自从Puppeteer创建以来,我没有找到这样的方法。 - Md. Abu Taher
谢谢你的见解。如果我在puppeteer上下文中运行tensorflowjs,你有解决WebGL错误的想法吗? - Sean
1个回答

6
我建议采用另一种方法,放弃在服务器端使用puppeteer的想法,而是在Node.js中实现一个实际的WebRTC客户端,然后直接使用@tensorflow/tfjs-node通过PoseNet。
为什么不在服务器端使用puppeteer 在服务器端使用puppeteer会引入很多复杂性。除了与多个客户端的活动WebRTC连接之外,您现在还必须管理每个连接的一个浏览器(或至少一个选项卡)。因此,您不仅需要考虑当与客户端的连接失败时会发生什么,而且还必须准备其他情况,例如浏览器崩溃、页面崩溃、WebGL支持(每个页面)、浏览器实例的内存/CPU使用情况等等。
话虽如此,让我们来看看你的方法。
方法1:在puppeteer中运行Tensorflow.js
您应该能够只使用cpubackend来运行此操作。在使用任何其他代码之前,您可以像这样设置后端:
tf.setBackend('cpu');

你可能也能够运行WebGL(因为你不是唯一一个在使用puppeteer时遇到WebGL问题的人)。但即使你成功运行了它,你现在正在运行一个Node.js脚本来启动Chrome浏览器,在网站内开始一个WebRTC会话和Tensorflow.js训练。就复杂性而言,如果出现任何问题,这将非常难以调试...
方法2:在puppeteer和Node.js之间传输数据
如果没有大量减速(关于发送和接收帧),这种方法几乎是不可能的。puppeteer需要序列化任何交换的数据。在Node.js和浏览器环境之间没有共享内存或共享数据对象。这意味着你必须将每个帧(所有像素...)序列化以将它们从浏览器环境传输到Node.js。在小图像的情况下,这在性能上可能还可以,但对于越大的图像,性能将变得越差。
总之,如果你选择其中一种方法,会增加很多复杂性。因此,让我们看看另一种选择。
替代方案:直接将视频流发送到服务器
不使用puppeteer建立WebRTC连接,而是直接实现WebRTC对等连接。我从你的问题中读出你担心这会增加复杂性,但这可能值得一试。
要实现WebRTC服务器,可以使用node-webrtc库,在服务器端实现WebRTC对等连接。有多个示例,其中一个对你的用例非常有趣。这是video-compositing示例,它在客户端(浏览器)和服务器(Node.js)之间建立连接以流式传输视频。然后服务器将修改发送的帧并在其上放置“水印”。 代码示例 以下代码显示了video-compositing示例中最相关的行。该代码从输入流中读取一帧并从中创建一个node-canvas对象。
const lastFrameCanvas = createCanvas(lastFrame.width,  lastFrame.height);
const lastFrameContext = lastFrameCanvas.getContext('2d', { pixelFormat: 'RGBA24' });

const rgba = new Uint8ClampedArray(lastFrame.width *  lastFrame.height * 4);
const rgbaFrame = createImageData(rgba, lastFrame.width, lastFrame.height);
i420ToRgba(lastFrame, rgbaFrame);

lastFrameContext.putImageData(rgbaFrame, 0, 0);
context.drawImage(lastFrameCanvas, 0, 0);

你现在拥有一个画布对象,可以像这样将其输入到 PoseNet 中:
const net = await posenet.load();

// ...
const input = tf.browser.fromPixels(lastFrameCanvas);
const pose = await net.estimateSinglePose(input, /* ... */);

现在需要将结果数据传输回客户端,可以使用数据通道完成。仓库中还有一个关于此的示例(ping-pong),比视频示例简单得多。

尽管您可能担心使用node-webrtc的复杂性,但我建议尝试这种方法和node-webrtc-examples。您可以先查看存储库。所有示例都已准备好尝试和玩耍。


非常感谢这些见解!尽管我现在只是原型设计,但我现在可以看到使用puppeteer会使事情过于复杂。我将选择node-webrtcsimple-peer并在某个阶段发布结果。使用@tensorflow/tfjs-node似乎真的是唯一的解决方案。 - Sean
1
@ThomasDondorf 谢谢,这真是一个非常有用的提示,我又差点走错方向了.. node-webrtc 就是它了 - 现在我明白了 RTCVideoSink 的作用。 - Sean
2
对于任何想知道这个问题的人:我使用 node-webrtc 和 @ThomasDondorf 提出的 RTCVideoSink 成功构建了所需的内容。我为 WebRTC 编写了一个自定义信令服务器,用于处理连接并生成 nodejs 工作线程。唯一的问题是让 node 的 canvas 库支持多个工作线程 - 目前它不支持。目前正在进行一些讨论:https://github.com/Automattic/node-canvas/issues/1394 - Sean
1
我来晚了,但我面临着与流式传输到服务器并进行实时3D姿态估计的类似问题。你有没有样例代码可以分享一下,展示如何使用 node-webrtc 实现实时流媒体?这将非常有帮助! 另外,你是否找到了一种方法使其与Python配合工作,因为大多数SOTA深度学习都是在Pytorch或TF Python API中完成的。 - Anuar Y
1
没问题,我已经想出了一种替代方法,通过websocket发送流来实现我的简单应用程序,这个方法更加简单。 - Anuar Y
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接