我正在使用Posenet(请参阅浏览器演示here中的)进行关键点检测。我已将其设置为在WebRTC MediaStream上运行,例如:
客户端:在计算机A
上的Chrome标签中运行。初始化WebRTC连接,并将MediaStream发送到服务器。通过WebRTC的数据通道从服务器接收实时关键点数据。
服务器:在计算机B
上的chrome标签中运行,接收WebRTC流并将相应的MediaStream传递给Posenet。 Posenet完成其任务并计算关键点。然后,这些关键点数据将通过WebRTC的DataChannel发送回客户端(如果您有更好的主意,我很高兴)。
问题::我想让服务器从各个客户端接收多个流,并在每个客户端上运行Posenet,向所有客户端发送实时关键点数据。尽管我对使用Chrome的服务器并不感到兴奋,但我现在可以使用puppeteer
和Chrome的无头模式,主要是为了消除WebRTC的复杂性。
我尝试了两种方法,非常支持方法#2
:
在@tensorflow/tfjs
上下文中(即,无头的chrome标签中)运行puppeteer
。但是,由于某些WebGL错误,我似乎无法使PoseNet Browser Demo在无头模式下工作(尽管它确实在非无头模式下工作)。我尝试了以下操作(将args
传递到puppeteer.launch()
以启用WebGL,尽管我没有任何运气-请参见here和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?
最好,我想使用posenet
后端运行@tensorflow/tfjs-node
,以加快计算速度。因此,我将链接puppeteer
和@tensorflow/tfjs-node
,s.t。:
puppeteer-chrome-tab
与客户端对话WebRTC。它使Mediastream对象可用于node
。node
获取此MediaStream并将其传递到posenet
(因此@tensorflow/tfjs-node
),在此处发生机器学习的魔力。 node
然后将检测到的关键点传递回puppeteer-chrome-tab
,后者使用其RTCDataChannel
将其传达给客户端。问题在于,我似乎无法访问puppeteer
内的{strong> {1>}内的MediaStream对象,无法将该对象传递给node
。我只能访问JSHandles
和ElementHandles
。可以将与该句柄关联的 javascript对象传递给posenet
吗?
具体地,会引发此错误:
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)
的{{1}}参数,其结果为ElementHandle。我知道可以使用pixels
的{{3}}访问Javascript对象的 serializable 属性。但是,如果我要通过调用NodeJSKernelBackend.prototype.fromPixels = function (pixels, numChannels) {..}
将puppeteer
的{{1}}(使用方法CanvasRenderingContext2D
传递到imageData
到getImageData()
,则意味着对整个原始图像进行字符串化然后在node
的上下文中重建它。
这给我留下了puppeteer.evaluate(..)
:有什么方法可以直接从node
内部访问question #2
的上下文中的对象(只读),而不必经历例如puppeteer
?
答案 0 :(得分:4)
我建议采用的另一种方法是放弃在服务器端使用puppeteer的想法,而是在Node.js中实现实际的WebRTC客户端,然后通过@tensorflow/tfjs-node
直接使用PoseNet。
在服务器端使用puppeteer会带来很多复杂性。除了与多个客户端的有效WebRTC连接之外,您现在还必须为每个连接管理一个浏览器(或至少一个选项卡)。因此,您不仅要考虑与客户端的连接失败时会发生什么,而且还必须为其他情况做好准备,例如浏览器崩溃,页面崩溃,WebGL支持(每页),浏览器中的文档无法加载,浏览器实例的内存/ CPU使用率,...
也就是说,让我们仔细研究一下方法。
方法1:在操纵up中运行Tensorflow.js
您应该只使用cpu
backend就可以使它运行。您可以在使用任何其他代码之前像这样设置后端:
tf.setBackend('cpu');
您也许还可以使WebGL运行(因为not the only one遇到WebGL和操纵p的问题)。但是,即使您开始运行它,您现在仍在运行Node.js脚本来启动Chrome浏览器,该浏览器将启动WebRTC会话并在网站内进行Tensorflow.js培训。复杂度方面,如果出现任何问题,这将非常很难调试...
方法2:在puppeteer和Node.js之间传输数据
如果不大幅降低速度(关于帧的发送和接收),这种方法几乎是不可能的。人偶需要序列化任何交换的数据。 Node.js和浏览器环境之间没有共享内存或共享数据对象之类的东西。这意味着您必须序列化每帧(所有像素...),才能将它们从浏览器环境传输到Node.js。从性能角度来看,这对于较小的图像可能还可以,但随着图像的增大,效果会变得更糟。
总而言之,如果您想使用两种方法之一,则会引入很多复杂性。因此,让我们看看替代方案。
您可以直接实现WebRTC对等方,而不是使用puppeteer建立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);
您现在有了一个canvas对象,您可以像下面这样使用feed进入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
。您可以先检出存储库。所有示例都可以尝试使用。