optimized canvas handling

pull/293/head
Vladimir Mandic 2020-11-02 17:25:35 -05:00
parent 3072e3a5d2
commit eaf603aa26
1 changed files with 49 additions and 40 deletions

View File

@ -61,10 +61,13 @@ class Human {
this.version = app.version; this.version = app.version;
this.defaults = defaults; this.defaults = defaults;
this.config = defaults; this.config = defaults;
this.fx = (tf.ENV.flags.IS_BROWSER && (typeof document !== 'undefined')) ? new fxImage.Canvas() : null; this.fx = null;
this.state = 'idle'; this.state = 'idle';
this.numTensors = 0; this.numTensors = 0;
this.analyzeMemoryLeaks = false; this.analyzeMemoryLeaks = false;
// internal temp canvases
this.inCanvas = null;
this.outCanvas = null;
// object that contains all initialized models // object that contains all initialized models
this.models = { this.models = {
facemesh: null, facemesh: null,
@ -160,23 +163,33 @@ class Human {
} }
tfImage(input) { tfImage(input) {
// let imageData; let tensor;
let filtered; if (input instanceof tf.Tensor) {
tensor = tf.clone(input);
} else {
const originalWidth = input.naturalWidth || input.videoWidth || input.width || (input.shape && (input.shape[1] > 0)); const originalWidth = input.naturalWidth || input.videoWidth || input.width || (input.shape && (input.shape[1] > 0));
const originalHeight = input.naturalHeight || input.videoHeight || input.height || (input.shape && (input.shape[2] > 0)); const originalHeight = input.naturalHeight || input.videoHeight || input.height || (input.shape && (input.shape[2] > 0));
let targetWidth = originalWidth; let targetWidth = originalWidth;
let targetHeight = originalHeight; let targetHeight = originalHeight;
if (this.fx && this.config.filter.enabled && !(input instanceof tf.Tensor)) {
if (this.config.filter.width > 0) targetWidth = this.config.filter.width; if (this.config.filter.width > 0) targetWidth = this.config.filter.width;
else if (this.config.filter.height > 0) targetWidth = originalWidth * (this.config.filter.height / originalHeight); else if (this.config.filter.height > 0) targetWidth = originalWidth * (this.config.filter.height / originalHeight);
if (this.config.filter.height > 0) targetHeight = this.config.filter.height; if (this.config.filter.height > 0) targetHeight = this.config.filter.height;
else if (this.config.filter.width > 0) targetHeight = originalHeight * (this.config.filter.width / originalWidth); else if (this.config.filter.width > 0) targetHeight = originalHeight * (this.config.filter.width / originalWidth);
const offscreenCanvas = (typeof OffscreenCanvas !== 'undefined') ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement('canvas'); if (!this.inCanvas || (this.inCanvas.width !== originalWidth) || (this.inCanvas.height !== originalHeight)) {
if (offscreenCanvas.width !== targetWidth) offscreenCanvas.width = targetWidth; this.inCanvas = (typeof OffscreenCanvas !== 'undefined') ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement('canvas');
if (offscreenCanvas.height !== targetHeight) offscreenCanvas.height = targetHeight; if (this.inCanvas.width !== targetWidth) this.inCanvas.width = targetWidth;
const ctx = offscreenCanvas.getContext('2d'); if (this.inCanvas.height !== targetHeight) this.inCanvas.height = targetHeight;
}
const ctx = this.inCanvas.getContext('2d');
if (input instanceof ImageData) ctx.putImageData(input, 0, 0); if (input instanceof ImageData) ctx.putImageData(input, 0, 0);
else ctx.drawImage(input, 0, 0, originalWidth, originalHeight, 0, 0, offscreenCanvas.width, offscreenCanvas.height); else ctx.drawImage(input, 0, 0, originalWidth, originalHeight, 0, 0, this.inCanvas.width, this.inCanvas.height);
if (this.config.filter.enabled) {
if (!this.outCanvas || (this.inCanvas.width !== this.outCanvas.width) || (this.inCanvas.height !== this.outCanvas.height)) {
this.outCanvas = (typeof OffscreenCanvas !== 'undefined') ? new OffscreenCanvas(this.inCanvas.width, this.inCanvas.height) : document.createElement('canvas');
if (this.outCanvas.width !== this.inCanvas.width) this.outCanvas.width = this.inCanvas.width;
if (this.outCanvas.height !== this.inCanvas.height) this.outCanvas.height = this.inCanvas.height;
}
if (!this.fx) this.fx = (tf.ENV.flags.IS_BROWSER && (typeof document !== 'undefined')) ? new fxImage.Canvas({ canvas: this.outCanvas }) : null;
this.fx.reset(); this.fx.reset();
this.fx.addFilter('brightness', this.config.filter.brightness); // must have at least one filter enabled this.fx.addFilter('brightness', this.config.filter.brightness); // must have at least one filter enabled
if (this.config.filter.contrast !== 0) this.fx.addFilter('contrast', this.config.filter.contrast); if (this.config.filter.contrast !== 0) this.fx.addFilter('contrast', this.config.filter.contrast);
@ -192,24 +205,20 @@ class Human {
if (this.config.filter.technicolor) this.fx.addFilter('technicolor'); if (this.config.filter.technicolor) this.fx.addFilter('technicolor');
if (this.config.filter.polaroid) this.fx.addFilter('polaroid'); if (this.config.filter.polaroid) this.fx.addFilter('polaroid');
if (this.config.filter.pixelate !== 0) this.fx.addFilter('pixelate', this.config.filter.pixelate); if (this.config.filter.pixelate !== 0) this.fx.addFilter('pixelate', this.config.filter.pixelate);
filtered = this.fx.apply(offscreenCanvas); this.outCanvas = this.fx.apply(this.inCanvas);
} }
let tensor; if (!this.outCanvas) this.outCanvas = this.inCanvas;
if (input instanceof tf.Tensor) {
tensor = tf.clone(input);
} else {
const canvas = filtered || input;
let pixels; let pixels;
if ((this.config.backend === 'webgl') || (canvas instanceof ImageData)) { if ((this.config.backend === 'webgl') || (this.outCanvas instanceof ImageData)) {
// tf kernel-optimized method to get imagedata, also if input is imagedata, just use it // tf kernel-optimized method to get imagedata, also if input is imagedata, just use it
pixels = tf.browser.fromPixels(canvas); pixels = tf.browser.fromPixels(this.outCanvas);
} else { } else {
// cpu and wasm kernel does not implement efficient fromPixels method nor we can use canvas as-is, so we do a silly one more canvas // cpu and wasm kernel does not implement efficient fromPixels method nor we can use canvas as-is, so we do a silly one more canvas
const tempCanvas = (typeof OffscreenCanvas !== 'undefined') ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement('canvas'); const tempCanvas = (typeof OffscreenCanvas !== 'undefined') ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement('canvas');
tempCanvas.width = targetWidth; tempCanvas.width = targetWidth;
tempCanvas.height = targetHeight; tempCanvas.height = targetHeight;
const tempCtx = tempCanvas.getContext('2d'); const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(canvas, 0, 0); tempCtx.drawImage(this.outCanvas, 0, 0);
const data = tempCtx.getImageData(0, 0, targetWidth, targetHeight); const data = tempCtx.getImageData(0, 0, targetWidth, targetHeight);
pixels = tf.browser.fromPixels(data); pixels = tf.browser.fromPixels(data);
} }
@ -218,7 +227,7 @@ class Human {
pixels.dispose(); pixels.dispose();
casted.dispose(); casted.dispose();
} }
return { tensor, canvas: this.config.filter.return ? filtered : null }; return { tensor, canvas: this.config.filter.return ? this.outCanvas : null };
} }
async detect(input, userConfig = {}) { async detect(input, userConfig = {}) {