4

I have raw image data (1000 x 1000 pixels x 3 bytes per pixel) in Python, that I need to send to a HTML page in realtime, at 20 frames per second (this is 57 MB of data per second!).

I already tried the multipart/x-mixed-replace method (as seen in Sending RGB image data from Python numpy to a browser HTML page), with various encoding: BMP, PNG, JPG. It is quite intensive for the CPU, so I'm trying alternatives.

I am now getting the raw image data directly in JavaScript with binary XHR HTTP requests.

Question: How to (CPU-efficiently) decode binary RGB data from dozens of binary XHR HTTP requests into a <video> or <img> or <canvas> on a HTML page, with Javascript?

oReq = new XMLHttpRequest();
oReq.open("GET", "/imagedata", true);
oReq.responseType = "arraybuffer";
oReq.onload = function (oEvent) {
  var arrayBuffer = oReq.response;
  if (arrayBuffer) {
    var byteArray = new Uint8Array(arrayBuffer);
    // update displayed image
  }
};
oReq.send(null);

Edit: The method given in @VC.One's comment: var byteArray = new Uint8ClampedArray(arrayBuffer); var imgData = new ImageData(byteArray, 1000, 1000); var ctx = myCanvas.getContext('2d'); ctx.putImageData(imgData, 0, 0); works and is lighter for the CPU: 5%-6% for the server sending the data vs. 8%-20% with BMP/PNG/JPG encoding. But Chromium now has two processes in parallel for this task, each of them ~ 15% CPU. So the total performance is not much better. Do you see other potential alternatives to efficiently send raw image data from a Python or C++ HTTP server to Chromium?

Also new ImageData(...) requires a 1000x1000x4 bytes array for R, G, B, A. This requires that I send alpha channel that I don't need; maybe there's a way with ImageData to only pass a RGB (nxnx3 bytes) array?


Edit 2: the real bottleneck is the XHR HTTP requests between my process #1 and process #2 (Chrome) on the same computer for up to 100 MB/sec. Is there a more direct inter process communication possible between process #1 and Chrome? (some sort of direct memory access?)

See Chrome + another process: interprocess communication faster than HTTP / XHR requests?

Basj
  • 41,386
  • 99
  • 383
  • 673
  • 1
    Try as: `var byteArray = new Uint8ClampedArray(arrayBuffer); var imgData = new ImageData(byteArray, 1000, 1000); var ctx = myCanvas.getContext('2d'); ctx.putImageData(imgData, 0, 0);` where **myCanvas** will be some Canvas element that you get by ID. – VC.One Jun 22 '22 at 13:26
  • Thanks a lot @VC.One for your input. It works indeed, I have edited the question with the tests linked with this method. Do you have an idea for the RGBA vs RGB? to save 1/4 of the bandwidth (I don't need alpha channel) – Basj Jun 23 '22 at 07:44
  • PS @VC.One: do you think there is an even more direct way to send a big load of data from my process to Chrome process (on same computer) without using XHR HTTP requests? Here the 60 MB/sec XHR HTTP request seems to be a bottleneck. (see my Edit 2). – Basj Jun 23 '22 at 09:49
  • See also: [Chrome + another process: interprocess communication faster than HTTP / XHR requests?](https://stackoverflow.com/questions/72731175/chrome-another-process-interprocess-communication-faster-than-http-xhr-requ) – Basj Jun 25 '22 at 08:51
  • **(1)** About RGB/A, well the canvas expects RGBA so you still need a value for alpha. Yes you could send RGB but you'll need a For loop to manually add your 3 RGB values + 1 more (0xFF) Alpha. Because `new ImageData` expects 4-values per pixel. PS: If you just create as `new ImageData(width, height)` then your For-loop simply updates 3 values + skip 1 (since alpha is already written), for every integer. – VC.One Jun 25 '22 at 09:12
  • **(2)** Just read [your other link](https://stackoverflow.com/q/72731175/2057709)... As for processes I'm only familiar with desktop programming (using **std in/out** to directly send data values between two apps). You said something about C++, well if the image processing itself is contained inside the C++ functions (no external process/tool/API is accessed) then you could run C++ in-browser using WASM. Try it out at: [WasmFiddle](https://wasdk.github.io/WasmFiddle/)... if useful to you then I'll try explain how use it in your own page. – VC.One Jun 25 '22 at 09:42
  • Thanks @VC.One! About (2) I thought about WASM, but in fact I need to query specific industrial hardware Firewire or USB camera (using a hardware specific DLL), I don't know it will be possible to do that from WASM, to query Firewire or USB devices. I'll study this though! Do you think it might be possible? – Basj Jun 25 '22 at 10:19
  • About (1), @VC.One, I thought about sending just 3 bytes per pixel with HTTP and do a JS `for` loop to update this into 4 bytes in the browser, but won't this JS `for` loop be even slower? Or do have an idea in mind to do a super fast "add a 1-byte 255 every 3 bytes" `for` loop? – Basj Jun 25 '22 at 10:22
  • OK. **(1)** Can the browser not detect the camera as a webcam? Maybe a virtual/software webcam, (visible to browser as a hardware device) but the virtual cam is receiving its input from the USB camera, could work? Investigate the feasibilty (_ie:_ what's available on freeware sites and does it meet expectations). **(2)** Consider using the clipboard API... You make a C# app to receive bytes from camera and writes them into clipboard, the JS side could detect clip updates and "paste" (or read) the data into your app. **(3)** Use a tool like FFmpeg to encode camera stream to a compressed format. – VC.One Jun 25 '22 at 14:59
  • PS: Yes the For/while loop is fast in Chrome for simple read or write of values. You can use "typed" arrays (set to a specific data-type, like _uint_) for a more optimized speed. – VC.One Jun 25 '22 at 15:06
  • Check out my answer to a pretty unrelated question here: https://stackoverflow.com/a/72757378/2284136 Eventhough the question is unrelated, my answer goes through a lot of the topic. You could, for example, use a [MediaStreamTrackGenerator](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrackGenerator/MediaStreamTrackGenerator) to which you stream raw video frames from your server and process them into a video. My answer also includes some thoughts about VP8 (basically webm) encoding regarding [VideoFrames](https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame) – Swiffy Jun 25 '22 at 21:34
  • @Basj **(1)** Any luck getting the browser to detect USB camera or maybe even some software proxy connected to it? **(2)** I thought your Python or C++ (which is it?) server was running from your hard drive and thus the output could be written as file somewhere on the C:\ drive, is that correct? **(3)** Why doesn't your app just receive RGB and put it into clipboard for instant access on JS side (no HTTP requests)? – VC.One Jun 30 '22 at 13:23

1 Answers1

0

There is another way, but it will take a lot of new material. First, use requests on python to send your info to a page, say /internals/port by doing something like this:

import requests

url = 'http://random.com/internals/port'
query = {'field': value}
res = requests.post(url, data=query)

then, use a fetch() from your website in js. for example:

fetch('/internals/port').then(res=>{
res.text().then(text=>{
//here text is your data. 
// you can also use an await fetch statement so things aren't wrapped up in the two functions. 
})
})

Try instead to send your info in 64bit since you can use data:image/png;64bit as its own 64bit parser:

data:image/png;64bit,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

if you insist on using binary, then try using a filereader to parse it into 64bit,

var reader = new FileReader(
  reader.onload = (function(self) {
    return function(e) {
      document.getElementById("img").src = e.target.result;
    }
  })(this);
  reader.readAsDataURL(new Blob([r]));

it will take a bit more time but still worth it.

you can just use an <img> tag in your html and set the url to a data:image/png;base64 data url that you generate. Still, coming back on your comment on the ImageData() class you can just set the alpha to 1, or 100%, since it is the opacity, so just incluce a 1 at the end of the array when you feed it into new ImageData(). Good luck on your project, I hope you successfully find a way to do what you want to, whether it be with my answer or with another. I believe that I have answered all your questions, and as a final suggestion, try changing your python backend to node.js, and use express requests to post images that can be accessed in pretty much the same way as using requests in python. It would be much more efficient and much faster.

lolBOT V9.17
  • 141
  • 10
  • 1
    _"Then, use a `fetch()` from your website in JS."_ Won't that mean HTTP traffic? The asker is trying to **avoid sending 57mb/sec** if they send raw RGB frames of `1000x1000x3` bytes. They have video data as RGB so it really should be on `` for handling their 20 images/frames per second. It'll be strange for an Image tag to load pixels 20 times every second. – VC.One Jun 30 '22 at 13:27
  • Well it isn't really possible to send data from python to a frontend application without an http api. Also, the fetch will be on the client end, so it will not take up any cpu on the server. – lolBOT V9.17 Jul 15 '22 at 01:40
  • Maybe use Web sockets instead of tons of client initiated http requests – Tucker Oct 17 '22 at 05:58