是否可以使用 puppeteer 将 Javascript 对象传递给 nodejs?
Is it possible to use puppeteer to pass a Javascript object to nodejs?
背景
我正在使用 Posenet (see the in browser demo here) 进行关键点检测。我已经在 WebRTC MediaStream 上将其设置为 运行,s.t.:
客户端: 运行s 在计算机 A
上的 chrome 选项卡中。初始化 WebRTC 连接并将 MediaStream 发送到 Server。通过 WebRTC 的 DataChannel 从 Server 接收实时关键点数据。
服务器: 运行s 在机器 chrome 选项卡中 B
,接收 WebRTC 流并将相应的 MediaStream 传递给 Posenet . Posenet 做它的事情并计算关键点。这个关键点数据然后通过 WebRTC 的 DataChannel 发送回 客户端 (如果你有更好的主意,我洗耳恭听)。
问题: 我想让服务器接收来自不同客户端的多个流和 运行 每个流上的 Posenet,向所有客户端发送实时关键点数据。虽然我对使用 Chrome 的服务器并不感到兴奋,但我现在可以使用 puppeteer
和 Chrome 的无头模式,主要是为了抽象出 WebRTC 的复杂性。
方法
我尝试了两种方法,非常支持#2
:
方法 #1
运行 @tensorflow/tfjs
在 puppeteer
上下文中(即在无头 chrome 选项卡中)。但是,我似乎无法获得 PoseNet Browser Demo working in headless mode, due to some WebGL error (it does work in non-headless mode though). I tried the following (passing args
to puppeteer.launch()
to enable WebGL, though I haven't had any luck - see here and here 作为参考):
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
最好,我想 运行 posenet
使用 @tensorflow/tfjs-node
后端,以加速计算。因此,我会 link puppeteer
和 @tensorflow/tfjs-node
, s.t.:
puppeteer-chrome-tab
与 客户端 对话 WebRTC。它使 Mediastream 对象可用于 node
。
node
获取此 MediaStream 并将其传递给 posenet
(因此 @tensorflow/tfjs-node
),机器学习魔法就在这里发生。 node
然后将检测到的关键点传递回 puppeteer-chrome-tab
,后者使用其 RTCDataChannel
将它们传回给 client.
问题
问题是我似乎无法在 node
中访问 puppeteer
的 MediaStream 对象 ,无法将此对象传递给 posenet
.我只能访问 JSHandles
and ElementHandles
。是否可以将与句柄关联的 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. I am aware that I can access serializable properties of a Javascript object, using puppeteer
's page.evaluate
。但是,如果我要传递 CanvasRenderingContext2D
的 imageData
(使用方法 getImageData()
通过调用 puppeteer.evaluate(..)
传递给 node
,这意味着将整个原始图像字符串化然后在 node
的上下文中重建它。
这给我留下了 question #2
:有什么方法可以直接在 node
中使来自 puppeteer
上下文的对象可访问(只读),而不必去通过例如puppeteer.evaluate(..)
?
我推荐另一种方法是放弃在服务器端使用 puppeteer 的想法,而是在 Node.js 中实现一个实际的 WebRTC 客户端,然后通过 @tensorflow/tfjs-node
.[= 直接使用 PoseNet 28=]
为什么不在服务器端使用 puppeteer
在服务器端使用 puppeteer 会带来很多复杂性。除了与多个客户端的活动 WebRTC 连接之外,您现在还必须为每个连接管理一个浏览器(或至少一个选项卡)。因此,您不仅要考虑与客户端的连接失败时会发生什么,还必须为其他情况做好准备,例如浏览器崩溃、页面崩溃、WebGL 支持(每页)、浏览器中的文档未加载、 memory/CPU 浏览器实例的使用,...
话虽如此,让我们回顾一下您的方法。
方法 1:运行Tensorflow.js inside puppeteer
您应该能够通过仅使用 cpu
backend 获得此 运行。在使用任何其他代码之前,您可以像这样设置后端:
tf.setBackend('cpu');
您也可以获取 WebGL 运行(因为您 在使用 WebGL 和 puppeteer 时遇到问题)。但是,即使你得到它 运行,你现在也是 运行 一个 Node.js 脚本来启动一个 Chrome 浏览器,该浏览器启动一个 WebRTC 会话和 Tensorflow.js 在一个内部训练网站。复杂性方面,如果出现任何问题,这将非常难以调试...
方法 2:在 puppeteer 和 Node.js
之间传输数据
如果不大幅降低速度(关于帧的发送和接收),这种方法几乎是不可能的。 puppeteer 需要序列化任何交换的数据。 Node.js 和浏览器环境之间没有共享内存或共享数据对象这样的东西。这意味着您必须序列化每个帧(所有像素...)以将它们从浏览器环境传输到 Node.js。就性能而言,这对于小图片可能没问题,但当你的图片越大时效果会越差。
总而言之,如果您想采用两种方法中的一种,就会引入很多复杂性。因此,让我们看看替代方案。
替代方法:将您的视频流直接发送到您的服务器
您可以直接实现 WebRTC 对等点,而不是使用 puppeteer 建立 WebRTC 连接。我从你的问题中读到你害怕复杂性,但这可能是值得的。
要实现 WebRTC 服务器,您可以使用库 node-webrtc
, which allows to implement a WebRTC peer on the server-side. There are multiple examples, of which one is very interesting for your use case. This is the video-compositing
示例,它在客户端(浏览器)和服务器 (Node.js) 之间建立连接以流式传输视频。然后服务器会修改发送的帧,并在它们上面放一个"watermark"。
代码示例
以下代码显示了来自 video-compositing
example. The code reads a frame from the input stream and creates a 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);
您现在有一个 canvas 对象,您可以像这样将其用于 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
。您可以先签出存储库。所有示例都已准备好尝试和使用。
背景
我正在使用 Posenet (see the in browser demo here) 进行关键点检测。我已经在 WebRTC MediaStream 上将其设置为 运行,s.t.:
客户端: 运行s 在计算机 A
上的 chrome 选项卡中。初始化 WebRTC 连接并将 MediaStream 发送到 Server。通过 WebRTC 的 DataChannel 从 Server 接收实时关键点数据。
服务器: 运行s 在机器 chrome 选项卡中 B
,接收 WebRTC 流并将相应的 MediaStream 传递给 Posenet . Posenet 做它的事情并计算关键点。这个关键点数据然后通过 WebRTC 的 DataChannel 发送回 客户端 (如果你有更好的主意,我洗耳恭听)。
问题: 我想让服务器接收来自不同客户端的多个流和 运行 每个流上的 Posenet,向所有客户端发送实时关键点数据。虽然我对使用 Chrome 的服务器并不感到兴奋,但我现在可以使用 puppeteer
和 Chrome 的无头模式,主要是为了抽象出 WebRTC 的复杂性。
方法
我尝试了两种方法,非常支持#2
:
方法 #1
运行 @tensorflow/tfjs
在 puppeteer
上下文中(即在无头 chrome 选项卡中)。但是,我似乎无法获得 PoseNet Browser Demo working in headless mode, due to some WebGL error (it does work in non-headless mode though). I tried the following (passing args
to puppeteer.launch()
to enable WebGL, though I haven't had any luck - see here and here 作为参考):
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
最好,我想 运行 posenet
使用 @tensorflow/tfjs-node
后端,以加速计算。因此,我会 link puppeteer
和 @tensorflow/tfjs-node
, s.t.:
puppeteer-chrome-tab
与 客户端 对话 WebRTC。它使 Mediastream 对象可用于node
。node
获取此 MediaStream 并将其传递给posenet
(因此@tensorflow/tfjs-node
),机器学习魔法就在这里发生。node
然后将检测到的关键点传递回puppeteer-chrome-tab
,后者使用其RTCDataChannel
将它们传回给 client.
问题
问题是我似乎无法在 node
中访问 puppeteer
的 MediaStream 对象 ,无法将此对象传递给 posenet
.我只能访问 JSHandles
and ElementHandles
。是否可以将与句柄关联的 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. I am aware that I can access serializable properties of a Javascript object, using puppeteer
's page.evaluate
。但是,如果我要传递 CanvasRenderingContext2D
的 imageData
(使用方法 getImageData()
通过调用 puppeteer.evaluate(..)
传递给 node
,这意味着将整个原始图像字符串化然后在 node
的上下文中重建它。
这给我留下了 question #2
:有什么方法可以直接在 node
中使来自 puppeteer
上下文的对象可访问(只读),而不必去通过例如puppeteer.evaluate(..)
?
我推荐另一种方法是放弃在服务器端使用 puppeteer 的想法,而是在 Node.js 中实现一个实际的 WebRTC 客户端,然后通过 @tensorflow/tfjs-node
.[= 直接使用 PoseNet 28=]
为什么不在服务器端使用 puppeteer
在服务器端使用 puppeteer 会带来很多复杂性。除了与多个客户端的活动 WebRTC 连接之外,您现在还必须为每个连接管理一个浏览器(或至少一个选项卡)。因此,您不仅要考虑与客户端的连接失败时会发生什么,还必须为其他情况做好准备,例如浏览器崩溃、页面崩溃、WebGL 支持(每页)、浏览器中的文档未加载、 memory/CPU 浏览器实例的使用,...
话虽如此,让我们回顾一下您的方法。
方法 1:运行Tensorflow.js inside puppeteer
您应该能够通过仅使用 cpu
backend 获得此 运行。在使用任何其他代码之前,您可以像这样设置后端:
tf.setBackend('cpu');
您也可以获取 WebGL 运行(因为您
方法 2:在 puppeteer 和 Node.js
之间传输数据如果不大幅降低速度(关于帧的发送和接收),这种方法几乎是不可能的。 puppeteer 需要序列化任何交换的数据。 Node.js 和浏览器环境之间没有共享内存或共享数据对象这样的东西。这意味着您必须序列化每个帧(所有像素...)以将它们从浏览器环境传输到 Node.js。就性能而言,这对于小图片可能没问题,但当你的图片越大时效果会越差。
总而言之,如果您想采用两种方法中的一种,就会引入很多复杂性。因此,让我们看看替代方案。
替代方法:将您的视频流直接发送到您的服务器
您可以直接实现 WebRTC 对等点,而不是使用 puppeteer 建立 WebRTC 连接。我从你的问题中读到你害怕复杂性,但这可能是值得的。
要实现 WebRTC 服务器,您可以使用库 node-webrtc
, which allows to implement a WebRTC peer on the server-side. There are multiple examples, of which one is very interesting for your use case. This is the video-compositing
示例,它在客户端(浏览器)和服务器 (Node.js) 之间建立连接以流式传输视频。然后服务器会修改发送的帧,并在它们上面放一个"watermark"。
代码示例
以下代码显示了来自 video-compositing
example. The code reads a frame from the input stream and creates a 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);
您现在有一个 canvas 对象,您可以像这样将其用于 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
。您可以先签出存储库。所有示例都已准备好尝试和使用。