Background
I am using Posenet (see the in browser demo here) for keypoint detection. I have set it up to run on a WebRTC MediaStream, s.t.:
Client: Runs in a chrome tab on machine A. Initializes WebRTC connection and sends a MediaStream to Server. Receives back real time keypoint data from Server via WebRTC's DataChannel.
Server: Runs in a chrome tab on machine B, receives a WebRTC stream and passes the corresponding MediaStream to Posenet. Posenet does its thing and computes keypoints. This keypoint data is then send back to the client via WebRTC's DataChannel (if you have a better idea, I'm all ears).
Problem: I would like to have the server receive multiple streams from various clients and run Posenet on each, sending real time keypoint data to all clients. Though I'm not thrilled about the server utilizing Chrome, I am fine with using puppeteer and Chrome's headless mode for now, mainly to abstract away WebRTC's complexity.
Approaches
I have tried two approaches, being heavily in favor of approach #2:
Approach #1
Run @tensorflow/tfjs inside the puppeteer context (i.e. inside a headless chrome tab). However, I cannot seem to get the 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 for reference):
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();
In headless mode, I am receiving this error message.
0: JSHandle:Initialization of backend webgl failed
0: JSHandle:Error: WebGL is not supported on this device
This leaves me with question #1: How do I enable WebGL in puppeteer?
Approach #2
Preferably, I would like to run posenet using the @tensorflow/tfjs-node backend, to accelerate computation. Therefore, I would to link puppeteer and @tensorflow/tfjs-node, s.t.:
- The
puppeteer-chrome-tabtalks WebRTC with the client. It makes a Mediastream object available tonode. nodetakes this MediaStream and passes it toposenet, (and thus@tensorflow/tfjs-node), where the machine learning magic happens.nodethen passes detected keypoints back topuppeteer-chrome-tabwhich uses itsRTCDataChannelto communicate them back to client.
Problem
The problem is that I cannot seem to get access to puppeteer's MediaStream object within node, to pass this object to posenet. I'm only getting access to JSHandles and ElementHandles. Is it possible to pass the javascript object associated with the handle to node?
Concretely, this error is thrown:
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)
Logging the pixels argument that is passed to NodeJSKernelBackend.prototype.fromPixels = function (pixels, numChannels) {..}, it evaluates to an ElementHandle. I am aware that I can access serializable properties of a Javascript object, using puppeteer's page.evaluate. However, if I were to pass the CanvasRenderingContext2D's imageData (using the method getImageData() to node by calling puppeteer.evaluate(..), this means stringifying an entire raw image and then reconstructing it in node's context.
This leaves me with question #2: Is there any way to make an object from puppeteer's context accessible (read-only) directly inside node, without having to go through e.g. puppeteer.evaluate(..)?