【问题标题】:Is it possible to use puppeteer to pass a Javascript object to nodejs?是否可以使用 puppeteer 将 Javascript 对象传递给 nodejs?
【发布时间】:2020-01-04 02:37:27
【问题描述】:

背景

我正在使用Posenet(请参阅浏览器中的演示here)进行关键点检测。我已将其设置为在 WebRTC MediaStream 上运行,s.t.:

客户端:在机器 A 上的 chrome 选项卡中运行。初始化 WebRTC 连接并将 MediaStream 发送到 Server。通过 WebRTC 的 DataChannel 从 Server 接收实时关键点数据。

服务器:在机器 B 的 chrome 选项卡中运行,接收 WebRTC 流并将相应的 MediaStream 传递给 Posenet。 Posenet 做它的事情并计算关键点。然后,此关键点数据通过 WebRTC 的 DataChannel 发送回客户端(如果您有更好的想法,我会全力以赴)。

问题:我想让服务器接收来自不同客户端的多个流并在每个流上运行 Posenet,向所有客户端发送实时关键点数据。虽然我对使用 Chrome 的服务器并不感到兴奋,但我现在可以使用 puppeteer 和 Chrome 的无头模式,主要是为了抽象出 WebRTC 的复杂性。

方法

我尝试了两种方法,非常赞成方法#2

方法#1

puppeteer 上下文中运行@tensorflow/tfjs(即在无头镀铬标签中)。但是,由于一些 WebGL 错误,我似乎无法让 PoseNet Browser Demo 在无头模式下工作(尽管它确实在非无头模式下工作)。我尝试了以下方法(将 args 传递给 puppeteer.launch() 以启用 WebGL,虽然我没有运气 - 请参阅 herehere 以供参考):

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

这给我留下了question #1:如何在puppeteer 中启用WebGL?

方法#2

我最好使用@tensorflow/tfjs-node 后端运行posenet,以加速计算。因此,我想链接puppeteer@tensorflow/tfjs-node,s.t.:

  • puppeteer-chrome-tab客户端 对话 WebRTC。它使 node 可以使用 Mediastream 对象。
  • node 获取此 MediaStream 并将其传递给 posenet(因此是 @tensorflow/tfjs-node),机器学习的魔力在此发生。 node 然后将检测到的关键点传回给puppeteer-chrome-tabpuppeteer-chrome-tab 使用它的RTCDataChannel 将它们传回给客户端

问题

问题是我似乎无法访问puppeteer 的MediaStream 对象 node,将此对象传递给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 对象的serializable 属性。但是,如果我要传递CanvasRenderingContext2DimageData(使用方法getImageData()node,通过调用puppeteer.evaluate(..),这意味着将整个原始图像字符串化,然后在node 中重建它上下文。

这给我留下了question #2:有没有办法让puppeteer 的上下文中的对象可以直接在node 内部访问(只读),而无需通过例如puppeteer.evaluate(..)?

【问题讨论】:

  • 由于执行环境的差异,您在节点和浏览器上下文之间传递的任何内容都将被序列化。自创建 puppeteer 以来,我没有找到这样的方法。
  • 感谢您的洞察力。如果我要在 puppeteer 上下文中运行 tensorflowjs,您是否知道如何解决 WebGL 错误?

标签: node.js google-chrome puppeteer tensorflow.js


【解决方案1】:

我建议另一种方法是放弃在服务器端使用 puppeteer 的想法,而是在 Node.js 中实现一个实际的 WebRTC 客户端,然后通过 @tensorflow/tfjs-node 直接使用 PoseNet。

为什么不在服务器端使用 puppeteer

在服务器端使用 puppeteer 会带来很多复杂性。除了与多个客户端的活动 WebRTC 连接之外,您现在还必须为每个连接管理一个浏览器(或至少一个选项卡)。因此,您不仅需要考虑与客户端的连接失败时会发生什么,还必须为其他情况做好准备,例如浏览器崩溃、页面崩溃、WebGL 支持(每页)、浏览器中的文档未加载、浏览器实例的内存/CPU 使用率,...

也就是说,让我们来看看你的方法。

方法一:在 puppeteer 中运行 Tensorflow.js

您应该能够通过仅使用 cpu backend 来运行它。在使用任何其他代码之前,您可以像这样设置后端:

tf.setBackend('cpu');

您也许还可以让 WebGL 运行(因为您是 not the only one 在 WebGL 和 puppeteer 方面遇到问题)。但即使你让它运行,你现在正在运行一个 Node.js 脚本来启动一个 Chrome 浏览器,该浏览器在一个网站内启动一个 WebRTC 会话和 Tensorflow.js 训练。复杂性方面,如果出现任何问题,这将非常难以调试...

方法 2:在 puppeteer 和 Node.js 之间传输数据

如果不大幅减速(关于帧的发送和接收),这种方法几乎是不可能的。 puppeteer 需要序列化任何交换的数据。 Node.js 和浏览器环境之间没有共享内存或共享数据对象之类的东西。这意味着您必须序列化每一帧(所有像素......)才能将它们从浏览器环境传输到 Node.js。就性能而言,这可能适用于小图像,但图像越大,效果就会越差。


总而言之,如果您想采用两种方法中的一种,就会引入很多复杂性。因此,让我们看看替代方案。

替代方法:将视频流直接发送到您的服务器

不用puppeteer建立WebRTC连接,可以直接实现WebRTC peer。我从您的问题中读到您担心复杂性,但这可能是值得的麻烦。

要实现 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。您可以先签出存储库。所有示例都可以尝试使用。

【讨论】:

  • 很好的答案,权衡利弊。
  • 非常感谢您的这些见解!尽管我只是对 atm 进行原型设计,但我现在可以看到使用 puppeteer 是如何使事情变得过于复杂的。我会选择node-webrtcsimple-peer 并在某个阶段发布结果。使用@tensorflow/tfjs-node 似乎是唯一的解决方案。
  • @ThomasDondorf 哦,谢谢,这是一个非常有用的提醒,我正要再次走向错误的方向.. node-webrtc 它是 - 现在我明白了 RTCVideoSink 的全部内容.
  • 对于任何想知道结果如何的人:我设法使用@ThomasDondorf 提出的node-webrtcRTCVideoSink 构建了我需要的东西。我为WebRTC 编写了一个自定义信号服务器,它处理连接并产生nodejs 工作线程。唯一剩下的问题是让节点的canvas 库上下文感知 - 现在它不支持多个工作线程。 atm上有一些讨论:github.com/Automattic/node-canvas/issues/1394
  • 我迟到了,但面临着流到服务器和实时进行 3D 姿势估计的类似整体问题。您是否有任何示例代码可以分享您如何使用node-webrtc 实现实时流式传输?会超级有帮助的!您是否还找到了一种使其与 Python 一起使用的方法,因为大多数 SOTA 深度学习都在 Pytorch 或 TF Python API 中。
猜你喜欢
  • 2011-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-16
  • 1970-01-01
  • 1970-01-01
  • 2021-05-08
  • 2012-10-31
相关资源
最近更新 更多