From 827a04e2d092517840dbb12b5a423a746ab4d73b Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 18 Oct 2020 14:14:05 -0400 Subject: [PATCH] compatibility notes --- README.md | 12 +++++++++++- config.js | 1 + demo/browser.js | 1 + demo/worker.js | 4 ++++ src/human.js | 22 ++++++++-------------- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 56a3971a..267550fe 100644 --- a/README.md +++ b/README.md @@ -229,7 +229,8 @@ config = { scoped: false, // enable scoped runs // some models *may* have memory leaks, this wrapps everything in a local scope at a cost of performance // typically not needed - filter: { + videoOptimized: true, // perform additional optimizations when input is video, must be disabled for images + filter: { // note: image filters are only available in Browser environments and not in NodeJS as they require WebGL for processing enabled: true, // enable image pre-processing filters return: true, // return processed canvas imagedata in result brightness: 0, // range: -1 (darken) to 1 (lighten) @@ -432,6 +433,15 @@ For performance details, see output of `result.performance` object during runtim
+## Limitations + +`Human` library can be used in any modern Browser or NodeJS environment, but there are several items to be aware of: + +- **NodeJS**: Due to a missing feature in `tfjs-node`, only some models are available +- **Browser**: `filters` module cannot be used when using web workers + +
+ ## Credits - Face Detection: [**MediaPipe BlazeFace**](https://drive.google.com/file/d/1f39lSzU5Oq-j_OXgS67KfN5wNsoeAZ4V/view) diff --git a/config.js b/config.js index f2557433..01b59669 100644 --- a/config.js +++ b/config.js @@ -7,6 +7,7 @@ export default { scoped: false, // enable scoped runs // some models *may* have memory leaks, this wrapps everything in a local scope at a cost of performance // typically not needed + videoOptimized: true, // perform additional optimizations when input is video, must be disabled for images filter: { enabled: true, // enable image pre-processing filters return: true, // return processed canvas imagedata in result diff --git a/demo/browser.js b/demo/browser.js index dbb6c158..0a6fc3bd 100644 --- a/demo/browser.js +++ b/demo/browser.js @@ -28,6 +28,7 @@ const ui = { const config = { backend: 'webgl', // if you want to use 'wasm' backend, enable script load of tf and tf-backend-wasm in index.html filter: { enabled: true, brightness: 0, contrast: 0, sharpness: 0, blur: 0, saturation: 0, hue: 0, negative: false, sepia: false, vintage: false, kodachrome: false, technicolor: false, polaroid: false, pixelate: 0 }, + videoOptimized: true, face: { enabled: true, detector: { maxFaces: 10, skipFrames: 10, minConfidence: 0.5, iouThreshold: 0.3, scoreThreshold: 0.7 }, diff --git a/demo/worker.js b/demo/worker.js index 94366e5a..be030858 100644 --- a/demo/worker.js +++ b/demo/worker.js @@ -1,6 +1,7 @@ import human from '../dist/human.esm.js'; let config; +let busy = false; const log = (...msg) => { // eslint-disable-next-line no-console @@ -8,6 +9,8 @@ const log = (...msg) => { }; onmessage = async (msg) => { + if (busy) return; + busy = true; // worker.postMessage({ image: image.data.buffer, width: canvas.width, height: canvas.height, config }, [image.data.buffer]); const image = new ImageData(new Uint8ClampedArray(msg.data.image), msg.data.width, msg.data.height); config = msg.data.config; @@ -19,4 +22,5 @@ onmessage = async (msg) => { log('Worker thread error:', err.message); } postMessage(result); + busy = false; }; diff --git a/src/human.js b/src/human.js index 1d0cee28..057bd59f 100644 --- a/src/human.js +++ b/src/human.js @@ -74,15 +74,6 @@ function mergeDeep(...objects) { function sanity(input) { if (!input) return 'input is not defined'; - if (!(input instanceof tf.Tensor) - || (tf.ENV.flags.IS_BROWSER - && (input instanceof ImageData || input instanceof HTMLImageElement || input instanceof HTMLCanvasElement || input instanceof HTMLVideoElement || input instanceof HTMLMediaElement))) { - const width = input.naturalWidth || input.videoWidth || input.width || (input.shape && (input.shape[1] > 0)); - if (!width || (width === 0)) return 'input is empty'; - } - if (tf.ENV.flags.IS_BROWSER && (input instanceof HTMLVideoElement || input instanceof HTMLMediaElement)) { - if (input.readyState && (input.readyState <= 2)) return 'input is not ready'; - } if (tf.ENV.flags.IS_NODE && !(input instanceof tf.Tensor)) { return 'input must be a tensor'; } @@ -127,15 +118,18 @@ function tfImage(input) { let filtered; if (tf.ENV.flags.IS_BROWSER && config.filter.enabled && !(input instanceof tf.Tensor)) { const width = input.naturalWidth || input.videoWidth || input.width || (input.shape && (input.shape[1] > 0)); - const height = input.naturalHeight || input.videoHeight || input.Height || (input.shape && (input.shape[2] > 0)); - // if (!offscreenCanvas) offscreenCanvas = new OffscreenCanvas(width, height); + const height = input.naturalHeight || input.videoHeight || input.height || (input.shape && (input.shape[2] > 0)); + if (!offscreenCanvas) offscreenCanvas = new OffscreenCanvas(width, height); + /* if (!offscreenCanvas) { offscreenCanvas = document.createElement('canvas'); offscreenCanvas.width = width; offscreenCanvas.height = height; } + */ const ctx = offscreenCanvas.getContext('2d'); - ctx.drawImage(input, 0, 0, width, height, 0, 0, offscreenCanvas.width, offscreenCanvas.height); + if (input instanceof ImageData) ctx.putImageData(input, 0, 0); + else ctx.drawImage(input, 0, 0, width, height, 0, 0, offscreenCanvas.width, offscreenCanvas.height); if (!fx) fx = new fxImage.Canvas(); else fx.reset(); fx.addFilter('brightness', config.filter.brightness); // must have at least one filter enabled @@ -173,8 +167,8 @@ async function detect(input, userConfig = {}) { let timeStamp; timeStamp = now(); - const shouldOverride = tf.ENV.flags.IS_NODE || (tf.ENV.flags.IS_BROWSER && !((input instanceof HTMLVideoElement) || (input instanceof HTMLMediaElement))); - config = mergeDeep(defaults, userConfig, shouldOverride ? override : {}); + config = mergeDeep(defaults, userConfig); + if (!config.videoOptimized) config = mergeDeep(config, override); perf.config = Math.trunc(now() - timeStamp); // sanity checks