2021-02-08 17:39:09 +01:00
|
|
|
import { log } from './log';
|
2021-03-06 16:38:04 +01:00
|
|
|
import * as sysinfo from './sysinfo';
|
2020-11-18 14:26:28 +01:00
|
|
|
import * as tf from '../dist/tfjs.esm.js';
|
2021-02-08 17:39:09 +01:00
|
|
|
import * as backend from './tfjs/backend';
|
|
|
|
import * as facemesh from './blazeface/facemesh';
|
|
|
|
import * as age from './age/age';
|
|
|
|
import * as gender from './gender/gender';
|
|
|
|
import * as emotion from './emotion/emotion';
|
|
|
|
import * as embedding from './embedding/embedding';
|
|
|
|
import * as posenet from './posenet/posenet';
|
|
|
|
import * as handpose from './handpose/handpose';
|
2021-03-04 16:33:08 +01:00
|
|
|
import * as blazepose from './blazepose/blazepose';
|
2021-02-08 17:39:09 +01:00
|
|
|
import * as gesture from './gesture/gesture';
|
|
|
|
import * as image from './image';
|
|
|
|
import * as profile from './profile';
|
|
|
|
import * as config from '../config';
|
|
|
|
import * as sample from './sample';
|
2020-11-10 02:13:38 +01:00
|
|
|
import * as app from '../package.json';
|
2021-03-05 17:43:50 +01:00
|
|
|
import * as draw from './draw';
|
2020-10-15 21:25:58 +02:00
|
|
|
|
2020-10-17 16:06:02 +02:00
|
|
|
// helper function: gets elapsed time on both browser and nodejs
|
2020-10-16 16:12:12 +02:00
|
|
|
const now = () => {
|
|
|
|
if (typeof performance !== 'undefined') return performance.now();
|
2021-02-08 17:39:09 +01:00
|
|
|
return parseInt((Number(process.hrtime.bigint()) / 1000 / 1000).toString());
|
2020-10-16 16:12:12 +02:00
|
|
|
};
|
2020-10-12 01:22:43 +02:00
|
|
|
|
2021-03-14 04:31:09 +01:00
|
|
|
type Tensor = {};
|
|
|
|
type Model = {};
|
|
|
|
export type Result = {
|
|
|
|
face: Array<{
|
|
|
|
confidence: Number,
|
|
|
|
boxConfidence: Number,
|
|
|
|
faceConfidence: Number,
|
|
|
|
box: [Number, Number, Number, Number],
|
|
|
|
mesh: Array<[Number, Number, Number]>
|
|
|
|
meshRaw: Array<[Number, Number, Number]>
|
|
|
|
boxRaw: [Number, Number, Number, Number],
|
|
|
|
annotations: any,
|
|
|
|
age: Number,
|
|
|
|
gender: String,
|
|
|
|
genderConfidence: Number,
|
|
|
|
emotion: String,
|
|
|
|
embedding: any,
|
|
|
|
iris: Number,
|
|
|
|
angle: { roll: Number | null, yaw: Number | null, pitch: Number | null },
|
|
|
|
}>,
|
|
|
|
body: Array<{
|
|
|
|
id: Number,
|
|
|
|
part: String,
|
|
|
|
position: { x: Number, y: Number, z: Number },
|
|
|
|
score: Number,
|
|
|
|
presence: Number }>,
|
|
|
|
hand: Array<{
|
|
|
|
confidence: Number,
|
|
|
|
box: any,
|
|
|
|
landmarks: any,
|
|
|
|
annotations: any,
|
|
|
|
}>,
|
|
|
|
gesture: Array<{
|
|
|
|
part: String,
|
|
|
|
gesture: String,
|
|
|
|
}>,
|
|
|
|
performance: { any },
|
|
|
|
canvas: OffscreenCanvas | HTMLCanvasElement,
|
|
|
|
}
|
|
|
|
|
|
|
|
export type { default as Config } from '../config';
|
|
|
|
|
2020-10-17 16:06:02 +02:00
|
|
|
// helper function: perform deep merge of multiple objects so it allows full inheriance with overrides
|
2020-10-12 01:22:43 +02:00
|
|
|
function mergeDeep(...objects) {
|
|
|
|
const isObject = (obj) => obj && typeof obj === 'object';
|
|
|
|
return objects.reduce((prev, obj) => {
|
2020-10-12 03:21:41 +02:00
|
|
|
Object.keys(obj || {}).forEach((key) => {
|
2020-10-12 01:22:43 +02:00
|
|
|
const pVal = prev[key];
|
|
|
|
const oVal = obj[key];
|
2021-02-26 15:04:08 +01:00
|
|
|
if (Array.isArray(pVal) && Array.isArray(oVal)) prev[key] = pVal.concat(...oVal);
|
|
|
|
else if (isObject(pVal) && isObject(oVal)) prev[key] = mergeDeep(pVal, oVal);
|
|
|
|
else prev[key] = oVal;
|
2020-10-12 01:22:43 +02:00
|
|
|
});
|
|
|
|
return prev;
|
|
|
|
}, {});
|
|
|
|
}
|
|
|
|
|
2021-03-14 04:31:09 +01:00
|
|
|
export class Human {
|
|
|
|
version: String;
|
2021-03-10 15:44:45 +01:00
|
|
|
config: typeof config.default;
|
2021-03-14 04:31:09 +01:00
|
|
|
state: String;
|
|
|
|
image: { tensor: Tensor, canvas: OffscreenCanvas | HTMLCanvasElement };
|
2021-03-10 15:44:45 +01:00
|
|
|
// classes
|
|
|
|
tf: typeof tf;
|
2021-03-14 04:31:09 +01:00
|
|
|
draw: { options?: typeof draw.options, gesture: Function, face: Function, body: Function, hand: Function, canvas: Function, all: Function };
|
2021-02-08 17:39:09 +01:00
|
|
|
// models
|
2021-03-10 15:44:45 +01:00
|
|
|
models: {
|
2021-03-14 04:31:09 +01:00
|
|
|
face: facemesh.MediaPipeFaceMesh | null,
|
|
|
|
posenet: posenet.PoseNet | null,
|
|
|
|
blazepose: Model | null,
|
|
|
|
handpose: handpose.HandPose | null,
|
|
|
|
iris: Model | null,
|
|
|
|
age: Model | null,
|
|
|
|
gender: Model | null,
|
|
|
|
emotion: Model | null,
|
|
|
|
embedding: Model | null,
|
2021-03-10 15:44:45 +01:00
|
|
|
};
|
|
|
|
classes: {
|
|
|
|
facemesh: typeof facemesh;
|
|
|
|
age: typeof age;
|
|
|
|
gender: typeof gender;
|
|
|
|
emotion: typeof emotion;
|
|
|
|
body: typeof posenet | typeof blazepose;
|
|
|
|
hand: typeof handpose;
|
|
|
|
};
|
2021-03-14 04:31:09 +01:00
|
|
|
sysinfo: { platform: String, agent: String };
|
2021-03-10 15:44:45 +01:00
|
|
|
#package: any;
|
|
|
|
#perf: any;
|
|
|
|
#numTensors: number;
|
2021-03-14 04:31:09 +01:00
|
|
|
#analyzeMemoryLeaks: Boolean;
|
|
|
|
#checkSanity: Boolean;
|
|
|
|
#firstRun: Boolean;
|
2021-03-10 15:44:45 +01:00
|
|
|
// definition end
|
2021-02-08 17:39:09 +01:00
|
|
|
|
2020-11-09 12:32:11 +01:00
|
|
|
constructor(userConfig = {}) {
|
2020-10-19 17:03:48 +02:00
|
|
|
this.tf = tf;
|
2021-03-05 17:43:50 +01:00
|
|
|
this.draw = draw;
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#package = app;
|
2020-10-19 17:03:48 +02:00
|
|
|
this.version = app.version;
|
2020-11-10 02:13:38 +01:00
|
|
|
this.config = mergeDeep(config.default, userConfig);
|
2020-10-19 17:03:48 +02:00
|
|
|
this.state = 'idle';
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#numTensors = 0;
|
|
|
|
this.#analyzeMemoryLeaks = false;
|
|
|
|
this.#checkSanity = false;
|
|
|
|
this.#firstRun = true;
|
|
|
|
this.#perf = {};
|
2020-10-19 17:03:48 +02:00
|
|
|
// object that contains all initialized models
|
|
|
|
this.models = {
|
2021-03-10 15:44:45 +01:00
|
|
|
face: null,
|
2020-10-19 17:03:48 +02:00
|
|
|
posenet: null,
|
2021-03-04 16:33:08 +01:00
|
|
|
blazepose: null,
|
2020-10-19 17:03:48 +02:00
|
|
|
handpose: null,
|
|
|
|
iris: null,
|
|
|
|
age: null,
|
|
|
|
gender: null,
|
|
|
|
emotion: null,
|
2021-03-10 15:44:45 +01:00
|
|
|
embedding: null,
|
2020-10-19 17:03:48 +02:00
|
|
|
};
|
2021-03-04 16:33:08 +01:00
|
|
|
// export access to image processing
|
2021-03-10 15:44:45 +01:00
|
|
|
// @ts-ignore
|
2021-03-14 04:31:09 +01:00
|
|
|
this.image = (input: Tensor | ImageData | HTMLCanvasElement | HTMLVideoElement | OffscreenCanvas) => image.process(input, this.config);
|
2020-10-19 17:03:48 +02:00
|
|
|
// export raw access to underlying models
|
2021-03-10 15:44:45 +01:00
|
|
|
this.classes = {
|
|
|
|
facemesh,
|
|
|
|
age,
|
|
|
|
gender,
|
|
|
|
emotion,
|
2021-03-11 16:26:14 +01:00
|
|
|
body: this.config.body.modelPath.includes('posenet') ? posenet : blazepose,
|
2021-03-10 15:44:45 +01:00
|
|
|
hand: handpose,
|
|
|
|
};
|
2021-03-06 16:38:04 +01:00
|
|
|
// include platform info
|
|
|
|
this.sysinfo = sysinfo.info();
|
2020-10-18 18:12:09 +02:00
|
|
|
}
|
2020-10-19 17:03:48 +02:00
|
|
|
|
2021-03-10 15:44:45 +01:00
|
|
|
profileData(): { newBytes, newTensors, peakBytes, numKernelOps, timeKernelOps, slowestKernelOps, largestKernelOps } | {} {
|
2020-11-01 19:07:53 +01:00
|
|
|
if (this.config.profile) return profile.data;
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2020-10-19 17:03:48 +02:00
|
|
|
// helper function: measure tensor leak
|
2021-03-14 04:31:09 +01:00
|
|
|
/** @hidden */
|
2021-03-10 15:44:45 +01:00
|
|
|
#analyze = (...msg) => {
|
|
|
|
if (!this.#analyzeMemoryLeaks) return;
|
2021-02-19 14:35:41 +01:00
|
|
|
const current = this.tf.engine().state.numTensors;
|
2021-03-10 15:44:45 +01:00
|
|
|
const previous = this.#numTensors;
|
|
|
|
this.#numTensors = current;
|
2020-10-19 17:03:48 +02:00
|
|
|
const leaked = current - previous;
|
2020-12-08 15:00:44 +01:00
|
|
|
if (leaked !== 0) log(...msg, leaked);
|
2020-10-18 18:12:09 +02:00
|
|
|
}
|
2020-10-17 13:15:23 +02:00
|
|
|
|
2020-11-04 16:18:22 +01:00
|
|
|
// quick sanity check on inputs
|
2021-03-14 04:31:09 +01:00
|
|
|
/** @hidden */
|
|
|
|
#sanity = (input): null | String => {
|
2021-03-10 15:44:45 +01:00
|
|
|
if (!this.#checkSanity) return null;
|
2020-11-03 16:55:33 +01:00
|
|
|
if (!input) return 'input is not defined';
|
2021-03-14 04:31:09 +01:00
|
|
|
if (this.tf.ENV.flags.IS_NODE && !(input instanceof tf.Tensor)) return 'input must be a tensor';
|
2020-11-03 16:55:33 +01:00
|
|
|
try {
|
2021-02-19 14:35:41 +01:00
|
|
|
this.tf.getBackend();
|
2020-11-03 16:55:33 +01:00
|
|
|
} catch {
|
|
|
|
return 'backend not loaded';
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-03-14 04:31:09 +01:00
|
|
|
simmilarity(embedding1: Array<Number>, embedding2: Array<Number>): Number {
|
2020-11-13 22:13:35 +01:00
|
|
|
if (this.config.face.embedding.enabled) return embedding.simmilarity(embedding1, embedding2);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-03-14 04:31:09 +01:00
|
|
|
enhance(input: Tensor): Tensor | null {
|
2021-03-12 18:54:08 +01:00
|
|
|
if (this.config.face.embedding.enabled) return embedding.enhance(input);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-11-04 16:18:22 +01:00
|
|
|
// preload models, not explicitly required as it's done automatically on first use
|
2021-03-14 04:31:09 +01:00
|
|
|
async load(userConfig: Object = {}) {
|
2020-11-06 17:39:39 +01:00
|
|
|
this.state = 'load';
|
|
|
|
const timeStamp = now();
|
2020-11-06 21:35:58 +01:00
|
|
|
if (userConfig) this.config = mergeDeep(this.config, userConfig);
|
2020-11-03 15:34:36 +01:00
|
|
|
|
2021-03-10 15:44:45 +01:00
|
|
|
if (this.#firstRun) {
|
2021-03-06 16:38:04 +01:00
|
|
|
if (this.config.debug) log(`version: ${this.version}`);
|
|
|
|
if (this.config.debug) log(`tfjs version: ${this.tf.version_core}`);
|
|
|
|
if (this.config.debug) log('platform:', this.sysinfo.platform);
|
|
|
|
if (this.config.debug) log('agent:', this.sysinfo.agent);
|
|
|
|
|
2021-03-10 15:44:45 +01:00
|
|
|
await this.#checkBackend(true);
|
2021-02-19 14:35:41 +01:00
|
|
|
if (this.tf.ENV.flags.IS_BROWSER) {
|
2021-03-02 17:27:42 +01:00
|
|
|
if (this.config.debug) log('configuration:', this.config);
|
|
|
|
if (this.config.debug) log('tf flags:', this.tf.ENV.flags);
|
2020-11-21 18:21:47 +01:00
|
|
|
}
|
2020-11-03 15:34:36 +01:00
|
|
|
}
|
2020-11-06 17:39:39 +01:00
|
|
|
if (this.config.async) {
|
|
|
|
[
|
2021-02-06 23:41:53 +01:00
|
|
|
this.models.face,
|
2020-11-06 17:39:39 +01:00
|
|
|
this.models.age,
|
|
|
|
this.models.gender,
|
|
|
|
this.models.emotion,
|
2020-11-13 22:13:35 +01:00
|
|
|
this.models.embedding,
|
2020-11-06 17:39:39 +01:00
|
|
|
this.models.handpose,
|
2021-03-04 16:33:08 +01:00
|
|
|
this.models.posenet,
|
|
|
|
this.models.blazepose,
|
2020-11-06 17:39:39 +01:00
|
|
|
] = await Promise.all([
|
2021-03-10 00:32:35 +01:00
|
|
|
this.models.face || (this.config.face.enabled ? facemesh.load(this.config) : null),
|
2020-11-12 04:40:05 +01:00
|
|
|
this.models.age || ((this.config.face.enabled && this.config.face.age.enabled) ? age.load(this.config) : null),
|
|
|
|
this.models.gender || ((this.config.face.enabled && this.config.face.gender.enabled) ? gender.load(this.config) : null),
|
|
|
|
this.models.emotion || ((this.config.face.enabled && this.config.face.emotion.enabled) ? emotion.load(this.config) : null),
|
2020-11-13 22:13:35 +01:00
|
|
|
this.models.embedding || ((this.config.face.enabled && this.config.face.embedding.enabled) ? embedding.load(this.config) : null),
|
2020-11-17 23:42:44 +01:00
|
|
|
this.models.handpose || (this.config.hand.enabled ? handpose.load(this.config) : null),
|
2021-03-11 16:26:14 +01:00
|
|
|
this.models.posenet || (this.config.body.enabled && this.config.body.modelPath.includes('posenet') ? posenet.load(this.config) : null),
|
|
|
|
this.models.posenet || (this.config.body.enabled && this.config.body.modelPath.includes('blazepose') ? blazepose.load(this.config) : null),
|
2020-11-06 17:39:39 +01:00
|
|
|
]);
|
|
|
|
} else {
|
2021-03-10 00:32:35 +01:00
|
|
|
if (this.config.face.enabled && !this.models.face) this.models.face = await facemesh.load(this.config);
|
2020-11-07 16:37:19 +01:00
|
|
|
if (this.config.face.enabled && this.config.face.age.enabled && !this.models.age) this.models.age = await age.load(this.config);
|
|
|
|
if (this.config.face.enabled && this.config.face.gender.enabled && !this.models.gender) this.models.gender = await gender.load(this.config);
|
|
|
|
if (this.config.face.enabled && this.config.face.emotion.enabled && !this.models.emotion) this.models.emotion = await emotion.load(this.config);
|
2020-11-13 22:13:35 +01:00
|
|
|
if (this.config.face.enabled && this.config.face.embedding.enabled && !this.models.embedding) this.models.embedding = await embedding.load(this.config);
|
2020-11-17 23:42:44 +01:00
|
|
|
if (this.config.hand.enabled && !this.models.handpose) this.models.handpose = await handpose.load(this.config);
|
2021-03-11 16:26:14 +01:00
|
|
|
if (this.config.body.enabled && !this.models.posenet && this.config.body.modelPath.includes('posenet')) this.models.posenet = await posenet.load(this.config);
|
|
|
|
if (this.config.body.enabled && !this.models.blazepose && this.config.body.modelPath.includes('blazepose')) this.models.blazepose = await blazepose.load(this.config);
|
2020-10-18 18:12:09 +02:00
|
|
|
}
|
2021-01-12 15:55:08 +01:00
|
|
|
|
2021-03-10 15:44:45 +01:00
|
|
|
if (this.#firstRun) {
|
2021-03-02 17:27:42 +01:00
|
|
|
if (this.config.debug) log('tf engine state:', this.tf.engine().state.numBytes, 'bytes', this.tf.engine().state.numTensors, 'tensors');
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#firstRun = false;
|
2021-01-12 15:55:08 +01:00
|
|
|
}
|
|
|
|
|
2020-11-06 17:39:39 +01:00
|
|
|
const current = Math.trunc(now() - timeStamp);
|
2021-03-10 15:44:45 +01:00
|
|
|
if (current > (this.#perf.load || 0)) this.#perf.load = current;
|
2020-10-18 15:21:53 +02:00
|
|
|
}
|
2020-10-17 17:38:24 +02:00
|
|
|
|
2020-11-04 16:18:22 +01:00
|
|
|
// check if backend needs initialization if it changed
|
2021-03-14 04:31:09 +01:00
|
|
|
/** @hidden */
|
2021-03-10 15:44:45 +01:00
|
|
|
#checkBackend = async (force = false) => {
|
2021-02-19 14:35:41 +01:00
|
|
|
if (this.config.backend && (this.config.backend !== '') && force || (this.tf.getBackend() !== this.config.backend)) {
|
2020-11-21 18:21:47 +01:00
|
|
|
const timeStamp = now();
|
2020-10-30 15:23:49 +01:00
|
|
|
this.state = 'backend';
|
2020-11-01 19:07:53 +01:00
|
|
|
/* force backend reload
|
2020-10-30 16:57:23 +01:00
|
|
|
if (this.config.backend in tf.engine().registry) {
|
2020-11-01 19:07:53 +01:00
|
|
|
const backendFactory = tf.findBackendFactory(this.config.backend);
|
|
|
|
tf.removeBackend(this.config.backend);
|
|
|
|
tf.registerBackend(this.config.backend, backendFactory);
|
2020-10-30 15:23:49 +01:00
|
|
|
} else {
|
2020-12-08 15:00:44 +01:00
|
|
|
log('Backend not registred:', this.config.backend);
|
2020-10-30 15:23:49 +01:00
|
|
|
}
|
2020-11-01 19:07:53 +01:00
|
|
|
*/
|
2020-11-10 02:13:38 +01:00
|
|
|
|
2021-03-02 17:27:42 +01:00
|
|
|
if (this.config.backend && this.config.backend !== '') {
|
|
|
|
if (this.config.debug) log('setting backend:', this.config.backend);
|
2020-11-10 02:13:38 +01:00
|
|
|
|
2021-03-02 17:27:42 +01:00
|
|
|
if (this.config.backend === 'wasm') {
|
2021-03-06 16:38:04 +01:00
|
|
|
if (this.config.debug) log('wasm path:', this.config.wasmPath);
|
2021-03-02 17:27:42 +01:00
|
|
|
this.tf.setWasmPaths(this.config.wasmPath);
|
|
|
|
const simd = await this.tf.env().getAsync('WASM_HAS_SIMD_SUPPORT');
|
2021-03-06 16:38:04 +01:00
|
|
|
const mt = await this.tf.env().getAsync('WASM_HAS_MULTITHREAD_SUPPORT');
|
|
|
|
if (this.config.debug) log(`wasm execution: ${simd ? 'SIMD' : 'no SIMD'} ${mt ? 'multithreaded' : 'singlethreaded'}`);
|
2021-03-02 17:27:42 +01:00
|
|
|
if (!simd) log('warning: wasm simd support is not enabled');
|
|
|
|
}
|
2020-11-10 02:13:38 +01:00
|
|
|
|
2021-03-02 17:27:42 +01:00
|
|
|
if (this.config.backend === 'humangl') backend.register();
|
|
|
|
try {
|
|
|
|
await this.tf.setBackend(this.config.backend);
|
|
|
|
} catch (err) {
|
|
|
|
log('error: cannot set backend:', this.config.backend, err);
|
|
|
|
}
|
2020-12-13 00:34:30 +01:00
|
|
|
}
|
2021-02-19 14:35:41 +01:00
|
|
|
this.tf.enableProdMode();
|
2020-11-01 19:07:53 +01:00
|
|
|
/* debug mode is really too mcuh
|
2020-11-09 12:32:11 +01:00
|
|
|
tf.enableDebugMode();
|
2020-11-01 19:07:53 +01:00
|
|
|
*/
|
2021-02-19 14:35:41 +01:00
|
|
|
if (this.tf.getBackend() === 'webgl') {
|
2020-11-09 12:32:11 +01:00
|
|
|
if (this.config.deallocate) {
|
2020-12-08 15:00:44 +01:00
|
|
|
log('changing webgl: WEBGL_DELETE_TEXTURE_THRESHOLD:', this.config.deallocate);
|
2021-02-19 14:35:41 +01:00
|
|
|
this.tf.ENV.set('WEBGL_DELETE_TEXTURE_THRESHOLD', this.config.deallocate ? 0 : -1);
|
2020-11-09 12:32:11 +01:00
|
|
|
}
|
2021-02-22 15:13:11 +01:00
|
|
|
// this.tf.ENV.set('WEBGL_FORCE_F16_TEXTURES', true);
|
|
|
|
// this.tf.ENV.set('WEBGL_PACK_DEPTHWISECONV', true);
|
2021-02-19 14:35:41 +01:00
|
|
|
const gl = await this.tf.backend().getGPGPUContext().gl;
|
2021-03-02 17:27:42 +01:00
|
|
|
if (this.config.debug) log(`gl version:${gl.getParameter(gl.VERSION)} renderer:${gl.getParameter(gl.RENDERER)}`);
|
2020-11-01 19:07:53 +01:00
|
|
|
}
|
2021-02-19 14:35:41 +01:00
|
|
|
await this.tf.ready();
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#perf.backend = Math.trunc(now() - timeStamp);
|
2020-10-30 15:23:49 +01:00
|
|
|
}
|
2020-11-06 17:39:39 +01:00
|
|
|
}
|
|
|
|
|
2021-03-14 04:31:09 +01:00
|
|
|
/** @hidden */
|
|
|
|
#calculateFaceAngle = (mesh): { roll: Number | null, yaw: Number | null, pitch: Number | null } => {
|
2021-03-13 19:47:45 +01:00
|
|
|
if (!mesh || mesh.length < 300) return { roll: null, yaw: null, pitch: null };
|
2021-03-06 23:22:47 +01:00
|
|
|
const radians = (a1, a2, b1, b2) => Math.atan2(b2 - a2, b1 - a1);
|
|
|
|
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
|
|
|
const degrees = (theta) => Math.abs(((theta * 180) / Math.PI) % 360);
|
|
|
|
const angle = {
|
2021-03-08 13:32:24 +01:00
|
|
|
// values are in radians in range of -pi/2 to pi/2 which is -90 to +90 degrees
|
|
|
|
// value of 0 means center
|
2021-03-06 23:22:47 +01:00
|
|
|
// roll is face lean left/right
|
2021-03-08 13:32:24 +01:00
|
|
|
roll: radians(mesh[33][0], mesh[33][1], mesh[263][0], mesh[263][1]), // looking at x,y of outside corners of leftEye and rightEye
|
2021-03-06 23:22:47 +01:00
|
|
|
// yaw is face turn left/right
|
2021-03-08 13:32:24 +01:00
|
|
|
yaw: radians(mesh[33][0], mesh[33][2], mesh[263][0], mesh[263][2]), // looking at x,z of outside corners of leftEye and rightEye
|
2021-03-06 23:22:47 +01:00
|
|
|
// pitch is face move up/down
|
2021-03-08 13:32:24 +01:00
|
|
|
pitch: radians(mesh[10][1], mesh[10][2], mesh[152][1], mesh[152][2]), // looking at y,z of top and bottom points of the face
|
2021-03-06 23:22:47 +01:00
|
|
|
};
|
|
|
|
return angle;
|
|
|
|
}
|
|
|
|
|
2021-03-14 04:31:09 +01:00
|
|
|
/** @hidden */
|
2021-03-13 19:47:45 +01:00
|
|
|
#detectFace = async (input): Promise<any> => {
|
2020-11-06 17:39:39 +01:00
|
|
|
// run facemesh, includes blazeface and iris
|
|
|
|
// eslint-disable-next-line no-async-promise-executor
|
|
|
|
let timeStamp;
|
|
|
|
let ageRes;
|
|
|
|
let genderRes;
|
|
|
|
let emotionRes;
|
2020-11-13 22:13:35 +01:00
|
|
|
let embeddingRes;
|
2021-03-06 23:22:47 +01:00
|
|
|
const faceRes: Array<{
|
2021-03-14 04:31:09 +01:00
|
|
|
confidence: Number,
|
|
|
|
boxConfidence: Number,
|
|
|
|
faceConfidence: Number,
|
|
|
|
box: [Number, Number, Number, Number],
|
|
|
|
mesh: Array<[Number, Number, Number]>
|
|
|
|
meshRaw: Array<[Number, Number, Number]>
|
|
|
|
boxRaw: [Number, Number, Number, Number],
|
2021-03-06 23:22:47 +01:00
|
|
|
annotations: any,
|
2021-03-14 04:31:09 +01:00
|
|
|
age: Number,
|
|
|
|
gender: String,
|
|
|
|
genderConfidence: Number,
|
|
|
|
emotion: String,
|
2021-03-06 23:22:47 +01:00
|
|
|
embedding: any,
|
2021-03-14 04:31:09 +01:00
|
|
|
iris: Number,
|
|
|
|
angle: { roll: Number | null, yaw: Number | null, pitch: Number | null },
|
|
|
|
tensor: Tensor,
|
2021-03-06 23:22:47 +01:00
|
|
|
}> = [];
|
|
|
|
|
2020-11-06 17:39:39 +01:00
|
|
|
this.state = 'run:face';
|
|
|
|
timeStamp = now();
|
2021-02-06 23:41:53 +01:00
|
|
|
const faces = await this.models.face?.estimateFaces(input, this.config);
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#perf.face = Math.trunc(now() - timeStamp);
|
2021-03-14 04:31:09 +01:00
|
|
|
if (!faces) return [];
|
2020-11-06 17:39:39 +01:00
|
|
|
for (const face of faces) {
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#analyze('Get Face');
|
2020-11-13 22:13:35 +01:00
|
|
|
|
2020-11-06 17:39:39 +01:00
|
|
|
// is something went wrong, skip the face
|
|
|
|
if (!face.image || face.image.isDisposedInternal) {
|
2020-12-08 15:00:44 +01:00
|
|
|
log('Face object is disposed:', face.image);
|
2020-11-06 17:39:39 +01:00
|
|
|
continue;
|
|
|
|
}
|
2020-11-13 22:13:35 +01:00
|
|
|
|
2021-03-10 15:44:45 +01:00
|
|
|
const angle = this.#calculateFaceAngle(face.mesh);
|
2021-03-06 23:22:47 +01:00
|
|
|
|
2020-11-06 17:39:39 +01:00
|
|
|
// run age, inherits face from blazeface
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#analyze('Start Age:');
|
2020-11-06 17:39:39 +01:00
|
|
|
if (this.config.async) {
|
|
|
|
ageRes = this.config.face.age.enabled ? age.predict(face.image, this.config) : {};
|
|
|
|
} else {
|
|
|
|
this.state = 'run:age';
|
|
|
|
timeStamp = now();
|
|
|
|
ageRes = this.config.face.age.enabled ? await age.predict(face.image, this.config) : {};
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#perf.age = Math.trunc(now() - timeStamp);
|
2020-11-06 17:39:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// run gender, inherits face from blazeface
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#analyze('Start Gender:');
|
2020-11-06 17:39:39 +01:00
|
|
|
if (this.config.async) {
|
|
|
|
genderRes = this.config.face.gender.enabled ? gender.predict(face.image, this.config) : {};
|
|
|
|
} else {
|
|
|
|
this.state = 'run:gender';
|
|
|
|
timeStamp = now();
|
|
|
|
genderRes = this.config.face.gender.enabled ? await gender.predict(face.image, this.config) : {};
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#perf.gender = Math.trunc(now() - timeStamp);
|
2020-11-06 17:39:39 +01:00
|
|
|
}
|
2020-11-13 22:13:35 +01:00
|
|
|
|
2020-11-06 17:39:39 +01:00
|
|
|
// run emotion, inherits face from blazeface
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#analyze('Start Emotion:');
|
2020-11-06 17:39:39 +01:00
|
|
|
if (this.config.async) {
|
|
|
|
emotionRes = this.config.face.emotion.enabled ? emotion.predict(face.image, this.config) : {};
|
|
|
|
} else {
|
|
|
|
this.state = 'run:emotion';
|
|
|
|
timeStamp = now();
|
|
|
|
emotionRes = this.config.face.emotion.enabled ? await emotion.predict(face.image, this.config) : {};
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#perf.emotion = Math.trunc(now() - timeStamp);
|
2020-11-06 17:39:39 +01:00
|
|
|
}
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#analyze('End Emotion:');
|
2020-11-06 17:39:39 +01:00
|
|
|
|
2020-11-13 22:13:35 +01:00
|
|
|
// run emotion, inherits face from blazeface
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#analyze('Start Embedding:');
|
2020-11-13 22:13:35 +01:00
|
|
|
if (this.config.async) {
|
2021-03-12 18:54:08 +01:00
|
|
|
embeddingRes = this.config.face.embedding.enabled ? embedding.predict(face, this.config) : [];
|
2020-11-13 22:13:35 +01:00
|
|
|
} else {
|
|
|
|
this.state = 'run:embedding';
|
|
|
|
timeStamp = now();
|
2021-03-12 18:54:08 +01:00
|
|
|
embeddingRes = this.config.face.embedding.enabled ? await embedding.predict(face, this.config) : [];
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#perf.embedding = Math.trunc(now() - timeStamp);
|
2020-11-13 22:13:35 +01:00
|
|
|
}
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#analyze('End Emotion:');
|
2020-11-13 22:13:35 +01:00
|
|
|
|
2020-11-06 17:39:39 +01:00
|
|
|
// if async wait for results
|
|
|
|
if (this.config.async) {
|
2020-11-13 22:13:35 +01:00
|
|
|
[ageRes, genderRes, emotionRes, embeddingRes] = await Promise.all([ageRes, genderRes, emotionRes, embeddingRes]);
|
2020-11-06 17:39:39 +01:00
|
|
|
}
|
|
|
|
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#analyze('Finish Face:');
|
2020-11-06 17:39:39 +01:00
|
|
|
|
|
|
|
// calculate iris distance
|
2020-11-09 14:57:24 +01:00
|
|
|
// iris: array[ center, left, top, right, bottom]
|
2021-02-06 23:41:53 +01:00
|
|
|
if (!this.config.face.iris.enabled && face?.annotations?.leftEyeIris && face?.annotations?.rightEyeIris) {
|
2021-01-05 22:41:54 +01:00
|
|
|
delete face.annotations.leftEyeIris;
|
|
|
|
delete face.annotations.rightEyeIris;
|
|
|
|
}
|
2021-02-06 23:41:53 +01:00
|
|
|
const irisSize = (face.annotations?.leftEyeIris && face.annotations?.rightEyeIris)
|
2020-11-09 14:57:24 +01:00
|
|
|
/* average human iris size is 11.7mm */
|
|
|
|
? 11.7 * Math.max(Math.abs(face.annotations.leftEyeIris[3][0] - face.annotations.leftEyeIris[1][0]), Math.abs(face.annotations.rightEyeIris[4][1] - face.annotations.rightEyeIris[2][1]))
|
2020-11-06 17:39:39 +01:00
|
|
|
: 0;
|
|
|
|
|
|
|
|
// combine results
|
|
|
|
faceRes.push({
|
2021-03-12 18:54:08 +01:00
|
|
|
...face,
|
2020-11-06 17:39:39 +01:00
|
|
|
age: ageRes.age,
|
|
|
|
gender: genderRes.gender,
|
|
|
|
genderConfidence: genderRes.confidence,
|
|
|
|
emotion: emotionRes,
|
2020-11-13 22:13:35 +01:00
|
|
|
embedding: embeddingRes,
|
2020-11-09 14:57:24 +01:00
|
|
|
iris: (irisSize !== 0) ? Math.trunc(irisSize) / 100 : 0,
|
2021-03-06 23:22:47 +01:00
|
|
|
angle,
|
2021-03-13 17:26:53 +01:00
|
|
|
tensor: this.config.face.detector.return ? face.image?.squeeze() : null,
|
2020-11-06 17:39:39 +01:00
|
|
|
});
|
2021-03-13 17:26:53 +01:00
|
|
|
// dispose original face tensor
|
2021-02-06 23:41:53 +01:00
|
|
|
face.image?.dispose();
|
2021-03-13 17:26:53 +01:00
|
|
|
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#analyze('End Face');
|
2020-11-06 17:39:39 +01:00
|
|
|
}
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#analyze('End FaceMesh:');
|
2020-11-06 17:39:39 +01:00
|
|
|
if (this.config.async) {
|
2021-03-10 15:44:45 +01:00
|
|
|
if (this.#perf.face) delete this.#perf.face;
|
|
|
|
if (this.#perf.age) delete this.#perf.age;
|
|
|
|
if (this.#perf.gender) delete this.#perf.gender;
|
|
|
|
if (this.#perf.emotion) delete this.#perf.emotion;
|
2020-11-06 17:39:39 +01:00
|
|
|
}
|
|
|
|
return faceRes;
|
2020-10-30 15:23:49 +01:00
|
|
|
}
|
|
|
|
|
2020-11-04 16:18:22 +01:00
|
|
|
// main detect function
|
2021-03-14 04:31:09 +01:00
|
|
|
async detect(input: Tensor | ImageData | HTMLCanvasElement | HTMLVideoElement | OffscreenCanvas, userConfig: Object = {}): Promise<Result | { error: String }> {
|
2020-11-04 16:18:22 +01:00
|
|
|
// detection happens inside a promise
|
2020-10-19 17:03:48 +02:00
|
|
|
return new Promise(async (resolve) => {
|
2020-11-13 22:13:35 +01:00
|
|
|
this.state = 'config';
|
|
|
|
let timeStamp;
|
|
|
|
|
|
|
|
// update configuration
|
|
|
|
this.config = mergeDeep(this.config, userConfig);
|
|
|
|
|
|
|
|
// sanity checks
|
|
|
|
this.state = 'check';
|
2021-03-10 15:44:45 +01:00
|
|
|
const error = this.#sanity(input);
|
2020-11-13 22:13:35 +01:00
|
|
|
if (error) {
|
2020-12-08 15:00:44 +01:00
|
|
|
log(error, input);
|
2020-11-13 22:13:35 +01:00
|
|
|
resolve({ error });
|
|
|
|
}
|
|
|
|
|
2020-10-19 17:03:48 +02:00
|
|
|
const timeStart = now();
|
2020-10-14 19:23:02 +02:00
|
|
|
|
2020-10-19 17:03:48 +02:00
|
|
|
// configure backend
|
2021-03-10 15:44:45 +01:00
|
|
|
await this.#checkBackend();
|
2020-10-17 16:06:02 +02:00
|
|
|
|
2020-10-19 17:03:48 +02:00
|
|
|
// load models if enabled
|
|
|
|
await this.load();
|
2020-10-18 15:21:53 +02:00
|
|
|
|
2021-02-19 14:35:41 +01:00
|
|
|
if (this.config.scoped) this.tf.engine().startScope();
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#analyze('Start Scope:');
|
2020-10-12 01:22:43 +02:00
|
|
|
|
2020-10-16 16:12:12 +02:00
|
|
|
timeStamp = now();
|
2020-11-04 16:18:22 +01:00
|
|
|
const process = image.process(input, this.config);
|
2020-11-20 13:52:50 +01:00
|
|
|
if (!process || !process.tensor) {
|
2020-12-08 15:00:44 +01:00
|
|
|
log('could not convert input to tensor');
|
2020-11-20 13:52:50 +01:00
|
|
|
resolve({ error: 'could not convert input to tensor' });
|
|
|
|
return;
|
|
|
|
}
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#perf.image = Math.trunc(now() - timeStamp);
|
|
|
|
this.#analyze('Get Image:');
|
2020-10-19 17:03:48 +02:00
|
|
|
|
2021-03-06 23:22:47 +01:00
|
|
|
// prepare where to store model results
|
|
|
|
let bodyRes;
|
|
|
|
let handRes;
|
|
|
|
let faceRes;
|
|
|
|
|
2020-11-06 17:39:39 +01:00
|
|
|
// run face detection followed by all models that rely on face bounding box: face mesh, age, gender, emotion
|
|
|
|
if (this.config.async) {
|
2021-03-10 15:44:45 +01:00
|
|
|
faceRes = this.config.face.enabled ? this.#detectFace(process.tensor) : [];
|
|
|
|
if (this.#perf.face) delete this.#perf.face;
|
2020-11-06 17:39:39 +01:00
|
|
|
} else {
|
2020-10-19 17:03:48 +02:00
|
|
|
this.state = 'run:face';
|
2020-10-16 16:12:12 +02:00
|
|
|
timeStamp = now();
|
2021-03-10 15:44:45 +01:00
|
|
|
faceRes = this.config.face.enabled ? await this.#detectFace(process.tensor) : [];
|
|
|
|
this.#perf.face = Math.trunc(now() - timeStamp);
|
2020-10-14 02:52:30 +02:00
|
|
|
}
|
2020-10-13 04:01:35 +02:00
|
|
|
|
2021-03-04 16:33:08 +01:00
|
|
|
// run body: can be posenet or blazepose
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#analyze('Start Body:');
|
2020-11-04 07:11:24 +01:00
|
|
|
if (this.config.async) {
|
2021-03-11 16:26:14 +01:00
|
|
|
if (this.config.body.modelPath.includes('posenet')) bodyRes = this.config.body.enabled ? this.models.posenet?.estimatePoses(process.tensor, this.config) : [];
|
2021-03-04 16:33:08 +01:00
|
|
|
else bodyRes = this.config.body.enabled ? blazepose.predict(process.tensor, this.config) : [];
|
2021-03-10 15:44:45 +01:00
|
|
|
if (this.#perf.body) delete this.#perf.body;
|
2020-11-04 07:11:24 +01:00
|
|
|
} else {
|
|
|
|
this.state = 'run:body';
|
|
|
|
timeStamp = now();
|
2021-03-11 16:26:14 +01:00
|
|
|
if (this.config.body.modelPath.includes('posenet')) bodyRes = this.config.body.enabled ? await this.models.posenet?.estimatePoses(process.tensor, this.config) : [];
|
2021-03-04 16:33:08 +01:00
|
|
|
else bodyRes = this.config.body.enabled ? await blazepose.predict(process.tensor, this.config) : [];
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#perf.body = Math.trunc(now() - timeStamp);
|
2020-11-04 07:11:24 +01:00
|
|
|
}
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#analyze('End Body:');
|
2020-11-04 07:11:24 +01:00
|
|
|
|
|
|
|
// run handpose
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#analyze('Start Hand:');
|
2020-11-04 07:11:24 +01:00
|
|
|
if (this.config.async) {
|
2020-12-27 14:12:22 +01:00
|
|
|
handRes = this.config.hand.enabled ? this.models.handpose?.estimateHands(process.tensor, this.config) : [];
|
2021-03-10 15:44:45 +01:00
|
|
|
if (this.#perf.hand) delete this.#perf.hand;
|
2020-11-04 07:11:24 +01:00
|
|
|
} else {
|
|
|
|
this.state = 'run:hand';
|
|
|
|
timeStamp = now();
|
2020-12-27 14:12:22 +01:00
|
|
|
handRes = this.config.hand.enabled ? await this.models.handpose?.estimateHands(process.tensor, this.config) : [];
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#perf.hand = Math.trunc(now() - timeStamp);
|
2020-11-04 07:11:24 +01:00
|
|
|
}
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#analyze('End Hand:');
|
2020-11-04 07:11:24 +01:00
|
|
|
|
2020-11-06 17:39:39 +01:00
|
|
|
// if async wait for results
|
|
|
|
if (this.config.async) {
|
2021-03-04 16:33:08 +01:00
|
|
|
[faceRes, bodyRes, handRes] = await Promise.all([faceRes, bodyRes, handRes]);
|
2020-11-06 17:39:39 +01:00
|
|
|
}
|
2020-11-04 16:18:22 +01:00
|
|
|
process.tensor.dispose();
|
2020-10-14 17:43:33 +02:00
|
|
|
|
2021-02-19 14:35:41 +01:00
|
|
|
if (this.config.scoped) this.tf.engine().endScope();
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#analyze('End Scope:');
|
2020-10-17 13:15:23 +02:00
|
|
|
|
2020-11-04 16:18:22 +01:00
|
|
|
let gestureRes = [];
|
|
|
|
if (this.config.gesture.enabled) {
|
|
|
|
timeStamp = now();
|
2021-02-08 18:47:38 +01:00
|
|
|
// @ts-ignore
|
2021-03-04 16:33:08 +01:00
|
|
|
gestureRes = [...gesture.face(faceRes), ...gesture.body(bodyRes), ...gesture.hand(handRes), ...gesture.iris(faceRes)];
|
2021-03-10 15:44:45 +01:00
|
|
|
if (!this.config.async) this.#perf.gesture = Math.trunc(now() - timeStamp);
|
|
|
|
else if (this.#perf.gesture) delete this.#perf.gesture;
|
2020-11-04 16:18:22 +01:00
|
|
|
}
|
|
|
|
|
2021-03-10 15:44:45 +01:00
|
|
|
this.#perf.total = Math.trunc(now() - timeStart);
|
2020-11-06 17:39:39 +01:00
|
|
|
this.state = 'idle';
|
2021-03-10 15:44:45 +01:00
|
|
|
resolve({ face: faceRes, body: bodyRes, hand: handRes, gesture: gestureRes, performance: this.#perf, canvas: process.canvas });
|
2020-10-19 17:03:48 +02:00
|
|
|
});
|
|
|
|
}
|
2020-11-09 20:26:10 +01:00
|
|
|
|
2021-03-14 04:31:09 +01:00
|
|
|
/** @hidden */
|
2021-03-10 15:44:45 +01:00
|
|
|
#warmupBitmap = async () => {
|
2020-12-16 20:49:14 +01:00
|
|
|
const b64toBlob = (base64, type = 'application/octet-stream') => fetch(`data:${type};base64,${base64}`).then((res) => res.blob());
|
|
|
|
let blob;
|
2021-01-12 14:24:00 +01:00
|
|
|
let res;
|
2020-12-16 20:49:14 +01:00
|
|
|
switch (this.config.warmup) {
|
|
|
|
case 'face': blob = await b64toBlob(sample.face); break;
|
|
|
|
case 'full': blob = await b64toBlob(sample.body); break;
|
|
|
|
default: blob = null;
|
|
|
|
}
|
2021-01-12 14:24:00 +01:00
|
|
|
if (blob) {
|
|
|
|
const bitmap = await createImageBitmap(blob);
|
2021-01-30 19:23:07 +01:00
|
|
|
res = await this.detect(bitmap, this.config);
|
2021-01-12 14:24:00 +01:00
|
|
|
bitmap.close();
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2021-03-14 04:31:09 +01:00
|
|
|
/** @hidden */
|
2021-03-10 15:44:45 +01:00
|
|
|
#warmupCanvas = async () => new Promise((resolve) => {
|
|
|
|
let src;
|
|
|
|
let size = 0;
|
|
|
|
switch (this.config.warmup) {
|
|
|
|
case 'face':
|
|
|
|
size = 256;
|
|
|
|
src = 'data:image/jpeg;base64,' + sample.face;
|
|
|
|
break;
|
|
|
|
case 'full':
|
|
|
|
case 'body':
|
|
|
|
size = 1200;
|
|
|
|
src = 'data:image/jpeg;base64,' + sample.body;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
src = null;
|
|
|
|
}
|
|
|
|
// src = encodeURI('../assets/human-sample-upper.jpg');
|
|
|
|
const img = new Image();
|
|
|
|
img.onload = async () => {
|
|
|
|
const canvas = (typeof OffscreenCanvas !== 'undefined') ? new OffscreenCanvas(size, size) : document.createElement('canvas');
|
|
|
|
canvas.width = img.naturalWidth;
|
|
|
|
canvas.height = img.naturalHeight;
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
ctx?.drawImage(img, 0, 0);
|
|
|
|
// const data = ctx?.getImageData(0, 0, canvas.height, canvas.width);
|
|
|
|
const res = await this.detect(canvas, this.config);
|
|
|
|
resolve(res);
|
|
|
|
};
|
|
|
|
if (src) img.src = src;
|
|
|
|
else resolve(null);
|
|
|
|
});
|
2021-01-12 14:24:00 +01:00
|
|
|
|
2021-03-14 04:31:09 +01:00
|
|
|
/** @hidden */
|
2021-03-10 15:44:45 +01:00
|
|
|
#warmupNode = async () => {
|
2021-01-30 19:23:07 +01:00
|
|
|
const atob = (str) => Buffer.from(str, 'base64');
|
|
|
|
const img = this.config.warmup === 'face' ? atob(sample.face) : atob(sample.body);
|
2021-02-08 17:39:09 +01:00
|
|
|
// @ts-ignore
|
|
|
|
const data = tf.node.decodeJpeg(img); // tf.node is only defined when compiling for nodejs
|
2021-01-30 19:23:07 +01:00
|
|
|
const expanded = data.expandDims(0);
|
2021-02-19 14:35:41 +01:00
|
|
|
this.tf.dispose(data);
|
2021-01-30 19:23:07 +01:00
|
|
|
// log('Input:', expanded);
|
|
|
|
const res = await this.detect(expanded, this.config);
|
2021-02-19 14:35:41 +01:00
|
|
|
this.tf.dispose(expanded);
|
2021-01-30 19:23:07 +01:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2021-03-14 04:31:09 +01:00
|
|
|
async warmup(userConfig: Object = {}): Promise<Result | { error }> {
|
2020-12-16 20:49:14 +01:00
|
|
|
const t0 = now();
|
2021-01-12 14:24:00 +01:00
|
|
|
if (userConfig) this.config = mergeDeep(this.config, userConfig);
|
|
|
|
const video = this.config.videoOptimized;
|
|
|
|
this.config.videoOptimized = false;
|
|
|
|
let res;
|
2021-03-10 15:44:45 +01:00
|
|
|
if (typeof createImageBitmap === 'function') res = await this.#warmupBitmap();
|
|
|
|
else if (typeof Image !== 'undefined') res = await this.#warmupCanvas();
|
|
|
|
else res = await this.#warmupNode();
|
2020-12-16 20:49:14 +01:00
|
|
|
this.config.videoOptimized = video;
|
2021-01-12 14:24:00 +01:00
|
|
|
const t1 = now();
|
2021-03-06 23:22:47 +01:00
|
|
|
if (this.config.debug) log('Warmup', this.config.warmup, Math.round(t1 - t0), 'ms', res);
|
2021-01-12 14:24:00 +01:00
|
|
|
return res;
|
2020-11-09 20:26:10 +01:00
|
|
|
}
|
2020-10-12 01:22:43 +02:00
|
|
|
}
|
|
|
|
|
2020-10-19 17:03:48 +02:00
|
|
|
export { Human as default };
|