question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[wasm] Optimization of tf.browser.fromPixels.

See original GitHub issue

I noticed that tf.browser.fromPixels can be very slow when it spends time in this loop:

    const numPixels = width * height;
    values = new Int32Array(numPixels * numChannels);
    for (let i = 0; i < numPixels; i++) {
      for (let channel = 0; channel < numChannels; ++channel) {
        values[i * numChannels + channel] = vals[i * 4 + channel];
      }
    }

(Also reported by #4218)

I was able to get significantly better performance with my application by calling this function instead:

function convertArrayBufferRGBAToUInt8RGB(buffer) {
  // create view of ArrayBuffer
  const x = new Uint8Array(buffer);
  const n = x.length;
  let i = 0;
  let j = 0;
  // loop through all pixels and shift them over to remove alpha
  while (i < n) {
    x[j] = x[i]; i++; j++;
    x[j] = x[i]; i++; j++;
    x[j] = x[i]; i++; j++;
    i++;
  }
  // return a subset of the original
  return x.slice(0, j);
}

And then calling it like sort of like this:

const fromPixels2DContext = document.createElement('canvas').getContext('2d'); // only call this once
function fromPixelsFast(inputElt, width, height) {
  return tf.tidy(() => {
    fromPixels2DContext.canvas.width = width;
    fromPixels2DContext.canvas.height = height;
    fromPixels2DContext.drawImage(inputElt, 0, 0, width, height);
    const buffer = fromPixels2DContext.getImageData(0, 0, width, height).data.buffer;
    const UInt8RGB = convertArrayBufferRGBAToUInt8RGB(inputArray);
    return tf.tensor3d(UInt8RGB, [height, width, 3]);
  });
}

I think the thing that makes it faster is connected to:

  1. Using Uint8Array instead of UInt32Array (small effect)
  2. The unrolled loop is better optimized by the browser. (medium effect)
  3. It is more efficient to shuffle data in one array than between two arrays due to cache effects. (big effect)

(But I have not done rigorous testing)

It might even be possible to get a little more speed from .subarray instead of .slice to avoid making the copy.

A complete example using this function with blazeface and a custom model on a webcam inside a webworker is available in this repo.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:1
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
vladmandiccommented, Jan 11, 2021

@kylemcdonald This is very much inline in what I’m suggesting in #4218

Two suggestions that can enhance your code:

Try OffscreenCanvas before DOM, it’s slightly faster and works inside web workers. But it’s not supported on Firefox yet.

const fromPixelsCanvas (typeof OffscreenCanvas !== 'undefined') ? new OffscreenCanvas(width, height) : document.createElement('canvas');

Try GL readPixels method first as its quite a bit faster.
And fallback to 2D context and getImageData if cannot get GL context.

const fromPixelsGLContext  = fromPixelsCanvas.getContext('webgl');
const buffer = new Uint8Array(fromPixelsCanvas.width * fromPixelsCanvas.height * 4);
fromPixelsGLContext.readPixels(0, 0, fromPixelsCanvas.width, fromPixelsCanvas.height, gl.RGBA, gl.UNSIGNED_BYTE, buffer);

But…one issue here would be that getImageData returns data starting top/left and readPixels returns data starting bottom/left, so processing loop needs to be inverted.

Of course, best method would be to implement fromPixels directly in WASM backend just like it’s implemented in WebGL backend.

0reactions
annxingyuancommented, Feb 16, 2021

Thanks for reporting this @kylemcdonald ! I’m going to close this as a duplicate of https://github.com/tensorflow/tfjs/issues/4218 - which was created 1 day earlier 😃

Thanks for the greaat discussion on this issue - this will be a really helpful reference for us!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Tensorflow.js tf.browser.fromPixels() Function
The tf.browser.fromPixels() function is used to creates a Tensor of pixels values of an specified image. Syntax: tf.browser.
Read more >
TensorFlow.js API
This function returns the shape of the result of an operation between two tensors of size s0 and s1 performed with broadcast. Parameters:...
Read more >
How can I get this basic example working of TensorFlow.js ...
forEach(canvas => { const xs = tf.browser.fromPixels(canvas); const ys = tf.tensor([1]); // output 1, since this canvas is from the `t` ...
Read more >
Run TensorFlow Models in the Browser | by André Ribeiro
fromPixels ' converts the canvas data into a Tensor (line 8). 'tf.image.resizeBilinear' resizes the image from the canvas size to the model size ......
Read more >
End-to-End ML Solutions using TensorFlow.js
In the above code example, an HTML image is converted into a tensor using the tf.browser.fromPixels JavaScript function. In Listing 6-4b, the same...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found